pax_global_header00006660000000000000000000000064132460471300014512gustar00rootroot0000000000000052 comment=18a39ba68a5c43730802e18570ba512c6c0e84d9 corebird-1.7.4/000077500000000000000000000000001324604713000133145ustar00rootroot00000000000000corebird-1.7.4/._vimrc000066400000000000000000000001421324604713000145710ustar00rootroot00000000000000set wildignore+=*.c,*\~,CMakeFiles,Makefile,*.lo,test-driver,*.trs,*.stamp,Makefile.in,*.la,*.svg corebird-1.7.4/.gitignore000066400000000000000000000013131324604713000153020ustar00rootroot00000000000000corebird Corebird.db src/*.c Corebird src/widgets/*.c src/containers/*.c src/util/*.c *.c *.o *.lo *.la .dirstamp *.stamp *.stamp-t *.template *.sed *.sin *.header po/Rules-quot .deps ABOUT-NLS Makefile.in m4/ src/sql/*.c CMakeFiles Makefile src/Makefile src/*.cmake assets/avatars/* assets/banners/* assets/user/* stats *.swp src/util/Config.vala session.vim *~ aclocal.m4 compile config.h config.h.in config.guess config.log config.status config.sub configure autom4te.cache depcomp install-sh ltmain.sh missing libtool ChangeLog INSTALL intltool-extract.in intltool-merge.in intltool-update.in config.rpath test-driver stamp* .libs data/gschemas.compiled src/corebird.h src/corebird.vapi build/ libcorebird.vapi corebird-1.7.4/.tx/000077500000000000000000000000001324604713000140255ustar00rootroot00000000000000corebird-1.7.4/.tx/config000066400000000000000000000004051324604713000152140ustar00rootroot00000000000000[main] host = https://transifex.com [corebird.corebirdpot] file_filter = po/.po host = https://transifex.com source_file = po/corebird.pot source_lang = en type = PO lang_map = id_ID: id, ro_RO: ro, pt_PT: pt, ko_KR: ko, hi_IN: hi, ja_JP: ja, tr_TR: tr corebird-1.7.4/COPYING000066400000000000000000001045131324604713000143530ustar00rootroot00000000000000 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 . corebird-1.7.4/Makefile.am000066400000000000000000000004641324604713000153540ustar00rootroot00000000000000ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} SUBDIRS = src data po tests EXTRA_DIST = \ vapi/config.vapi \ vapi/rest-0.7.vapi \ vapi/libtl.vapi \ vapi/corebird-internal.vapi \ corebird.gresource.xml update-translations: po/corebird.pot @tx pull --all --force --skip --minimum-perc=1 @tx push --source corebird-1.7.4/README.md000066400000000000000000000070131324604713000145740ustar00rootroot00000000000000# Corebird 1.7.4 **Until further notice, all development is happening in the `next2` branch** [![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=baedert&url=http://github.com/baedert/corebird&title=corebird&language=vala&tags=github&category=software) [![Supprt Corebird on Patreon](https://baedert.org/patreon-donate-yellow.svg)](https://patreon.com/baedert) ## Shortcuts | Key | Description | | :-----: | :----------- | | `Ctrl + t` | Compose Tweet | | `Back` | Go one page back (this can be triggered via the back button on the keyboard, the back thumb button on the mouse or `Alt + Left`) | | `Forward` | Go one page forward (this can be triggered via the forward button on the keyboard, the forward thumb button on the mouse or `Alt + Right`) | | `Alt + num` | Go to page `num` (between 1 and 7 at the moment) | | `Ctrl + Shift + s` | Show/Hide topbar | | `Ctrl + p` | Show account settings | | `Ctrl + k` | Show account list | | `Ctrl + Shift + p` | Show application settings | When a tweet is focused (via keynav): - `r` - reply - `tt` - retweet - `f` - favorite - `q` - quote - `dd` - delete - `Return` - Show tweet details ## Translations Since February 2014, there's a [Corebird project on Transifex](https://www.transifex.com/projects/p/corebird) ## Contributing All contributions are welcome (artwork, design, code, just ideas, etc.) but if you're planning to actively change something bigger, talk to me first. ## Dependencies - `gtk+-3.0 >= 3.20` - `glib-2.0 >= 2.44` - `json-glib-1.0` - `sqlite3` - `libsoup-2.4` - `gettext >= 0.19.7` - `vala >= 0.28` (makedep) - `automake >= 1.14` (makedep) - `gst-plugins-base-1.0` (for playbin, disable via --disable-video) - `gst-plugins-bad-1.0 >= 1.6` (disable via --disable-video, default enabled) - `gst-plugins-good-1.0` (disable via --disable-video, default enabled) - `gst-libav-1.0` (disable via --disable-video, default enabled) - `gspell-1 >= 1.2` (for spellchecking, disable via --disable-spellcheck, default enabled) Note that the above packages are just rough estimations, the actual package names on your distribution may vary. If you pass `--disable-video` to the configure script, you don't need any gstreamer dependency but won't be able to view any videos. ## Compiling ``` ./autogen.sh --prefix=/usr make make install ``` Corebird installs its application icon into `/usr/share/icons/hicolor/`, so an appropriate call to `gtk-update-icon-cache` might be needed. corebird-1.7.4/autogen.sh000077500000000000000000000016601324604713000153200ustar00rootroot00000000000000#!/bin/sh # Run this to generate all the initial makefiles, etc. srcdir=`dirname $0` test -z "$srcdir" && srcdir=. (test -f $srcdir/configure.ac) || { echo "**Error**: Directory "\`$srcdir\'" does not look like the top-level project directory" exit 1 } olddir=`pwd` cd $srcdir PKG_NAME=`autoconf --trace 'AC_INIT:$1' "$srcdir/configure.ac"` if [ "$#" = 0 -a "x$NOCONFIGURE" = "x" ]; then echo "**Warning**: I am going to run \`configure' with no arguments." >&2 echo "If you wish to pass any to it, please specify them on the" >&2 echo \`$0\'" command line." >&2 echo "" >&2 fi set -x aclocal --install || exit 1 autoreconf --verbose --force --install -Wno-portability || exit 1 set +x cd $olddir if [ "$NOCONFIGURE" = "" ]; then set -x $srcdir/configure "$@" || exit 1 set +x if [ "$1" = "--help" ]; then exit 0 else echo "Now type \`make\' to compile $PKG_NAME" || exit 1 fi else echo "Skipping configure process." fi corebird-1.7.4/config.h.meson000066400000000000000000000047261324604713000160630ustar00rootroot00000000000000/* config.h.in. Generated from configure.ac by autoheader. */ /* Define to 1 if translation of program messages to the user's native language is requested. */ #mesondefine ENABLE_NLS /* Gettext Package */ #mesondefine GETTEXT_PACKAGE /* Define to 1 if you have the Mac OS X function CFLocaleCopyCurrent in the CoreFoundation framework. */ #mesondefine HAVE_CFLOCALECOPYCURRENT /* Define to 1 if you have the Mac OS X function CFPreferencesCopyAppValue in the CoreFoundation framework. */ #mesondefine HAVE_CFPREFERENCESCOPYAPPVALUE /* Define if the GNU dcgettext() function is already present or preinstalled. */ #mesondefine HAVE_DCGETTEXT /* Define to 1 if you have the header file. */ #mesondefine HAVE_DLFCN_H /* Define if the GNU gettext() function is already present or preinstalled. */ #mesondefine HAVE_GETTEXT /* Define if you have the iconv() function and it works. */ #mesondefine HAVE_ICONV /* Define to 1 if you have the header file. */ #mesondefine HAVE_INTTYPES_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_MEMORY_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_STDINT_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_STDLIB_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_STRINGS_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_STRING_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_SYS_STAT_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_SYS_TYPES_H /* Define to 1 if you have the header file. */ #mesondefine HAVE_UNISTD_H /* Define to the sub-directory where libtool stores uninstalled libraries. */ #mesondefine LT_OBJDIR /* Name of package */ #mesondefine PACKAGE /* Define to the address where bug reports for this package should be sent. */ #mesondefine PACKAGE_BUGREPORT /* Define to the full name of this package. */ #mesondefine PACKAGE_NAME /* Define to the full name and version of this package. */ #mesondefine PACKAGE_STRING /* Define to the one symbol short name of this package. */ #mesondefine PACKAGE_TARNAME /* Define to the home page for this package. */ #mesondefine PACKAGE_URL /* Define to the version of this package. */ #mesondefine PACKAGE_VERSION /* Define to 1 if you have the ANSI C header files. */ #mesondefine STDC_HEADERS /* Version number of package */ #mesondefine VERSION corebird-1.7.4/configure.ac000066400000000000000000000112771324604713000156120ustar00rootroot00000000000000AC_INIT([corebird],[1.7.4],[mail@baedert.org]) AC_PREREQ([2.65]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_SRCDIR([Makefile.am]) AC_CONFIG_HEADERS(config.h) AC_CONFIG_SRCDIR([configure.ac]) AM_INIT_AUTOMAKE([1.11 foreign no-dist-gzip dist-xz subdir-objects]) m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) AM_MAINTAINER_MODE([enable]) AC_PROG_CC AM_PROG_CC_C_O AC_PROG_CC_STDC AC_PROG_INSTALL LT_PREREQ([2.2.6]) LT_INIT([disable-static]) VALA_MIN_VERSION=0.28.0 AM_PROG_VALAC([$VALA_MIN_VERSION], [], [AC_MSG_ERROR([valac >= $VALA_MIN_VERSION is required])]) GLIB_GSETTINGS # --enable-debug AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug], [Enable debugging]),, enable_debug=no) AM_CONDITIONAL([ENABLE_DEBUG], [ test "$enable_debug" = "yes"]) # --disable-video AC_ARG_ENABLE(video, AS_HELP_STRING([--disable-video], [Disable video support]),, enable_video=yes) AM_CONDITIONAL([DISABLE_VIDEO], [ test "$enable_video" != "yes"]) # --disable-gst-check AC_ARG_ENABLE(gst_check, AS_HELP_STRING([--disable-gst-check], [Disable check for needed gstreamer elements]),, enable_gst_check=yes) AM_CONDITIONAL([DISABLE_GST_CHECK], [ test "$enable_gst_check" != "yes"]) # --disable-spellcheck AC_ARG_ENABLE(spellcheck, AS_HELP_STRING([--disable-spellcheck], [Disable spellchecking support]),, enable_spellcheck=yes) AM_CONDITIONAL([DISABLE_SPELLCHECK], [ test "$enable_spellcheck" = "yes"]) pkg_modules="gtk+-3.0 >= 3.20 glib-2.0 >= 2.44 libsoup-2.4 json-glib-1.0 sqlite3" if test "$enable_video" != "no"; then pkg_modules="$pkg_modules gstreamer-video-1.0 >= 1.6" fi if test "$enable_spellcheck" != "no"; then pkg_modules="$pkg_modules gspell-1 >= 1.2" fi PKG_CHECK_MODULES(CB, [$pkg_modules]) # We do this again for librest. TODO: Do this only once. PKG_CHECK_MODULES(SOUP, libsoup-2.4 >= 2.42) PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.38) CB_VALA_FLAGS=" \ --pkg gtk+-3.0 \ --pkg json-glib-1.0 \ --pkg sqlite3 \ --pkg libsoup-2.4 \ --pkg glib-2.0 \ --target-glib=2.44" AC_SUBST(CB_CFLAGS) AC_SUBST(CB_LIBS) AC_SUBST(CB_VALA_FLAGS) # Check for valac >= 0.34, which binds gtk_popover_popup # as well as if that function exists in gtk+ AM_PROG_VALAC([0.34.0], [ AC_CHECK_LIB([gtk-3], [gtk_popover_popup], [CB_VALA_FLAGS="$CB_VALA_FLAGS -D GTK322"], ) ]) AC_ARG_VAR([GLIB_COMPILE_RESOURCES],[the glib-compile-resources program]) AC_PATH_PROG([GLIB_COMPILE_RESOURCES],[glib-compile-resources],[]) if test -z "$GLIB_COMPILE_RESOURCES"; then AC_MSG_ERROR([glib-compile-resources not found]) fi AC_ARG_VAR([XMLLINT],[the xmllint program]) AC_PATH_PROG([XMLLINT],[xmllint],[]) if test -z "$XMLLINT"; then AC_MSG_ERROR([xmllint not found]) fi if test "${CC}" = "clang"; then CB_CFLAGS="$CB_CFLAGS -Wno-incompatible-pointer-types -Wno-incompatible-pointer-types-discards-qualifiers" fi if test "$enable_video" != "no"; then CB_VALA_FLAGS="$CB_VALA_FLAGS \ -D VIDEO \ --pkg gstreamer-video-1.0" CB_CFLAGS="$CB_CFLAGS -D VIDEO" fi if test "$enable_debug" = "yes"; then CB_VALA_FLAGS="$CB_VALA_FLAGS -D DEBUG" CB_CFLAGS="$CB_CFLAGS -D DEBUG -D G_DISABLE_CAST_CHECKS" fi if test "$enable_spellcheck" != "no"; then CB_VALA_FLAGS="$CB_VALA_FLAGS -D SPELLCHECK --pkg gspell-1" CB_CFLAGS="$CB_CFLAGS -D SPELLCHECK" fi if test "$enable_gst_check" != "no"; then # Check for gtksink GST_ELEMENT_CHECK([gtksink], [1.0], [], []) fi # Gettext stuff GETTEXT_PACKAGE=AC_PACKAGE_NAME AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE], ["$GETTEXT_PACKAGE"], [Gettext Package]) AM_GNU_GETTEXT([external]) AM_GNU_GETTEXT_VERSION([0.19.7]) AC_SUBST([GETTEXT_PACKAGE], ["$GETTEXT_PACKAGE"]) AC_CONFIG_FILES([ Makefile src/Makefile data/Makefile data/hicolor/Makefile data/hicolor/256x256/Makefile data/hicolor/256x256/apps/Makefile data/hicolor/128x128/Makefile data/hicolor/128x128/apps/Makefile data/hicolor/96x96/Makefile data/hicolor/96x96/apps/Makefile data/hicolor/64x64/Makefile data/hicolor/64x64/apps/Makefile data/hicolor/48x48/Makefile data/hicolor/48x48/apps/Makefile data/hicolor/32x32/Makefile data/hicolor/32x32/apps/Makefile data/hicolor/24x24/Makefile data/hicolor/24x24/apps/Makefile data/hicolor/16x16/Makefile data/hicolor/16x16/apps/Makefile po/Makefile.in tests/Makefile ]) AC_OUTPUT VALAC_VERSION=$($VALAC --version) echo " Corebird $VERSION Prefix: ${prefix} Vala Compiler: ${VALAC} ${CB_VALA_FLAGS} valac version: ${VALAC_VERSION} C Compiler: ${CC} ${CFLAGS} Debugging: $enable_debug Video enabled: $enable_video Gst check enabled: $enable_gst_check Spellchecking enabled: $enable_spellcheck " corebird-1.7.4/corebird.gresource.xml000066400000000000000000000076631324604713000176400ustar00rootroot00000000000000 ui/menus.ui ui/settings-dialog.ui ui/account-create-widget.ui ui/compose-window.ui ui/tweet-list-entry.ui ui/tweet-info-page.ui ui/about-dialog.ui ui/dm-thread-entry.ui ui/profile-page.ui ui/dm-page.ui ui/search-page.ui ui/user-list-entry.ui ui/start-conversation-entry.ui ui/list-statuses-page.ui ui/list-list-entry.ui ui/new-list-entry.ui ui/user-lists-widget.ui ui/filter-page.ui ui/filter-list-entry.ui ui/modify-filter-dialog.ui ui/user-filter-entry.ui ui/account-dialog.ui ui/media-dialog.ui ui/modify-snippet-dialog.ui ui/shortcuts-window.ui ui/cb-emoji-chooser.ui data/16x16/apps/corebird-compose-symbolic.symbolic.png data/16x16/apps/corebird-conversation-symbolic.symbolic.png data/16x16/apps/corebird-dms-symbolic.symbolic.png data/16x16/apps/corebird-filter-symbolic.symbolic.png data/16x16/apps/corebird-mentions-symbolic.symbolic.png data/16x16/apps/corebird-new-window-symbolic.symbolic.png data/16x16/apps/corebird-retweet-symbolic.symbolic.png data/16x16/apps/corebird-profile-symbolic.symbolic.png data/16x16/apps/corebird-favorite-symbolic.symbolic.png data/16x16/apps/corebird-user-home-symbolic.symbolic.png data/16x16/apps/corebird-edit-find-symbolic.symbolic.png data/32x32/apps/corebird-compose-symbolic.symbolic.png data/32x32/apps/corebird-conversation-symbolic.symbolic.png data/32x32/apps/corebird-dms-symbolic.symbolic.png data/32x32/apps/corebird-filter-symbolic.symbolic.png data/32x32/apps/corebird-mentions-symbolic.symbolic.png data/32x32/apps/corebird-new-window-symbolic.symbolic.png data/32x32/apps/corebird-retweet-symbolic.symbolic.png data/32x32/apps/corebird-profile-symbolic.symbolic.png data/32x32/apps/corebird-favorite-symbolic.symbolic.png data/32x32/apps/corebird-user-home-symbolic.symbolic.png data/32x32/apps/corebird-edit-find-symbolic.symbolic.png data/play.png data/play@2.png data/verified-small.png data/verified-small@2.png data/verified-large.png data/verified-large@2.png data/no_avatar.png data/no_banner.png ui/style.css sql/init/Create.1.sql sql/init/Create.2.sql sql/accounts/Create.1.sql sql/accounts/Create.2.sql sql/accounts/Create.3.sql corebird-1.7.4/data/000077500000000000000000000000001324604713000142255ustar00rootroot00000000000000corebird-1.7.4/data/.gitignore000066400000000000000000000002771324604713000162230ustar00rootroot00000000000000org.baedert.corebird.gschema.xml org.baedert.corebird.desktop.in org.baedert.corebird.desktop org.baedert.corebird.gschema.valid org.baedert.corebird.appdata.xml org.baedert.corebird.service corebird-1.7.4/data/16x16/000077500000000000000000000000001324604713000150125ustar00rootroot00000000000000corebird-1.7.4/data/16x16/apps/000077500000000000000000000000001324604713000157555ustar00rootroot00000000000000corebird-1.7.4/data/16x16/apps/corebird-compose-symbolic.symbolic.png000066400000000000000000000003061324604713000253550ustar00rootroot00000000000000PNG  IHDRasBIT|d}IDAT8 P-c4[ҁ-x XE%'xawQ5xCj0ËO4 \!J%n9b0Bx:NRpIENDB`corebird-1.7.4/data/16x16/apps/corebird-edit-find-symbolic.symbolic.png000066400000000000000000000004511324604713000255540ustar00rootroot00000000000000PNG  IHDRasBIT|dIDAT8-Ka,I] 6_E~Ul|A&apI s?wg-g>Q>&T{Xf(4jYɏKv V+B>™[UqWeBn )]`ylp8 $H rv C49p? `aZ/gC IENDB`corebird-1.7.4/data/16x16/apps/corebird-favorite-symbolic.symbolic.png000066400000000000000000000003511324604713000255270ustar00rootroot00000000000000PNG  IHDRasBIT|dIDAT8=AA'`q TH4 Xݥ07Õ[79L=3OA=zh^5Zy3.,JÅCE'wczLJE3enS/)N0KϙR({ߊ1 -#+VCD+IENDB`corebird-1.7.4/data/16x16/apps/corebird-filter-symbolic.symbolic.png000066400000000000000000000004301324604713000251730ustar00rootroot00000000000000PNG  IHDRasBIT|dIDAT81NBQЃ!DXn\+а5PSƎB6?8$7ys羆j;>2^p!Bp0&9:h;mn9`/\4 7]T]ŝs,C+»crrGEG؆=x5\Ě(~9LO>v1^N%{5xX/9JҶ8 KhZ cqx~* -rnTI*IENDB`corebird-1.7.4/data/16x16/apps/corebird-new-window-symbolic.symbolic.png000066400000000000000000000003721324604713000260110ustar00rootroot00000000000000PNG  IHDRasBIT|dIDAT8Œ 0F_]HH E6`X  >`l Y:8PgL- pi%f,5 @'*5!%0HQI[\\s`:uPwPZK` %J{R)e5_9c(|oz^'w'/CIENDB`corebird-1.7.4/data/16x16/apps/corebird-profile-symbolic.symbolic.png000066400000000000000000000003221324604713000253460ustar00rootroot00000000000000PNG  IHDRasBIT|dIDAT81 @OZKADdo`'p!n4I ;Wa % V| JBP'@10Usi'fטm>8{'eEpQno@'4qIϣIENDB`corebird-1.7.4/data/16x16/apps/corebird-retweet-symbolic.symbolic.png000066400000000000000000000003461324604713000253730ustar00rootroot00000000000000PNG  IHDRasBIT|dIDAT8͒1@D6i Xy*Ja, Wh3|vL [B @|VZ%0I'F^GC&DY}íp/fvU\sM s5M?x۶OA\lg vW?@uxWUg9Sؐ_ɐq.` ~nBpOww |Ws˂I\Ę[wAq6 ZW 'ҭaIV4L{ 4xV)"`3 Pa!2IENDB`corebird-1.7.4/data/32x32/apps/corebird-mentions-symbolic.symbolic.png000066400000000000000000000014561324604713000255470ustar00rootroot00000000000000PNG  IHDR szzsBIT|dIDATXKEE"AhF"ڴqCjFTVT "aHAf 7v(z.;g漾3sN+0sЇ!o0v0~lıDOǔ ܃8f|nOނ"x{f'z; ޏÙpOح͜ž`/j{M n H{5!`S#F{ͩk ~fDZwZwK1`|sl nzإj'+aܖoU 6o3bOPx*+aىRv6 L6WʉZq ^'A5ZX0;gy)ĩd @y7nG@ge'B>hO% s}6do:[mIi+Y .GtUUpu]#M@gLZACfkz')WJJЕk=Y`%+tLj R {oqɻ 96P2 D9 >rhU$f:oԁND~uXg(Ze OZ&3rIENDB`corebird-1.7.4/data/32x32/apps/corebird-profile-symbolic.symbolic.png000066400000000000000000000005531324604713000253500ustar00rootroot00000000000000PNG  IHDR szzsBIT|d"IDATXխNAE``m0$PG[  S;ICXv*Ȟ䘙KF//jW,eQDx$7ѭK<"@?EaDaՏjk"MژG[JS d~ ! j/ۇ8+֒Qbl'\[ulaSuÖ? e\D@ P{gTxn 0RBC; Z; |"3 &ᱭPשZGrg)f Ԥy]:ɕRȏ٤}e9D$cƣuC۵ V tS|XZ/j>sQvlcvRg\cQȫ>@Eyr4z6)XnIENDB`corebird-1.7.4/data/32x32/apps/corebird-user-home-symbolic.symbolic.png000066400000000000000000000006301324604713000256100ustar00rootroot00000000000000PNG  IHDR szzsBIT|dOIDATXֱJQ ZXhi6 )4/?J!`bP[41 Iط$"셁{goc| T~x %&B-<fA\1>ȣh?EYwwXmaNwiQCFm5#4hWXN]EvA#c8T!x4 ʫLI܋5&܈KQ̏AaFF;aD1=h%~.|?u$CLh@weY }CS&K{2C.  (i68 RWvhwԨʞ +WIENDB`corebird-1.7.4/data/Makefile.am000066400000000000000000000024611324604713000162640ustar00rootroot00000000000000iconprefix=$(datarootdir)/icons/hicolor/ SUBDIRS = hicolor desktopdir = $(datarootdir)/applications desktop_in_files = org.baedert.corebird.desktop.in desktop_DATA = $(desktop_in_files:.desktop.in=.desktop) appdatadir = $(datarootdir)/metainfo appdata_in_files = org.baedert.corebird.appdata.xml.in appdata_DATA = $(appdata_in_files:.xml.in=.xml) %.desktop: %.desktop.in msgfmt --desktop -d $(top_srcdir)/po \ --template $< -o $@ %.appdata.xml: %.appdata.xml.in msgfmt --xml -d $(top_srcdir)/po \ --template $< -o $@ man_MANS = corebird.1 gsettings_SCHEMAS = org.baedert.corebird.gschema.xml @GSETTINGS_RULES@ gschemas.compiled: Makefile $(gsettings_SCHEMAS:.xml=.valid) $(AM_V_GEN) $(GLIB_COMPILE_SCHEMAS) --targetdir=$(builddir) $(builddir) dbusservicedir = $(datadir)/dbus-1/services dbusservice_DATA = org.baedert.corebird.service org.baedert.corebird.service: Makefile $(AM_V_GEN) (echo '[D-BUS Service]'; \ echo 'Name=org.baedert.corebird'; \ echo 'Exec=${bindir}/corebird --gapplication-service') > $@.tmp && \ mv $@.tmp $@ EXTRA_DIST = \ $(desktop_in_files) \ $(appdata_in_files) \ org.baedert.corebird.gschema.xml \ $(man_MANS) CLEANFILES = \ gschemas.compiled \ org.baedert.corebird.gschema.valid \ $(desktop_DATA) \ $(dbusservice_DATA) \ $(appdata_DATA) all-local: gschemas.compiled corebird-1.7.4/data/corebird.1000066400000000000000000000023011324604713000160740ustar00rootroot00000000000000.TH "corebird" "1" "08. Feb. 2014" "man page by Malcolm J Lewis" "" .SH NAME .B corebird - Native Gtk+ Twitter client for the Linux desktop. .SH SYNOPSIS .B corebird [--tweet=@screen_name] .SH DESCRIPTION - Corebird is a native GTK+ twitter client that provides vital features such as Direct Messages (DMs), tweet notifications, conversation views. - Additional features include the ability to change to the GTK+ dark theme, searching and media uploads. .SH KEYBOARD SHORTCUTS [command] .B [Ctrl + t] - Compose tweet .B [Back] - Go one page back (this can be triggered via the back button on the keyboard, the back thumb button on the mouse or [Alt + Left]) .B [Forward] - Go one page forward(this can be triggered via the forward button on the keyboard, the forward thumb button on the mouse or [Alt + Right]) .B [Alt + num] - Go to page [num] (between 1 and 5 at the moment) .B [Ctrl + Shift + s] - Show/Hide sidebar .B When a tweet is focused (via keynav) .B [r] - Reply .B [tt] - Retweet .B [f] - Favorite .B [dd] - Delete .B [Return] - Show tweet details .SH AUTHOR Written by Timm Bäder. .SH HOMEPAGE https://corebird.baedert.org/ .SH REPORTING BUGS https://github.com/baedert/corebird/issues corebird-1.7.4/data/corebird.svg000066400000000000000000001123151324604713000165420ustar00rootroot00000000000000 image/svg+xml corebird-1.7.4/data/hicolor/000077500000000000000000000000001324604713000156645ustar00rootroot00000000000000corebird-1.7.4/data/hicolor/128x128/000077500000000000000000000000001324604713000166215ustar00rootroot00000000000000corebird-1.7.4/data/hicolor/128x128/Makefile.am000066400000000000000000000000171324604713000206530ustar00rootroot00000000000000SUBDIRS = apps corebird-1.7.4/data/hicolor/128x128/apps/000077500000000000000000000000001324604713000175645ustar00rootroot00000000000000corebird-1.7.4/data/hicolor/128x128/apps/Makefile.am000066400000000000000000000001231324604713000216140ustar00rootroot00000000000000icondir = $(datarootdir)/icons/hicolor/128x128/apps/ dist_icon_DATA = corebird.png corebird-1.7.4/data/hicolor/128x128/apps/corebird.png000066400000000000000000000340721324604713000220710ustar00rootroot00000000000000PNG  IHDR>abKGD IDATxy]E/]UsDHB( (2*g^gA'IDD[Dsp]~Z{shV>'{W֚ x^Kb7Ŧ5'{JuڦSӾ~^31؀ցܣ>rɢ$۬|dh8{_a0Bxc)6ִ|ջ/r'^rx<s`03" _ϱTs{1>?.z/6xOsA73had#+|l}WF-  :0,e{]N<`oNͶcz7K/c,'0`l֒0` M=`/ 7N[:W8<;h_ҫ(|gvjGe.xjuԴWu|2_ )`VK!@|O:=YH1ޱ>H{Ac߅`{/RTZl4Wlchi;Fr 5;Mfo/c?2{ L :g2!33꥿q<7oDw%(gpO( @T0$,Hbaz=|*#x{[\3Y 4#2}G.vxΟ#|!#" ,8o#,; 2-12#7V@Q0jzqU}N@D=\؃]=3u)Ϫb8ՐAfqÂKinR1e;&{ KcTܛ+b<;|~kI`@5% 3'\U4z A pԂ Fl vz$p όgW N6OZFhhũW-?Yk \${0XA|Xku&Sk2cYf7G;  F=؇ب.nC0 g8s= c 2g-7MѤK+ed=L#h+J{ #sD!r2 ax YŹ0{\e8MBgب !#C 'Es!2yU3\2}28g~Y"aXS-u$l9٣:+@Ѡ,K~P98tA繃|pb^(C 4 XY| iAYk`@f+ϼ`gֆ1Ƅ (nY$3pΉ8խ,Lcw>$ZGWg|t\^#F -3p2_{zQQR=ᜃ=@ E}%aɈa$d>q{ DS"#2Ѽ7.b1"Y PNE `M:D!!3~8:" Q#c34 Uk=H#cI0|a$x-prh/x=!3U&:&&\XKȬAf-lfeYf/%y &"39MP1Bt4Pí2@šc4X;||pD. ̀7HEH7J?>\#[cu*(|MDE(Fj  ,7p)eh%4hn> i H2 LQLߠ*4/2bzGGHIPgLk<E"`h^Q{p:Ѵvss\`v70 ]K&8bOTW#n {!jqǬ\>0@aS? .R.cO@ KI@\;IEbIsMŖHҊM_ `(X3(M8 ֍֎BDJ)HqH`cD mLYz-Lf)GfK30]xM@k^f:[=;*{t`y'Ynѱ9T W˻gV"(Q[~kLD~&!Q(Li`3LŸDbTXf}t?8yT=Ȭ~M8h;?w:ނoH-c,l vB0^o:5SKTD-Q;(<$|EdiBV+MxmL(5j#꭪V?5xPʋ xk3qFC9-FJY 7X!|'fW\Hs7|vb46?}e#vJ_g7,ދ3lœjYw5e, _weW= J, PXiK Tˀ`QG?p0kf.<6r^#J{0,~L~@``xt)xlz bj s1[@@fQLPef:g\$A1G2vqv pZYӝxjJ'VvVe ;Ϫm1b0"}"V0Œ 0حnz1PFA^*`u_*XiP,(P&P73<uBg+:lg=%UEAZӑ߽'FБ4!xwnc5;*ֳfHJزb^ӝko ٠#F+ $bo M5ȀN܆盳 wU|g;*x,Uj (mYTΞ_pdq[ftvdԊfPPbٲ"حWcS~vPzVJQJ :*X]Зn.qn% ga4n;Hi^d)bZIsj ̭ױ[5o`F>8IFD\ɰas9æRY ;2l,O  $0냸7xD\)h&I5{_u.zd{+:KIR8]" 0d,`A˅ cmGk;K.hd6r4&:džk5拨 ;$AZ;<|Et}&V#4j0Yl<~W2{) E:yn'ك}漨QO ䷪K, N@"dl?9#\z3,GYMP"$ő(bQ:Wʱ1mEA҅*%@CR0@e`Cb8#﫨fZ@kH"o M-ҧ|p4, n;cˉlQߞDE<4Ɵ>/~Fy vi :J(e=u܋=5( u$3M\wQ].DY |!n;ONS;Ց\^cl^-Ɔ>6!,H!] I Ad#x'Wֳ%FQIR-%\#OW ۫b]kUE}af?=zy~ذ% lHղ7)T{Ţ,mf3VWW+ʙjxuV9H;BYf/k|a P?`骔~gGƘQJw\z{oZºo#jRf..{,;g?o,+VO[$ )QVS  (TZ*Xg0;̛Jy'NIۆp+1A>s14$U/ѐPnػ8ۦV,{xia]iY.8᰽k6!)XTp̾(,uS>PC11Rp]W-@o Ϭ$1f'=llt6x^H6bW:5ƚOTvѠ AA^Em{! w߽XcV*,oWNx51hw𾁃?bcPU튽 ǡa1!WpwIF CO Pџ ~qhh0G =q%Fg4fΚ~z nM$@`>xӑ{G<97{*9h5SG h(=(&/k{p_}kˈ{tEMFKx"70?w9|7< C_=vQא7Xz"oǬ靸=cD0&Q*QS01 D(eNI;F >|?'ǥ\aƳsӋg4eB0w.:Fy?8֎)e\0vkLHT`aV{Cg #9 sbHs [Dnh.?4cBiڔ|/ODG5hwҫv|;*ێEWDuX|D08So,gK?U#y=wBaaNRCϋ_-R`Npѣ(N2s*N\sgM-6H]Ь $jqLS3# #Ѯne0ZMg9iH+Iߍ-jx{?PY Me,Wp7;X>sqa{i4cjz5uaZM Hr C߸=ܽ@Vn.["MֿO}#KԺdXrDF9Bf[C1:j{ "z*OQnE<#]%EY톙Sʢ]101={aTbA ݯ",K%AE5hy}cx-$9`^Z,^C_1sgM/9RyC 145]Š}wx`hϖ| gcwxR9xܨ!Y?D*$1mY狆&_#}[IwQ9j91N$֘jŭ%`ewB@׀`<`&?Kr w Ӻ*nbqQc1wfGdu!%vnJ])/< l1 Uݲæ P)ϝn=[a3qġ`Nuǟ\ǟz}7g6^}51x%35-I40)Ӵ\sR@=;2;b_Ojmpc1.WŲ+Xs+Czp?WO<\J.>}ݘ7gڭ 3qG%@AA{!Miw*I"R_5A/TYF<(wp_~rwǟpO~1`>ju\6 h(11ETPC"\3?n579g }_98W=~o-8/ŠgWQ.3D0 Lk==&VOwWP4 Z 6e 56_:R.x ~ßd1|;Ϛo}b՟Ü]Wan'?qc0vm?pQn``5Z8 @:xx4(ّנݛ_yxo}}CjϿwO?O9|!s'W66$?{̲d})vGcH)>pMX)#Tt1#3[L ugW|j #]J̌;)>K3yHu~aaKg|:ɒ0r X?!wԼJ8_#G=Bq}oz9ѝ?w˶l^_wXn#lfMlc;#z_nT Hg?%n@'h4?·w['U{>[ܒƄ^ꡇC[DLއFԖv+ڑb8 *'@WK6]fn[ Zo6}ƭE{>McȌS1sؼv\ }m^&2&FDzsSZO1 yڍ0Ex-zNipM"IǏ-afܛDºp%R' !vPgIDATK[,>u1R}CIPժh4¾{~Ok@'!?_7j[e)a7)x([u,2bqf!C@| $$QhR1=:7^^#sfϜ/^\ >p1ke.#~JcB?XƎLRTL*9X߷A俁_Gutg֣ZV^Ύu &ΰӌi>ok~oso]vq{)Uwa ]odZ̫M4—٘߶kJ7naz*d6lm}jh4Mn< w,ozws*~q1CS  u͜1 |߹Ƣu{S+RĪ-(6@x(d`Mܾ/Զ,|u܀U +E<:OފE{D@yFE2lڲ |lܼ swC|::~G56tpo?͝C|ŰQd~M졕-nw8n ٹ&H5~ 'mm׎aϜ|@ͱi'#'b,Xzߓx42^tst?pJzMUarJ#@ww!=&D `] |dCR=83ukZPV?#us6J6b)" @KX }k$6qaPp4>9cD{GVlaQ|řY ܅luУ P l(ze3Auy b;ZhG|=3㛷= f kukZ#foZ{FҾ]|Sb=a(^20fߡAQ]sy'o`9s2ZdY1keTbz.GVX֞(Cu)ndY@lZ O*n ߽d3a~)zk@d1Ǣ"1KO7҅&eg^<. h\d^ȃ`jEPHcln`8.Vێ죰Νq4\H46n O#?ZשJSZc]{$͢WݯzML{{ EvW_mƜy9ۯ߄áiPIP&"m+b]ةӻ;>v_d&tpi5-3iڥYlW#' p S0zc1Xo_ذ_|d09MbC}"z>Gּ?`}ύ먻21xG׭}vcQXD CUMs}DW?Xl^cҨc6S) xac?b扮76/L`>A^ #d~z%g8~= | bԦ$ju 5f Aw97^lLRFɠd0׏;^,E,`l8X>~rpLcעHB2} CE *CwX0.[| k4TJB@NnQ a p)"t΁]— &ZU=> {d%dJ,gs9IGD/$̗1JB1 .]cøzCL1`mࡺ(fb-aDDh,C|am0Y6+fx:_#sN\CƖ`2l&+_] ?Ud+d3NM.I//=tx7n|ٙkqocA@P-(L $] ~ab`0Y8Rֆ6 c2̞oHyso9#L -G CʙϫVO}~E m;=儔7$/x`m8ĩ8ɘ`C$ "JX}(!900g=,g VlAhr#|"ƣQKJPVԁ-#J%lk쌍9<W.Qy=OT-7:ۋ_QWȎHX GkGusyzg;IJI" 9-u;-=7^W|:-1[󃱗x5V- 4.g/J?mǫSPN}NgEbr|OViU  8,;Gb 9|o契~n8Z8GÔ:`drL2Hl3eLO_ x AVTpǣfFcˆ jȴq"8Sћ!9aLlwvXi7~ד?̬ր:) :)|&AIS~._큫d'غ>t q m9/p0Lٳ>I\\'0/e,M*[| lfnti2ቂ51ĨVjIkJJxx!x/i5iBfﹾ}w3Z(L_HNr*&xx>6^vέɟI3x.1蒳4/A!TRP8FlVU05P߼j,e@"K`K`GWV-U檈pwR;:QJYv/}-͛3n736=7A)⨴CA6YJ2ǯW{6m>}MJ25zj {鷦p L'NOindO7z ؉XLjʬ04. /߆kW޹cpGcKD_ۑe`7X3, M=<|1tЧ̳3u7^qv%I;p㲳#x,1IH>c8V > Yu{ "vuT?9&_۸"ਫ਼CbBPV&Xs!ƮW+ѸS/9ɏ3r'g3aȲT^ z(H!,>r%Kw?(%ZFǁ R0P{&J$kUd~ B'n3N|dz"+Ubo 0bsѭWuqҟ|Mo ϗ| 4Vybb7Uy3.?+(JktN&-D=`κ}ǎJg|8k>J@7DžE-hB[qq 3W *R(e [p'to#L4g/iQNyHU:F*I4!"ҡMkw]߈@z g/0/ AW"x}yIy&s߻V$#R6~; f#)ƅ*!x;,?4>}`{k̵zl:׿|^ez^eLG~}IENDB`corebird-1.7.4/data/hicolor/16x16/000077500000000000000000000000001324604713000164515ustar00rootroot00000000000000corebird-1.7.4/data/hicolor/16x16/Makefile.am000066400000000000000000000000171324604713000205030ustar00rootroot00000000000000SUBDIRS = apps corebird-1.7.4/data/hicolor/16x16/apps/000077500000000000000000000000001324604713000174145ustar00rootroot00000000000000corebird-1.7.4/data/hicolor/16x16/apps/Makefile.am000066400000000000000000000001221324604713000214430ustar00rootroot00000000000000icondir = $(datarootdir)/icons/hicolor/16x16/apps/ dist_icon_DATA = corebird.png corebird-1.7.4/data/hicolor/16x16/apps/corebird.png000066400000000000000000000015171324604713000217170ustar00rootroot00000000000000PNG  IHDRabKGDIDAT8u]he~yL7+W Jf`fFQ&QG԰:Zz8SP"Yl'o6}swa>?\K~j=zm|j6gzįG9U^+u Z@YN%?T{ kF"hJi$%X 'u; HJ_."EJDt}Q$KV'&_3:i.Rr9 aY\0ȁ Yr e\&kD#ӳKneiΏMr l)덡D;^۷Uw50YJc}5[ys|!‡n"dýkfbR' b09 CR ݜjⅅ#]"ns889\p~CTAq 80Éޑ+~QߴwELMw )ayDsE40bD1&Rh9Tx.ױNjv:DJerҦ,ZQjQ b8$ ij'kb!u+DW:U@54^X" QU49Nĉ3 6pYfrvHk%#XvoMpllA<{տzx@P_λ[M"’Fn؁13̔;o~/rbð H U%+ܸ3Oszkǟ\ "u5-haѬ}a`gT?NDݝ8<̋/s]1ryCa`y^"fo)cW)؀GG8=]i\Ӫ*l0er^j|yn68ǾY39GAnZT# C#tް<ϩVTKW`+'5!AsbJBZ6eeT&w1:MAnXEeT*<}ǘ,|QZOmX*s`x5˗G^$݄za/qarkZ;WRN:Ob-j?M*?UزbX{X}U3WoXJwl)>ޖOw??_MIIENDB`corebird-1.7.4/data/hicolor/256x256/000077500000000000000000000000001324604713000166255ustar00rootroot00000000000000corebird-1.7.4/data/hicolor/256x256/Makefile.am000066400000000000000000000000171324604713000206570ustar00rootroot00000000000000SUBDIRS = apps corebird-1.7.4/data/hicolor/256x256/apps/000077500000000000000000000000001324604713000175705ustar00rootroot00000000000000corebird-1.7.4/data/hicolor/256x256/apps/Makefile.am000066400000000000000000000001231324604713000216200ustar00rootroot00000000000000icondir = $(datarootdir)/icons/hicolor/256x256/apps/ dist_icon_DATA = corebird.png corebird-1.7.4/data/hicolor/256x256/apps/corebird.png000066400000000000000000001117241324604713000220750ustar00rootroot00000000000000PNG  IHDR\rfbKGD IDATxweUu?Yצ 0CoC&"Ec(FƆ@bƘc!_MbU1*kA$)Cgyݳ.{m[{>g60M4M4M4M4M4M4M4M/n4M>Zu~`yJ^۟xҷn$ڸۗ&Ot;s/rzkgT{V ̻̆9 @D`0@僭u0D`}&R  2+׀!29|`oo5xg0ΫÌOv1`&lS1ٖlޱ|h6"-`ن ['"USaAnuNӸV*c˿u_Y'NS4-&>T7ٜNK٢7FImq w_TEB|^]CmtdLp䙈E?XoD,s@̰_ѪJ˾/w+M@sjve8  & p6΁q Bf1Nd`hl BūfgD|7d@m@˾߳*U&LϜ~-cqa g6'iMoX5l1$gUƁūz60` @N>!0}} S ?`0JTw5?Ivz53Х!(']4^kG 0- # OCY6waܾ/j~ Gr~08qKƝ(hވ9c"aO$\'71pLh'2@L`s&"4ʑ%Lgl_.wM3g}vH><3  QAGW}[ ]nGsk۾ 3>׸7>kXt 4&a\gYuFFi $gXh?`>gap1e-|5o#T?isOvM5 FF͌z߲>z#mZpnN1I 99|hkIA6<ӯ.F`Pf&e؜askعHF V<; Mi( q\K73))V[~ܪ]`4rp b[Gr<4,3{Ko|+WU?9- ,yN ", D(@aR<ȐEd?0wLM[ynI[؂v؛Gat9TZH\dd I%!"` 7sVn묓&?ïB ۃԗb O>&2Ա\TNX͌}':-#$88Nb+8Oa0 ˙eoSB6۪DuG&fZ"g s0d_CZNZߣFO45[/»CIv`!**Ǐ߻<۫U^2\Imxc<Uͯ4g̶5[1>c$n&338+#5 P|4Z9~#A{l!VU&g3|P 1\QZsBv$'1[%E LFx#^Ñac'4yý?k&*b Rdm?|N{֎:q &ynOXЗ׺o;ֈcx2@^qvh.lYg\A{\ sWWFah9k5o%CLr O#*&U S7d`m#P'9 '9`IQE\dAU>p8έ{S^S>7byFnGFlp$& Dg=`y0v, R 6 tf1sۍJ:G5_G,ǢBA.EdZfILgR П9xm2J,fל \`ګלOS^&e\_2"P!3@&N"#6lokM X" 1HLƢɬT) KSL$"G]pՇaקPdMޱ@| [@dzLFz <ea['Q= }}Q8cc` YQ⇰ &v!ӛy-C7{ujє7;V WN~i?  Kb%e #N腊h/z$Z #Fоy 〃qVlsH GWr="s&vD ,3<ܙ,B1y \Te1r\“N yڡ ;<~y<3R':dbnCR2BKXP`h Xq>kb}]/{Nm)^2aj++ri@YW_6ΌRp 8A)SƸ>DFt@ ܪ(V9dE@px‘S f}FS^XûeĦoY/Fjl٥* sVjM@O _8Ӝ1>5~淪إtb}7UE]u 01u?> EK  0)1Lf`\z6ᐈltϓy$[1,DX6`234zәoX/ꬣݛ,6~oVKڲG̝wZbck /.0"5Q&_elp̦iGIpe @܋`k)UN6H2K~\R94,d"\=Unsčͭ}(=hG2jJFM};@$(CHI&$XlFQUJJXjW~6n dȂ5m"/4W-zv;E!,3**abV(WyI&/+P]̡p%*k1r-b <5dlIק$M}< WLJ7Ը7k]/4U'ɠ#k}Iv%Ѫ h"PȁdB2?%ϠUk0:YhWdĮL>R$cA`t+`wQ7-YPkHЅ:x fMiYp̯ZL@F %@p993pn`( (xT{Ymse@ h‘e?},mEfD"Asu%zvΓ\AqtA+,?d)2h:T\>.VB"H[?WL5G[0姹 r@ZB6kK0ȌF6w7su^24S>}(&$-WJ+}"d1Pwx&d%PܻaP7S~NP\p U"7(&o6?(gPiW]꓉ x.C3C2f䥵հ<8t|怀K QˎDBՎUWڅȬeiK< aȄ<eCG~k96Aȫn2aS؞4.(w*FݷLk0f5klH^9.Gr ;):ieLX?v˿wٛ&I0u€#m_h?` ^xF=ހ~ĵA)fGiqJ+QTGڕqYO&C vT?C{z(fɒ5 "3>gCaɕ! y&!=k$Ɖg`wymj{Yfkμ9sys鶕f:4eÞ;B%ष8X.; ^z3Rlf%q$.DqeX"##q&MRoXdbL<-1uYDPyУ`\ @R礫Bfw ;\}A* P!*UدgU| "䄬e砄V͞o^Ǎ Ol3}t՛e$V'j^ A`>? pLl@^wɂYn޷ ׆EB5#-~(n` pC~lC:K . d\8DEU`C4̴QWT)V?#gkPͭ6wpP2惼}_Ϙ4%'),-/P  *jj5;@a2QiY,gn2,ޗr?x5ۭTszN.D)G w"(DY 3K&? VRr0Š*Mw& U v=s4:,Տ]iDiJr?6E&i͹xqL/<8mݐJƈt!ر37p l2QHFl@RB癷L&B@YJ&gW< . (AXW@CO8F1M @9A\}i2QN6bdKA?ظ1O*Q‡QT83x[e]P^&0?.kRPhe1[_ԲV3I"&_Kh\M-1 *ȃ"M;D>S>册1M @$,|O,ás4UE-aI*Vd'X 9 K15D`>/x@:cYϱWBQY,״] |%j>Dz po";rot]Ur!d D iAE>,ayK}Cd(`J f2ƇrB'}M&H" Xd@R6 g/Z1Pwj"˘_r\}4\O_aQ}-FJu4$(گvX?G5|PT k?$$BKkN-t4@p_ZkƖ .} i1\uQ`ףCV|?ﴂ1$e2*i X8v>,CJdv^IC$f^>%;˅`ⅆ0̠2Ncȗif~uLUJkqfvE<>M 58lQ3SO~+.E]D)eǨw3o<f=łD6AƔd.spUd 3 IFWX0 ]9' : Fғp_ZVöeug՛GdT(vSO8Mw~w}LBFd/%ej9)Cd9U8?5bëOCӑV . ?߬âQuZMa&Stk:K $$Ac36Ly۴F@YB_VoJe^W@R x[Z߰_s̮݇7yfˌL(G#b!ry.#xa8Tr,*{cJSB=%T^]Ft1;F\[:V}+'.P& i} lN+%L,EFWs i#Tf-EsNj~@\}ͩp65I *[B'G+ȥQHrW4z5@}0H89|h.hǍW~M&3P IDAT5^Ff@ {h+C0Qy55VlGg J,.fn ZҙUK6NqC -VxlXKPCH!*% ,!C2/@ޣv3_U + `o  Z_﹜ѥn! 88f?VM&T"2vXҔCjoKEDq*n[fdL@5c)q*'Uy6'`*eSsThD"dڵ sZqK r"oOv-0u j48IؕvˈK-I T`j87dt!AVzfB8y& ҆VxiJwOg;pZFl#N4cLD@_)~hL'hqmU%%V y1nP|=G3 8$ C(O.-0]:y&vs #ͪ@Y'/C97I')M:S@wl7puKv\sXFXjαȆkRE,vcV 4 Lɲ\ 5i>%I>-yE4e#1 7o2IYEqg?f,cEt50YMSF|tEna\bn3N{j-zh{r@،g4 (n/쎗 wmƄ{#b?|T`M+փջn\,8#  :Ո[AH@i#gjeZesdP[QZ_c޶ tޤ!{_uNOG"]U۶OŞAvTs-U#B)qVI->Ѝ0DIՔ<6) aY׀K"2F12j7i8Wl֓΢$͌sm Z _K!ѣuh4PWC_<06MtFV< DKI\[3;yfvPS8conp&EwuQukR=D*t4_@'ygTX"_Ak)嶘?N^CItWi/ *k4euYE'&2l/I=s'\t{)&wݗ_g/ҫPӘ,@rZ/EqQAdYPE>ë_=>[WO Dc3-҈B{emM 7 ^UȆ594M? 3TÁ&M˹393p[p3cFlԔCkqZ9 h?WVǢu5X䉆q8&3'&rCb<A'ޤ/k_s4Ф0/}Smxp ?S}=?RKV\p6DI2ҭ cDmy3qƭ8~&,J| o.1CjH Bwuh8Lz9HD }Qc4 ,65+4|zdzs=X5~j,9MKیC3>#}ȵOfwsg>4Q1?~bds"rjk}i<5ĮȆkݼ ={oJo֑? =Z@8dc-5`]܄B曷USmE>ePmdYJgc\P`Ū>W@»Z[LV=uoO843][d2:2òI(սx' ,VQ7[0`C܀1-A2]b4qk@e znHT .ԯsZ?юoUC3@JRuy@дd:yHS]>}`NM:p\]̯2AD[)AsKgiEYfgA{oK7ll. 48f4A^y/] kŏsX;\ƀu~mm2~ٹM0 Տz@/HF.O&Z$X#Mj]%-Tr~חa.mr- K}vv+ `H6maoOPW$_J4#F QUڷuʩ(V$u9uӕL^<>ЋGzd_Űq.z]"2a(&SZZ&.z~f5S߯ JFs= *l 6,%77!'I vdHP\S<{ߴnDvM2sh\^gGY0 1^7$݌ OɁ><9P3}=x!AHd?<"9ozT '_Lw3~suzdhDdrڵ|%veכ+aD@, l) V<ׇ'{ T|%PfS| ( 1@$Uj H $$^C$=J^UgθФ@CGW\Ă bG|{LHA= mG0qWA3GRɔ \ԤTvP=Ɯ X`]o*Ub|Oz2l ,(Xo&67"WdC'Oݺqs#g҄]B1VR(> eD̺8|B357] E\3|"(uAS.{ץq{dR@$7C<?2`X0cG݈ż*Vs1ZEV 2n\l366W*ZɰasFؒeؓaC`Sf|o&Vi.gv{]-" z?Q VPvHqx0;>j< zWm{lhBWz+5$ b[z\9b~HMiŇ5Ҡ3ւc,@ 5b1j1j3s-s692 Z4 `DБ̠jۈ0c0BC0h2 gm!ldl U [{ r#hxx~v˄h~ODa9W$T\6$H$Vp{q?ɺe]v+;u7ܿPfiL>yy a~L9wwB&t,lnu~qIfd. |#@Vfce@vo橣 -aK_5}Bm0zJȠ. $ uURdZ#NYbA,q)0~c4T@D5D}#cs AG8 Cѧ1 g~`$F։S~`c9jQIurL$@] )@vAz^3e&qku5:r~nߵ( Dh8m}Tƕg"!#-\DwDĻ*pܭF]Ӄ2weFWV!1cv[dKHHm4uxǁ截,z¤%fnimeDTm1{$>G m)Sm' %P8ʣ躏=\_eH?~$D݃a>"+a[jb`u!IK[0'}}'ݧqE?, *c"K?1w)F`ߤ_ۤgh&JL2QƐK5q*py=Ah]ɺ玟9K*h~Z;Ƿ/a(k<Ъ,}dszO^^G߃x6|6BD9݁q%oIcD.ODI&/##hWxDd?c涫3s:X'xp1?+ + 0q]mH`LsB3a@'\ KŇ5M$ #𞂷_!)yfe뀠ٽݥ(nM-`mRe$׼fT9?;tƭ*0Ziڛs8bNm:3Lwc܂m9t.X/[BlQ,cmNZO-VRMn'+X0v ٯݟar. /[帼ZZQojM_pZ͓?ܟ}og].= 加Jy!VfyH^>kOx&sjNSA/o#At^m(nGdZˮNZW՘~[>XϗE\{[q9#3IuD}cL[) lݔhfKp䊥e1 Xn3~yϓUXq -ApQ>_Xh ~h FI ^G2(;1dJ -Mo5~Gc*./̛Ӥ@M6(%w6{ҊȂ-/_=h{/賸UBTOƄm2UH耭+4?O=92%̌%;k/ggT[z2?!sZϐh HX8D~oHE#ԔjS@{a8镝P98,0x Q|%H;>zIVc;&NGoN.7ෟ;jl%ͬG=ٽk-Ba(brF9X4F^Czz_Lj"ĵDX AdD05 ?(鶩 7V-*nɿs 4f˃KN&h3^r ǂEAc^m/l}9n?̗b٢0wV?^d;zz6n*82~ǎ;e?3Xf W9̞p /òsypH^ q$0\kxS]JVu1(؁ojWkٴn*/uU]/vœi œȑ3 r!9ƚ??jeZ)@Qe5^&x[?}RՁSo)-s}sjiͿ\ S MV(e !q&(k7oZmN8 ⷻtRؑa[fXn[LĮwFWj㪶=O>}hCe{bsذe[({;)"rs_qNy^32p q#b,[D1  IvazYm[?wuwwrċghn o00Ql=h{/9ﹸ3G ]!@뀇A?b/_4 |ə˱nV<@S)0/E㶖F wmI҅G +W=^KLc% W~U$@]?#0]x4o7&2cp;`lHs/!{-5!#tߝ1ktg!L6*5#`Aގ n-3,s[ѵ];cLfޭڿSn]΍\PaRwPX:i5Gwuxl}Sw}(Çu^ra+c,~6~`hu%) E.vLF'4lO~w_C IDAToVz 0f pᆱοG62[LG=F01b>\۱m$I2v >'6xb~uj?2*<P%#Ӟ=]` '{:u@kjaE-_ܧTUTR:^p̯qGBW2x2"X>m` py/m4hy`m.x%fϘy?zv7#?R"5 !j,l-hR34 dG݈5(iR҆XW}q2͝5K%88CEݤ 9$r$ !hS^g4iL"%7qb#_V=བྷ_uD7eRОK5/SC6S8# (D84C6-d2pt;V.:5PUwJkb _*E2 ^~/Jq))}qzt*9gL]9-?X"[_r,;\rǟS{8g/vbYi|q\>I2рBD o& q-zp+PMW@aho("^}Do^j4cެ~v4u}^N_ 4, D8p`"j?[?:n?L94g^t0#U,'6_0gulcK5sLw'4TÖü8[h4XIsg؃ؿO8CTIϏd{'pu9uw@֞AS{I N<1uƆR>#ݺg'z*T0'x{?P݈@rXYooK2C`>/k!`KھmR9~YN}7M/6U,?k-*f,|dc/lgT:2:n@U2Կ@Ͽ ZtaijH:~MQP3ZC#MDcOr m3}VGx,ۣ'uR`T (~łJ[pXG}4M/"1b gz?Sfen͖kXc CC}嘶BPw斮Q~J OxK[i4M100k/88%ƨڳ: HS5YMh (Ba@gWbijb/leű.E05e~O\ h e&a͍*J a TӮG7gI|̿ijb~˂2ר#n9h8_Z `lT3PК #Ě?vvMSTdxl7YAL(F@zJ"n:J_atZqK\oC`"/5MSQĿ+ד!6X ~e~[>),VqfyNa(F T\HΪ4KR8uRКg?SkMysK#G Əo~*yn6mފٳf`bpqG`w66mL#k-^B ca%\ޏ}ծF3ں(: -Z!=9PpK*O?.b3v|owk>C3{&N=hc[|6|oWx'>r|b\W:\e>s]@ ZŃ}i{J.UhzL*F1FEE8. ;-hw݋νo#坿1mܴ_pī¦[Ǵ̌/]39 ا!O^ro*; (B`9ј gLK0ll`'K`*QQGs}{ {,nuE]Wprڝ z pY<ʤ/p3NruH~;8wUwuGο;\̳ںƚg^?r GE3+5u,I@ /uԕ}ۓC`ۡ1j68()t"F篮Hk=8K$h8++ͷ6nҕuJE_4~}r?ˑrJ ڋgW0D/k'ҵQw&scHp gp}W~&\ɧe>uއ7]Qy'eC\c""a61 9om;R:^Ѽ eR?B=ü6 ߅|cr5ϭDžok t|^E?ޅVҭ߁^1vN0+߿"3rj*Jɷx'k` 3Զ3ލ9B'2/́M8UϠ5Ι?l|}o>MA|ẛ:jz k9]Fp}~7Q$ 93{W(">|Czm[B0 GQ62[=a#wީS?s q__~?ZG[|/kH[m6Ӂ4 Xe),GQG?q3aZx GWP@GY8T͵_/W1a~858߰ u>oD>$) >@(쇷{z\"3.'VvVP| GBc,n;`3S_Qzdulʬw}Ս׃ҝ@俺zNǎ.6c{$5|'cAe>J}Ck!aJ`C=CmF8jivNV⑓~` &ܔѝ׬mz\zlV÷[-'b; gYo0nc39 tA6,h+#`rT3GHuCI/sO}W}|{ 7gmZƯj-mh{FU箫_6oV%"67~ݽ-sͭi㯿\-6AtB*#u.Z^9 UX+J+gR[0mi95 hGМ+I//TjA:QHJq'1otcTFпUHF&F-jsmSC̟[;biҤ͙y&.l]Yy0@ug$!8/ h[k.fyR6{E{Wcfo!ظ);<6)˯z S`WXζK/~ ]7>w'k>vEW,7RX/o@^x +"ITL)׉=lZX_ \dm'H"Pj߰p`pRBZM}2[.>O]'?%]hYޞ ٿtńzW>aB.bxmm~ yD$(Վÿݣ~i |?>*oq@.^I6V0Z[+WAP1U}qkk7aAۋ4$"eo=78`ۺ{J\wNXWd2;V pa\B!fNֺZg]U^3NZQAqB=e23vy>4Ke~ϓ͙3^},N<l܆'^3vr1/;8Lupybe;c-xgkre8ꈃK/߯|vk1-3jU ? c`HƏ1 C7E9.**v?G?0ݩVKBMRWFɛ苉^OPQ^P1,b8HC lsXX)esZXxkEeT*E__zzzۋI+b~qxذi * Ş.+:PZa~zWa6>,qkWame"ŶY<44a '>#Mҏ~C(`tȂ E}-dN}yUv9&4" ح(C*sl_`VԧW ؓnmgBB a2j$a !djkUu{܉sSko f`[z'NI5'Ba")oo|cEO5({4ե'yN>I~Rx G'=їQyޘw;0ﴉZ"?"/|&7$H_ J:53L|\8ucbZ{?s?޵}V`T|Y Rq3K~@]yaH_[7nW6CMgD|_ kB!%zl,>3S߀I9&'Pϣ!K?yRyʣq(t5?C1.p14,^qۢ_MWiϠ,,*R$ϻh %"$;@0+?jXDW 8Uy``Q؄ wږj17]:R N*(o]rq uvd_,v{ dN~{pGh"rK{bBe9, e IDAT#㭿,hϥ}cFcU>< *xr4BxLOq諭dPޞ߬oSc&xv;W7klBڼy|=*+QɮEe,Ŷ,Re(m4!}mk~?q,sa'*w,Ƀ`FP z OZj̐wL0} TZKl?^ RiyO@VY{6.,lĹhYh׹@`2t|ڍͥ9a_M0u$8zMМyYHf+&Z4_;L]z{/nb#g%ǀdX<3(}}}cJL?I,rZ{I'YL 272b+!~~L[;K1R믡1p-?Ce3x.͟*G!:Y_q4ooK N]&?cޝ &8wݿkqt.Ix˴#:.Bcvynh ]PIƢ/>?e࿖zl?ЯB߷ <88 =v{#Os`#>9yT}>/?a5<W~_USJ"NX+P\~.4mL~{T=}dmt{gPT2_:I |P(cbߓ[DnO,rLw"p2]3/F,^38P@oC 캯q ^᳹q[@ WBU/ɲ Ys m>Jimh8="- ]ov+wuz:P @+2&*~pM@1 Zwtm4.~xo٣/} 9j\8z֔^0~'u* <QDm-JHhleV>xϽ<)Hm?qŮUvo`iПӡ.*#oý[~"~4_C龐S|2%)G~ɐzm\4fyϧYpĹg܇Mhh=y+e͜ _82V$C``Qt2}γ(Lر}h k^ {U]u;UA==특IZ7.|n;$ $.7j~?#n?@W"8% Rf ޮ7]۱~R"B9I~PM+at@@[f@#Yr<~Qv6s0 RC8C'`_KO1fA$L6KuX'w -v0Fury az͵nG+*d"e*II;̦Ne_2N١RA67Xy=1#A{k-x+$pW&q K: ya!U[/sfYP@`=ęGjAzJqcLNJSuT=45kaC2~%*xO=@QGɧc")ukpWcp?? !Ɠ|;po2o^k'0S/8=H%`i{wȇ(|kd ɿWB/fGfbl,3&Flo/;Hs~6V Jǒ,ek$ja@YDX1Vc\PG\4[` ѻ*GMt_dz|X6)H#TP~3P7t2n=Ͽ2"rF{9I!EC͟P@P6 L,sV\ўzoWliA=_՟ Ȳ&zkٮ7 agE}e^E/IR\),_}.Y}K}?p)?~w|P^љ:r%@*G`#˸}5յDt0K+ ,|b|tex& 8s;'`ج:^;_O a_>@m;l#Zۣ,v//S~g = [Un~k?唟B_.ͧ8,G+gI)GB::|҅g.IwbCv᷀' YB 6rP@C17v [C{"7`(튀B3_܃ Vȃ2p[ax؜CU9Dj};!W`s vJ9(tQݮ,F y85ŋf$-/;+{ӿco|k?7dCZ'q/r+։Jx̾)?|fWz?XC84}ق^[m8?=`5p;;@xPz ^ (Rѓѯ=/†p%NKBc:o)$+`ۣ,E)v esJ j AzvzP {Q~(H3uCApp =e}ʿ! 1.9CAA@Z9_aB |xA%̞F 1 YOf<g+IJ0u.83aa!DBlf[J2}QpB%6$MAy 2`*̼#jU_O_dyΰ Z&?{ғ8zӹ.8h AF%Dl [&$H MBNQA`pC}܁Kz$9!I\52tT}Lb` v `ތ6on>Eā rvNR㾭 ۿuˏg5y+?k'1P?t>E(O .;)k(=Hlk `@9{@)րKydzA@_c4!.))q+tXh8d/OJ#s amN_U(B_g=WpO_L8%_<;_QfeY^ m.^N  ;X1'$NL U ~3peo5@{W ?al+(Ҁ+UN[ =rS}8g~US"NbIp[KW{3&Pm0Hb쎏Z}"Ѿe?bko|@Z&R`AFxv>0?\EG(,5bq/SY׹:2$dT4f53h37[OP 2}O'eN8 4A__=+XR&o$uVj' WsDcf3;k!#Z]o;O"=(z@_ܸi3$v =} ^\c@ *?^/*?G%?W~o1j32i.ɷ[hpP&cȰ ޚRBJ r qi.<&L Pl CLwō&)uzm"Q(^o 6!$nGnBg@Z=6ʤX䵨9cӆU@cϐN.x:,P̱ kh~]Bq E,Tţ[_?Cyj@aE~=B Gu@: 럍VR<'kC>n'!3*BsD31!K̘ohcKNW\!#z: %‚].,`@b*$&I+0ILhj{2{@dT]OH|$ϴBVk@3j?b购St ?rϑYo'`OЂ~Uk@F@v)1h3{V[78H*Jln/Zd!|7z~l+$U KH@~z.P D^9ٺ02]ku"%t5;O&=~/O2;R' aE sC鼉'&MaҪ{RW 4 nY\ౠ4JD'R6Վ9Gr֞OQ P=0ā@҃k?Cxȶo7~7ΚȆ ōّxRzqK?›&y͐K}˔Zcc&1\謯ع'X}DT?a啕}L @ i0(p qX\ 븀$ITH\9/[7"7CE5¤UDDVS)SL_)}Un6*9A$)?@VIb2y s sl+9(h=ޝ ܃w|CC8@: ;'}0lK䖗!I*dH4m[-v^&3 G@U|BX@CC+?+sV}l'L9Kpj248\8 X+ƈ@z!&}֩)K xU)W}+g/vc؏7Z?|q(IpD9/M1!@ dsם\^fUS{qo$Ӷ$) pIߜ̨IDvhΒ2|HA[A@|?+J/qlnH*4U|s;3e&ڲ?ҦM3 W;HM3HT~mz9P3x2F\-ZqD$rÓ\,sL,ZXX55$`QYf#ӊ ]~6ncǰ80IS*>w(xuzpoF~XPVT!!㭾i^JdC/ BݞSi>?kjH~n?3` g.<2,8}%>F,kmfUD9`^~SJAEq΁g}kz/7}YG%뽕[mm#Vg!':Be1CtWsv_p^I|3ueZ4WYIDAT{O`w!6r B <⽁@AŞ-t>[@o]˧Jw? NoCha*w7ޟ⽅{V̿^f+|pZ.zݺV~Y1;*DW7f=uc;e,;AO57yzHi; jd1 7iHVq5Eff'IL_Xݶ8l)([p+ #E=MVq.s΁;콜nB17=|WkZcH2)S[_}QzɌ}^G[t$?psWjyf4oJ>m)Oi0ИGm}g/,%왭Y4w+ ҃ė"{VZctiWŌp<\o`cU@L&Yx¹ZjUg2o?g0(9'Ǐ/8);jyS# Zg pNx:dC[Q% SA2f/=^LIZu>}N ~%Rރ \qH {٢4™S^uP_>Iba׈#cMW5rg>d[QEX:IXtSmbxơgx`f6)~Ê#so x~N>75DufI #5m0HNlt|D^P}yβJVd_!$5Ya : d{o+?N:B<ù)~W#J_<ף9 -c?[csh{(qn,1wGJy~e8삗E1}$}Eg.i(/8}s4 E!e-fb]DF>t@)K +H) }_  ϠC~}A-Y8{PT^?H{I]$&$6e??_BsHV߲l~Ms%Ƭc^pƣ;𦸼 82ypO_`Pa Kcj %=O H\t#EpDzmaQ /-> 3c?q֩@o<2iL@M4.·;힫X>xj 27s_iOr1Xt_<|ˏ?9 تUvݍ]{[["@"'VvQw(d pΊH>@Bĥظ}/]N @eh[p#j+a>߹L^$Jp9,!s/cX_%|H@klqORj\qF rsTop_ `CQP @V~ǶRxz_L3IzTzO{HQ0jOn1k5=tq˜ ֟WHئmV mYK+|Uʯ\sNm4^`Q>i)]]zkG3orO2 :c|_+ XBVo@~$ o埂-m~w|v 8k}6 VATI?O+&z4Ye`"h[omu%^GUCLÃ^`҄i#* B,Lck3fF Y}g~w[`ˏC;"N7EmK~ekQ폏/toHď;#'ivS[~X[|Ķ) Z&ʅˍ0ͤeϽ>], ?`HQ9X' H<2}TSi-'uV Շ${3#[b;.my\s "IoBv oa-=w='\@Gp|xd魾yyA~Ήف 8BL# "J*o%Յ~4G~`~ N; ΏgDsh3/z9dlYC,n I ?H^77I f 4qw{dv3I9k &^qO=꧃ex!K:ȪU.ሷͿ,t,pO"y @(֚Bɣ@yF /1݅m0᭨>m4(s'om kw`f4aV}x+sNFkB%lRw_JSZ3.o,]g>{&9bT FR[:@48Z3.b#4=~=i__S-*{bL <\rͳz $υk7 }uA!oW6ѾX}`A{χ/ޱ. rg]AʑWHJ5.䡬N ,*XNybHy3=dM4AV>3x#FmS6YHU$=2kHW.psȓ1.hPgcz?tќ+"]`'eM?iU¡*b6#' ʬABM gᬯ'n}0B.JhnFe㺂2 <{#[>w_{jJ-mk?F mKL=wInjhz!-yDl-}tȑa1q#DǾ(D:rApef00N~ͭQCqt#Ň!uzpEkIiH@N_[̞yaKC }re I02G·y0F+w/\A|H`K2,/CfBhkC.w}MknŐ'_Xq$H+= ?՗,9\cEi "Zgm+MtC1oWo%` 5D-Psb :׽jB8)A^fs9t o`wA2 7, /[`3Oc5'%GA@3g+݇-o|dZL=QXN[2 ჈߫,Dn{tVJL gI2E5:}aǿ*zs'~*⏸!gJ/6 J(¤n##Av7-~Ȁ(V"ؙw.!9ӗ`hex  ѠT6sml{? j=|P 9NÒ_ i4`d=e>!DcpuO_py],]2aJn!+@` |Z4ڗr5 0Fa}lG(|Tn>IQ~ajҽ&Qe|sWW|W]Ir{X\qa~*q:qGcn7"Zr6τ<}66G^X`V,X&l^LRETa¥D `)GKRt+>"_ݼ>ٕ]0gy2|WĪ> |:0:tL<>35h6xwo16^#2 a|~'0lg >>O~XsG#E+#PI}uWT0gn|YD/},zD^f26'5x!CGd-_\ 5g}ɤ~a1(}M4D|WFɅW.⏟t`"|f0p-/x ufd4)A:2ج+~i|› -+q5ưjIxOUŋhMyFJ-/wۭ+c&]$ry^!0zKOR1 Fkff,#Ex58EO㮧ʵ IgdAoIME(?@s ^mxH&KL3SeW##\~Hs eY6ݪ3ݢN mH_owjh L)ܕ.LVa9[Nnf2"ȡN-fչ~R+`^;!}`"U26",IR|kg߹Kmѕ=&]2gъ}^f{63j7Z<'AOyW'Gm?=0aaW|kt])&'-{iZ>,b5xsC>Dy_5=ǙfЎ%m~.bWQ0OM L| TXZhLcJhMjhԪhW*hj'+"y0{^ΝG+9'k_'0zd4zq/L|U׆|GkowhiK6 bO}s0 "\zsg/oO+INpwg"sD@,b/#J?uqu8?ў(r8iC`8 .x0P3L1 NEa~`oW I"8AD0_.&^/8sﮙw c89809y|b8 :qc}&hv"8)9퀙B0Li=N@ Em7$m|msj@%OTAs<s"9gms#TŬzS'/{OS{ڔޞtdĘ,,@D?Ҍ9: 1qĖb" tawp~,4Po{-cj.2@U1 j.0q\vw=JIR;䚥ҽw}UeꤱhP BA Őz0T#y']E.tǃ_w'7mڥr fFs)BC@UQ31UPih쏪ff "sWUkxiJ]oݰs3#f.iRd訊WÎ>*Yas.84T&M%J"]tn,غ3mCLw:%ϔȷ-JqY_e68&$O3$ GykɲZFZg7F$.V=2#LIp.mjnV)Z L&z 7R}2L,k[xvzy CM1S,8GސGe}~90B@#H#u2Y_GW ۹㥅d!#yUhWh)/ kXnAi@xflm* joȇ}j,CM ߪQtcؓOk6 -~Lvf&=q MȥOQQ&0_{w=_߿3o7:iҘd1k:'rH B>웄 "L=v7\OYhc?)*N [-2ĭ'w^Wk~}IENDB`corebird-1.7.4/data/hicolor/48x48/000077500000000000000000000000001324604713000164635ustar00rootroot00000000000000corebird-1.7.4/data/hicolor/48x48/Makefile.am000066400000000000000000000000171324604713000205150ustar00rootroot00000000000000SUBDIRS = apps corebird-1.7.4/data/hicolor/48x48/apps/000077500000000000000000000000001324604713000174265ustar00rootroot00000000000000corebird-1.7.4/data/hicolor/48x48/apps/Makefile.am000066400000000000000000000001211324604713000214540ustar00rootroot00000000000000icondir = $(datarootdir)/icons/hicolor/48x48/apps/ dist_icon_DATA = corebird.png corebird-1.7.4/data/hicolor/48x48/apps/corebird.png000066400000000000000000000070551324604713000217340ustar00rootroot00000000000000PNG  IHDR00WbKGD IDAThyU}?ϹM<DTR:\J"(bLLbiS5]jmc$>5IZSVjMF +1Q#$3<޻9 λ{op1/r^m'~ʾ/`c---4\h.x w؍s>vfW_ǨL}}ē7.0L޿) RA"ţ L74h/Wˣ}΀uuo v [4\>bdq!"0*f,7 ϋjhEMϪrnXXt=rL"(3w(U  3T ظy+6' 8ds ΁8Df` 8qKx@0^;K b'ӧEd#a(`|G&f{8ApH5BE9@c Y~q]j AnrPF 5T#x<-qcqٸ߼o,f*'Q{fhs$}__qcG&jh^4`x$ݷ}&D!y#y1l@ LLѐ!꼣)N9=HpչHQ +1Ľo8E #sԨa ެCѐGk$>iذ;5;c?y( I5jo@Tk"U i'G˃:8傯07ҫs~M\w8qwJG6t"Ј-RLs nce`g.o) /:K_jΖ O=K&M[zzޒAQHಎѩ7GT g6d3AG{dV3/z~[??-X/[WfݎF 4|:mifzZ^kfw%la~(mWvfϬ>̴=;jUUQU* YšF-ȥGo:m#Mǚdn,CUٺoԈ  r͗?q=SycPUm썵=u0Q+٢ƾj!S֊ ]; is9xP`\1Bw| }8 /+k1#_B$i±bH ^1j0=VE`V}R—ZI[:)vrk_!۰[w.}JؼߝD_jçeħSԹ",3ggt|Wwk/t'DQ )$e޺#0-߶ÕWzTȬE"ghl3&=jpyQ{w3\`fb#FCħ툤@ TVzF|( E."|:~,l{ M$U#c|(/EWҳr 6pm\Ȋ(f-<1>c+քVu,]rܴ3ͺhC({;FVa?d ;ʺT6-$>_e"Ϧg?,[~Z@of'',PkU,=NY{1ϒC_M]p ]S{#Ԡ}ٔs^[7˗I H9U۟u}[ S?obkYbKc.wO~l?8or,+6yg^~ }:.IENDB`corebird-1.7.4/data/hicolor/64x64/000077500000000000000000000000001324604713000164575ustar00rootroot00000000000000corebird-1.7.4/data/hicolor/64x64/Makefile.am000066400000000000000000000000171324604713000205110ustar00rootroot00000000000000SUBDIRS = apps corebird-1.7.4/data/hicolor/64x64/apps/000077500000000000000000000000001324604713000174225ustar00rootroot00000000000000corebird-1.7.4/data/hicolor/64x64/apps/Makefile.am000066400000000000000000000001211324604713000214500ustar00rootroot00000000000000icondir = $(datarootdir)/icons/hicolor/64x64/apps/ dist_icon_DATA = corebird.png corebird-1.7.4/data/hicolor/64x64/apps/corebird.png000066400000000000000000000124741324604713000217310ustar00rootroot00000000000000PNG  IHDR@@iqbKGDIDATx{]Uuǿks!@J,KQFM b_h~mQ÷bCbGTT oC9!׽VLȄݟιzo%O㖛 8NQn/uek~gx`ff8afm$<><0IU@isox'^v_ێ㝴7{K'}ꐺG[&fSM|!4dAD@fyw w̜cjcK>?}bW8[,Z<ּ麣p|- W]+C pŇ90w $afavY(y4EGsVs=: 1qv!3 r$N> ǰ$9iQ~** j8+ ok<~A_tqY =)N?19+A1(X'I9"xp!N/Рӥ*p -/, 1LQ:ʼ9e`&PB%Z3*8qx18ޑ9O{ cCi֦tYRNLq EKs)u!_qY/L#V*..tkEH hP( t҅j jqe1d{cilAD$k8{o0Q8!'AzW^C$`n@`)( 5҆1 7d}5,pշ 1ql0o1C14F&#EzpR L2Yrg+Z"ů&nnkLGCHV)vg9PR RB*u*bb$n=b<ņSgCW 1+)_u./wHythLɋQ 3ZMf4sawڋO\%T^W؞eloU'$WisAW~^ ?ci!jB2xm&L _3Eh;O^p`B;K@.=K~/Ԯg\ 5d]d_F֨)*-7!9tfN#%+1f!eE+X(d "Nj 51_7Kνkw>" ۭ+A̐l5E-K!X ,*,pX܋b0 XhQ1BiO X&%BXgfr5ws\@fX(P̱Oc 4j-޴@HL7 J))ѳb̌am6#GW-1H̴yRqn3y Gxndfag !0ƑMҚ7.#q;_ibL0PO07D3}r͍Pc5ugASv˟Z^ʗ̸Wrѥpeﺂ;9y߸/ _ݩid%ic,3Z"Ȉ|;28QTDy {c~̜1o~J˫ϸ竪?੧7ח\^V;}cWLOer[ %qd;P*6&Kf+ 2dz\k?p.g־{z3`>koˮk7>3ʳE#bS5R*@vt~wk_ᨊˮVdyUGX9F pԼG΍`hph޷5!0*R] { ɓuDhV\amՠBg5ʪ[wn)bJK.Wx|=w̼Q55.1(q@ym9Lb+Ln 0ǥc@ }WJ(}po^WpXx8|/%跮pdlnkTL'?9(8g2(R}F݇VqIp-)1G0u[9n!isKt:6lfڔIL<C_a˶4[-5cS*\U)"*<&p8GY3J[uk||e.ÑqzEA"Q29r,NUm6,Ә3>@ݦjQ\9cw%G(w(Wg<DRH$d$Vc]y^)!Pž.6f!\1͗·m"| jI(t"W\}*/WLmbb#|goyV;2\:oЭi?rm9xF;@jltOW@xH-fi^ѻ=e4_ pde s1 56lr]p뎯"qV#˲ԲJU{EHxTO Ѝ0ڼ?!AIT@ݮɍ;'׻q.j vXV3~aEMԪJƐa ޴? 懞.F }[DjRUB<~'gZ̥ԗ&O=xʕ+G^a9;Q =ɬ%&RKŊ "GKNI@Fc8{nD7B%&ތI,J7hʭ?zq7{MKLP'}CKn9ωcww.>j%JXT IҼ͂cgheȄdifMw?0[R3B'YjJY _kk]d.Z?s"ES\/XͪM'zWx_Gzjfq8RǹO%_ۗ|̇/4̐R)Y- F 600HcܗS: We "- khcg.lCKnPӋTc!Ɗ:h Yz0>/(vu'Sw}-|qYlEB@c@=k-}ȵ_ѕ_opfdMR'"s8!3wsȤ>"th\~;k \} zdWZ!ju֫9^{X7]k*dE(1A9@KRBy|V/)#k*y`[q0fMWqh׽a4B*-0HGűC|{Cع 8j#H<,A#4Yݟ~|-mӓUskjFK)ZZ{ 1Q07AܵL &|-吥jGq2OT{ͷ.X|ޛZ}xPNIrj^=@g+2&>#bO`{aj]Y=FzJJ@` yەo΁{=__qoEuf CSypujCB(W3x;yeOe{vֹWrywΧD+h$op噟fz1aP/n _˺(]ugIom EQ@-grl9X&!/O&wq |Xo¢}Wc|EU@rB)RF(А9?|]ӉLӝkE򽎹עumN'2Sl3I㡝W좁__]Ai4szIENDB`corebird-1.7.4/data/hicolor/96x96/000077500000000000000000000000001324604713000164715ustar00rootroot00000000000000corebird-1.7.4/data/hicolor/96x96/Makefile.am000066400000000000000000000000171324604713000205230ustar00rootroot00000000000000SUBDIRS = apps corebird-1.7.4/data/hicolor/96x96/apps/000077500000000000000000000000001324604713000174345ustar00rootroot00000000000000corebird-1.7.4/data/hicolor/96x96/apps/Makefile.am000066400000000000000000000001211324604713000214620ustar00rootroot00000000000000icondir = $(datarootdir)/icons/hicolor/96x96/apps/ dist_icon_DATA = corebird.png corebird-1.7.4/data/hicolor/96x96/apps/corebird.png000066400000000000000000000227351324604713000217440ustar00rootroot00000000000000PNG  IHDR``w8bKGD IDATxy]U?{߫@DDQAP!̠`ڶ*.V~Yۖ-vӍKm)("s2Uzssν*UI%[^Ws|x΃? JBO`" j@T0FjHE4 gg/ԽF[UNb]iμ;0mGuD HbcѰ*8W]9wX~܅xM~ɞxP.;m=mh_y)r;Ơ^c0"Khb#ݺ4;e E0Qg f>^|V*=N}0pwu1~[ 027-I)x ! z{ 'yzVιrRb"3܈W4c#ޣ`2l1Zm&c ևk:TTқK[lБ) G#Hd~0` ZfL ;؏h Il$;je:@ ,e"<9y@S࣡v{35b TeG읏FR4^#F9PNGϧZ ̒<ϰ ,Yn2mf#x Ǹ 9À1Ы\c)koV.NW? Zc1^K$GP 8 `,YB5é1ڳ*CNqƃzxPՠ1 dR-PHܹu_$d-eQDAzCZFYY,2y)!BEl.ֺ@/ 3,SnC!O6G'pnQ#8 ^hgrūbE-5`9AlLd6@J̒gk &HpAKMNDԏ5,;=>T Bq,]N8&mIt6[ySD)KI$m#^ ; 0>e )S.B|E;zL-}JQ¼BԬL  ܴ<"dq31 N+pqMl;*) 9^ ^kc/H#$ IXId[EP^PM-ssaf-UCַRZP12Q\4-rbSc0o<1џɸ&EAyJ=Z[rLQT/pbsxEXw7)O=˔bѬK@ĪpElA%DAFPqm(-ZbeY̤tʷыЅk,( Zy=CPN%zXѓ|wsظ+_&3.LS9lj^r 'iOR-`5%*C%i`f\#2D@"4(K=~O9DyB M,kRkqqϽpoS.^4U{n5/oQfgyt5KkxŘQg%Ӵ(٩NkV4rO6yϫq\_:M& ԾK'5&LG0{@ ԥ7A:Ztm`jQT$rvõ\!JSC_XlŽ ilO-[Lt)&)T<ꢍϮvfw|oc'*])ܧI !L2}mwl3cˎU6vf>>2`-5-͌yΚ5XlptrNP+1>J`m_9h0u(XR++]l"*޳]`wL,< DT8x^E4BN&t ، ,keK8Rb㓘j7 b9Ocn*@z? Ϳ1Vyyx 9C ( &edXB? bɲ! nr5d ד7cl>zo715U.Bj NLVPW>(7jORkI 1?>2&h|(z ډ6vBo3pkx|rxYj,j#ǭ ,i)xjwy{cmnJ/6|.O/[X.ѥk̙ur@!뫘*WF &^95n:bC(N?;\,DDrT j>)fpa{3k6NǹGYbL ɦ} 92Mnf_<zdY5QXѠp|ꨃ64FpƗn4f"&'PS5.j|_ݤ&?~swaL N].=|S(VWT l~il^Gr;X=P blW +À&I&Jzm ; "Zb*,xusxe]>]O,e^;&w?LYl nFg?M6Ffl7^1?.zH˽La+1N)"e6gi< QP]+bi|ꝯcboc3ر>|W}nEѷʫ9.cL:nQvE2?Pdmh RG~zL?D櫯=fL#o;t4r$:`]9sn^=feK!.4b|_W'EM{~']7{1F犪Ƒ?aC3J &>zkhc 76%yО{4wV_Co.-MeΡVJ bHOG9EGn6L )|(xȎsvsvUC{LmpqLhʜT@C۩_/HuAÞCa\>\pEY8[G+gvm8*{l2Wep\mSF<ڵGQX5fTh|}w[~/޸O":Z>'{x!ak&t>Ī~S {V?%*EQ0kyjߤ&2*^uk?Oȳ=Gdt1L-գ.7&⋛TnˑfƼ;r\jM>%#]{C[PV=ΦZʇ&Y'h%q#j8k7m/)\gSfr닖a]h5ZyHl6{덜,=RRJ75CZ9nE7[\z[Xl%ӧm=w|4==o_t9?lq7s){B#?},]6Se5V:y7mk( Ħ {*Dt[IK9恇 y~7/\Ǩl 38._c_J CPYQ`2OHZ]aIc)Lj9 zLa8V3ƥU_Ğ-K}WȢc..znM[ZVO)xvfzTeq-e$~RYeQUmLb^f8-lFW/{=Ï=.;8d##W]G5&vV Ym@VMa:a*CS_@NwYĿUkw~/_uLX)O--ǯ|ko 1ϸJ9_i}TհC4AHQ*5R#5,!AG #jZ3\{B&??<22kIweB&Q=˛~ĦcϺ;4`ʚ?|8CVY|uוڒn`S5-ts5:{lzTV%׶([۵ތC.y }z(K>y( өdد" %Wtv%;7o[I'ggwq{NFߗr\ 쯊OP+Y4'Þk$HY@'WniEQ a/e߷A:oQՈ̇!&N>Co)_E8UEYVYM]";"j?+J<\ۑ؀~&̯IBF]Axds^_4MYo/`9>{૟c3g{z\yW`*&1F8uq4t9 -Y;5 AZȓyTݿ;FgT5{o8Q+`5ߋu;nz {f^M6V0mms%sbEC>I+Ӷ:qSjuw]dY70XH1&nRC +=qU$u=0K %;MZKZ;$)4kڵ*q ~}<6ҊM)nKQ*0F *{|!r2XB%ܨ+`{VV!lSAU ^n'f1bK92`bpTI;UΣ6v%}%{YarWWr.~~{]%."rϳ1 pQA&EPv >Jc9;hw+mZFm *A#\u)n+_Fs|/(d~tW~vkqEk}pw&] ]wthZusxюUD sLu:CIN%+=:< ӻ.?X!Q`:{+B z-VUB"ۏeALcMy~XJ[1_wܿmUsx,DGUoMNl,yn-k3sʆΐN{>AX7vKu8ZTc}4FQe888HӉ/,ǚ񀿌Dr6ȊԿV/UQ)KlIF=X=Ck;U 9f0ݟpnt|s3\s-xkX#6q6,+cč)W^wFϏc~~vt@tbP#>ޞ xŹ.Onr2uBV2#$wm߼JZ.|p5>ec@k96uᲵagƷȍy 9^1 }NX{b !LM.Dʾ& ްL|Ë_0mT*_CI"`ާ16+oMVƞPQ79U0kW/8cc&,XQwz&8 WNв6}fMf1s)6,FAخ{ C.ǁ/]:ZϮᦻ'WbL@ٰ3Ƙ,b~b>!:,lF.ǚ^ϧ2M 0b .Z$/L2oo6J)xݜx!iwHV>D+ԜY|،~gEE{ ,MmjaU7g嘬ɛؼAf{A"=N&Yփ͛\m73kJcQ#1_;;ܬ~9i镆WLhTW-1Qg\x6|@ 9x %[r!Y d0'ǃmmEw3l? QVNg7̹q}oWpSϙU@:ar!7yåwBmZ8ipW ,msK0*c3ъRG&︦o-:oN߳|;'8D8RJHGۈ i Q[bqk;˓ Se{\u yK &˃7LyJx =';-1Y${O2'ǭ]5} 8R3_*ѥBPdg:-~-\ӯ_i Sn)[2|ydX7HQH"#6Asyr`?83؏v;;dM;d{2V '6D_8[ʿqvU߉\'?{w8@'rdج<7K17Xp!kb&wlIDATdcV—7$f5W}C֖o. m{/l1P&Mk^*68Whb+[3k!]t]Of%:e[HUwx gW!(wBNgUňwN8ND rzupw.A0b!d&߼f ݵ֘tׄY#hŔu7r$fe DS co^G^œVnMlz\sxVhp.Ӟ( >}Ŀה 䙯E˦?0QTᗋO"oN$˛'Q-f1ryt/oSd:]`%kzWz wo< 兰%d9nx'd eh;X0a[ߊBVS 0K<7Wb>W@uG{&0<8*ĞӴ- Kbz)/"UiL _&=|5Fl$@UFkkW/8a|7m@G;wPаs-)F9" emɒr{q.3X>t"w.ޗ +HB扩 їO胿OzV@)yg=G)1cr.Q]Ps8צٷqƾZp׊[O"{1y3~ =SA?|uyoeE!DG]CRѲ'=%1uu r;9e?;>W&֚ػJ "܉X; v5zQ Ѽ3/w~Sw^816MUV>/,2hLF䚼ߺkBH@%;ɪrp0zb=IE*"p#O/Ο|c_O!U"3wj"¨Y=Եr [9x3LOM3epiIENDB`corebird-1.7.4/data/hicolor/Makefile.am000066400000000000000000000000761324604713000177230ustar00rootroot00000000000000SUBDIRS = 256x256 128x128 96x96 64x64 48x48 32x32 24x24 16x16 corebird-1.7.4/data/meson.build000066400000000000000000000023131324604713000163660ustar00rootroot00000000000000i18n = import('i18n') install_data( 'org.baedert.corebird.gschema.xml', install_dir: join_paths(get_option('datadir'), 'glib-2.0', 'schemas') ) meson.add_install_script('meson_post_install.py') i18n.merge_file( input: 'org.baedert.corebird.desktop.in', output: 'org.baedert.corebird.desktop', po_dir: '../po/', type: 'desktop', install: true, install_dir: join_paths(get_option('datadir'), 'applications') ) i18n.merge_file( input: 'org.baedert.corebird.appdata.xml.in', output: 'org.baedert.corebird.appdata.xml', po_dir: '../po/', type: 'xml', install: true, install_dir: join_paths(get_option('datadir'), 'metainfo') ) install_man('corebird.1') conf = configuration_data() conf.set('bindir', join_paths(get_option('prefix'), 'bin')) configure_file( input: 'org.baedert.corebird.service.in', output: 'org.baedert.corebird.service', configuration: conf, install_dir: join_paths(get_option('datadir'), 'dbus-1/services') ) gnome.compile_schemas() # Install all application icons in one go # TODO: This will unfortunately also install the Makefile{.am} files which are still in hicolor/... install_subdir( 'hicolor', install_dir: join_paths(get_option('datadir'), 'icons') ) corebird-1.7.4/data/meson_post_install.py000066400000000000000000000004321324604713000205120ustar00rootroot00000000000000#!/usr/bin/env python3 import os import subprocess schemadir = os.path.join(os.environ['MESON_INSTALL_PREFIX'], 'share', 'glib-2.0', 'schemas') if not os.environ.get('DESTDIR'): print('Compiling gsettings schemas...') subprocess.call(['glib-compile-schemas', schemadir]) corebird-1.7.4/data/no_avatar.png000066400000000000000000000044371324604713000167150ustar00rootroot00000000000000PNG  IHDR00WsBIT|d pHYs@tEXtSoftwarewww.inkscape.org<IDATh՚]UWܙ'BR'Ҧ5%hh1WM4>߬5VbLD0 -tܽ|_ks3&g{n>) `?&c0ڝ6`<`w6ꑩ Lj F@(񨓔f EwP*Xp ג*_qb)rzDLFAB 9muqXaʖN7D$x ŀ~Ί\:_k aEMP+]I\'Љ\y˽NPwrq Jr `p)V2" EHq\IaœM]mBATQJTt.AWhd-<Qgw9)E~HJA#> :bhA"TT/-<{ӘRW?#EP [{ygϾvնųL9鿿T}J# Rߺp_a]yfTz}z~v*S|Q-!@>WK/UB.*R07bSo7({;bARemQ K߻t~zzl5)]]MFn#)u5r Vb(|VQj/0q#w7M^tZbc_;܌$Z_5]ٲ3}-B\;F l{?Y?n} @6VIB(ϰZÎ+YKwXIbw`=;w? ~ k.-F~f&8vWlC o̤Y+'d!I"ž}ށ6w6})|v3w,/rkfd1e0 C2h0;53?&ӓ۸kL+/-Oͤ=,VZ㾇P]֙L2e"f2i[V׿9&g?|)ZWit7YFnv7Æ-i7. ܯ[ek^l"ʯaZ6nc۽ԲԿ/mu|- NXh1]F/'7"vT:nWH DJHu%/@&"` #wֳa(dGt%$|ձY67L|L`v#?/o]#%$PFFrM +X_$X.T51gIfU֏O܌қ Ϋ( Yz$V˂մ5^~#ʵr&ҞX;h8ՊQIENDB`corebird-1.7.4/data/no_avatar.svg000066400000000000000000000120601324604713000167170ustar00rootroot00000000000000 image/svg+xml corebird-1.7.4/data/no_banner.png000066400000000000000000001361751324604713000167110ustar00rootroot00000000000000PNG  IHDR@Z]bKGD pHYs  tIME 51־iTXtCommentCreated with GIMPd.e IDATx}]z:pfTL,ׯ+: 5(l0f{5wM([ _^Ϣ${䔓p=5ͳ"i|d;I?91iǟ6cG`*0̀'u^~CM^ OWNc1}vK?훫˵43G5s>_LKyv~pm">b::!2A \ɨ)GZ%b'Y̠Q#V9Rʱ[@TۑʹOg7G2=)SyѠyTll`ٙΪǯԅ n)Ҥz[ e/! Vú LKp^0.AT(fL>Nfv'M6: CHf 굡cߘJӞ.#]0s~K:g / w%t 3~vw:˃<L^pq x䊧$҈f 07N0#W..aчBNR%jw41 pabc׋_'gdVcF'G 6R7 ;J64ns\߀#5,0(< CIi.Fގ1G'rdFLg4WQ8OY f/+9ǚ`>chuOs~d 4xV]dn|y{0΋q⃃A9yr_׬nq |K8eazrDcWF<҉5p295~e)ZGՕ:cQ b3{ɚhtꓰS$!6AheUlQgSݜX'|%ҙzZ2ۋ)jVb(%MeBĀ :cWE52w Ug9bT zt}Yx%&U - nk7pMҬj[49Պ/rZrJ*AX&쿏RXϕ5Θ9W^k@v|{0[%Ta 8ʳZ5ףrY_\[nqIҞ L,~*u_Gο@xɀ:x1^W H8/niKC{]HRMZM%8]zj5~xU*2Qsm7x.[ y_NS&sQ6ֺK2tmb?m6m3)t*\cmUK5ҹ8sjR3IZN&d܃20 @+10 ]z9^ uTm 56ֳ_Zg I /`Lkz&pHgr%'8NGvrM:1U\~]̊[l\:>s8?Z$\Ift`T(\}"k{t^Voҝj6f'b8RgW#leHء}fU;ߊ1}\ՠN2ggAy쐢;"VSjBu{dq {$=dL\W̝/X̀X2l&;D8-Cc~ii8, ȈV/}$eT;Nwb} Og2bܡգו2/LgF_NsH8j>? W-j\93^ǾG= .I<5b.O:)^F;_XΗIa\ e]S@(R,tW x@g4|ԀuS"ZdQWAĨxKu{Ew7LTonڟ_0u3f~J(|SVfuqF,irP[:`pm@Y4@ՠD ˃y?U4sz}s̙<ɱ~cܧ37$!= ;[;8j8-ک*o:t>σ طֽ!]5%FK)5Hwv ]zIt&i0f^RS1um݊iS0 w1hu!-G˲`7ߟ0{Q^ Qxn7U:6\[ U ,eEl3t525h#lf}HXzImߨj5u'=+37]<9ヱ|E]Tk$&'{k^#q46w0yԘ0; b]\+XY\s%GVrVn2Qf?(GJ(xK5#2_Lh+FZ65NT;>EjMq؞fL[PMz-?7/NIsŅH3ltxa8+ۑ YOLwQ࣡vOzUQn)0)*)6Rlw>Ǝ%4ft4=h {3ɥGlVҌ5B{tu>wrV18ŭp,*>ی|>#0V~kw}`2]Y%ԌMb}ǩT16{p}AE_Sl=3+k4-`*`(MF$&19[zB&#ZtU=ix"M`m9!{FuqU1Rmv.RslzfE-y,@4r:w?h3*!޵`ö6W$ <8]6y{/]r[ zO緌?unN8 Lk6^OJpқo/LUw&D5ILn4U!ؙ8 fz]WAp-ֺ:)iȌ``!ZȝDCmUAtXQ-w  WJt1:]yWܩuIuJq}B0bg9~m~L6O'PrBM ԃ>>%]K)(mk|5j4= AWuxp]6nУoۢ ]ԡG͍3|< xLO?>[FM,n_)TS!dbdբ}=%(88 ߸ Q HSUs?" 9jbv*a8{U*сJ#oS+ eAA C;8UL8(| :{ݮ):`3ѱGm8IS`$YoTΜաk3E׹ ̝yΠCH#Pu`A7ߥIwFa;/V+ZГOk/-սN Z榹I[%x ͷVoLbK@M3.hZ.!l`bmO2Z gH`8 ]HM|4Xj }/fsKqw??)~~c^F 9Ƹxq["5`%BY+w .U z P^T4>#կXZM7T#wRvJoUR{pV嘡ղƯoכhQF2{QISTzܲqX"'VpJGŎ쑊c 2EA .ƹ$g&hjͅtahfXC ȕ|dq(\tTW]TRm$ᱟ%`Eu+(jO ZQ燐g ‹N| ٵs}N88?hŃ1i1~^q>.#g^q({|}Gr#@.0ӯAPsA/ф2z=ӗ!jbbdQoƌ;T*aQZ=,M^OuR`pCM_+}WѸPʗ Ǭ0IVf>AJT'+#;K$'x.kEoaHxze祉pnkW%6>cw\ *^MsjszBfدGxArMa%WJ={9 у}ǽbNW7x(ɗ&|6 'd]ų`j:y!1 =Uj}*qÃE0hazExَ݋6;9bڙR7ʪuU vV%'RvѨvR*2wWW֨$QvGͬ 9]MFk[N޵e\gsfmXr%ϫ _7rL?΂cG8^Zd~;Z<س w}Gs~NŏC <|waS,7utE IDAT( ꦙ"]kS+ 0S= ?ֹ33Mx|#dwLv׷xG,LMuM}.G1:SFRC S$ZįNݥp3R'G2 XҦF܈3G:Br5"Y׸1t?]g5AdY'/7y^KE¿= ]ifn#| +W%u6+ RZ?#èL+?<{oؠ} T0QngDf<=#S^p4G}^?Iǁ7$au$}*>?/D]d£}sf'Љ`)ir&vƈQF۽qCHVix6 rѧa6N ΒhP{amO-cLdKڻ#^BD*l;gjQQzO䊌aM4s ҍҖvpWF0*́Tٷ J1;N `{j)^qU$l}+Fp>ly71,^ç+N@V) fr> uW.XX) Q5w6sdCOFSٛ"XS׀+tVݟҬ g ;@$n3mTZVT=<'Fz6a+إ21I UX'0RHqM{+6D-h14 uV((q)Diaix$t֑DE 9ޤWK+HQJcx\ζ}4q@3xy,ILn>J}NSo"່Gi FC cOEJ8;^?\ʧ뎕(1gPWA&bi#!]dhݢ0G]u^. X3|lCjtWX!*pN>rnDjrdg]&Ci[cɉa6NeB}GxrY*|N.Rt̗hovKJ26 BKf2=mY*~|㥮y \]|= OR Oɨeq4ؓOf!ʟC0V:Fȫwx`&2FcKJƼ>蔛 鍥aX5]*l%PmZY ,<\9OfT$faʹ714hkk@KSMPS/4 bm ;ژGͬ$M.IsnQJFW{nSk'?cJba'nF;5T\micbk`G:L|x:82t~-OviL]wzԆy՜s!17Wo<0{jS0nE#z,ۉ-F*y65h:NuiW6d:wn}iXH" 85Q6`Q>M*Fۼ^LܱZaU nj+T\ͽX% 7.[vyې\bG/3cB*2"hd:P[g)éӭ0Š%i12YfXzHUcN,XUpaoZ),Tyk˪-d1f\ c`cEձˤf Q-+nlzƝO.dU55w%K3hJ(Ot'1ػR6(.U7qC!pn%/ XWn,;S^,h5{n7 d2F3W{ :!2T7Xbo^6HE1?-nc]yy(Ow7ObYՎƿ<#ݭIB`4VJc<ʼmVn)jq|td* *֮B̽Enϊ|1rÛRyHaAg%^7]]ɏ?Im`4i0~cLB# ?諻]l㬒/Nx-||RK惈y}1P(a<=R>.~ǦR<4}&.WƖ#`E+ m%uW֓O}&'0VU.>6~"CNu,WMl9DP/U4 -,h@ӝ9\Ʈ^\;֤$ﭺ(WK`$ÚFF+3Ŏ^ Мvv vGZ$ %lNiճdZ!w&iu-q+>\tE#ܙ JuuԻ~ GdѬy[ ]x٩ ǫwT}qW;ɓdL_Cûݬ++WNo(txc_V5VLt1\{I-6w^=Đ; PTyQMck=XƫX֗i\|){0kf;V43ϵ* їrncHFqU(Fk,;/U|N@ /'F("aZǩ(,EjW1b;;FL8K!+?44IHI,lptLmJԡՕb5c M =.1Gz] 0,7bh".xϛf)jM9N5;\1N>N_8:~V˥L7bN}LB Tncg)F1t܁F,{y;7ؚ䆢T9bĿJSkFNCKI.t)` XMW;27 7NJtJ\(kϢGMZ|+zMb2G7iԀxkf왌w-s]3ܼ.,]<9OAvA<1LhXwi2¬H7WF1FqƄƇh7FcǾWu1h~k_ML[`D•ϡ꿛U W& z)ЀD!9L+Bp6kJ(;rVUBCS[Q☢@*_Ɨ^mStU!Vޓ&spʥuJH7jfh㨴e mwoGcNwQ,>U|_;B M ̊IC;_A?9!}^o۱4#h5. uOXn="r:^ERoQ5Ɍ bz,9!ݰpt&Տiй!U.h3 si92iyp)-rxg1HF(S6ed'4Bhn؅S\\ۆ˭ht[EXUr'd%vmJN%>QKF ΝWE]<#]w+XW_qW'a <ʼ$r}unw#AcײO.C.{+:-%gN^[`Z#LL養,|Ԫ|\&E+yl0w*`DV|NG+Iд"Q:W׹_ì *]{=-CCe_3~mnUJЕ>W0{UT :R8(ԃU{+bRgFcqV9!'Ƶv>ġrB91PWT>J8b C3W8 9CZ9QҎYh5$ay 9'i,-ߌwEǹ>%=n=ΓjLk oPH#|<}3e#**fD炣E+ΫsүY5ѴqԑhtsqڋVB?W/𾿣_L+>]sg{ؽ4`+ lCVq8y㓓Ef>lS2EW -NC9[c>dO$TAS"`iU>h RU)ZԵx-/>O㮏W>;}Gʼ`8U\7WxNtG RaNhY~T{ΑA%",bR]zC )l^+K7B3y1jS7bIEPYܱPw1lxFceڹ7ZSSJ|Mڭ N5&7ՙg/vDISRdsOjpE^Ie\P9Eqɂ#gRuCoư"Ci-}YXM7D; ȴbE R)g}vfѫA>8}x&/MrgNDsX ,b݊:ZՏv;K<էӵ$[Ly(8$VM!Qi*NR,ӄ=<-0F0PE0(p1GL3ɋDKH¿;ն@ =Wp +ިdMCYሺk;VƉFXe)79uzVeZh#ۂ&f=xh-n+ʘ>NV~|TbR,?Q/[pV,^!6Ax| 1y>g #>Șa9-'*}bA'R.YG bi àеw9Z ]_*KgWO$s5ʂ5+ccv0}KWdz~HH%hEVY`E?WFףuґLuk^JoX!fu,X-BM7@KF^jkU2U]̒Ŭ92nB!kX)!c",~ꝥbWZVnwV|̫})#|ܐDr{z-5=[F/= p7=}9逯QɼW"֎ ^jxݍq~hzd ݊`: AQi+Ej(2NVX-y,k8̝}*NOFn7&9'/Μ@1i[,SY -ZHGoC7;*PoTwiP?oFn 24]rX6~nWQ\nq !fS-N EkԬ{6`UEJAS uS9+5YЛcTݻF V~5 Fu.|0ڽG#M|]uފJ˳e߲-Wlcshq i"L'< QW"*f0 wx+ zCx܀NR*8ltZQւ6вHW:0l X JF^%4܈T.(+Ԍ}u=4.DAVoE'JJ7eTsٮW/$=A>c3VQ󌦹欠ɹb%o^wsO CK8U yl#yc0Pϓdw/{~^)>!S;д1xbkt@F\;JpU# 6Zqej,ҝmp7GdOn+ 諿Pn}a&#K6qZw*$*VU1{Lj~9Tk2hi51oq9=VM؃ώ[ęׇARx½~q\Ài…^WJUХD.tmm-6`?Z3VRs+ޠ\̀P 7\?؁Ҥ*l*1K*`;א,|\F#^غs1,tG '_ `J m'^-_銎y7o,FJe56\\- n-bעi( w[o#+rGgnFF.Xzڅ蕖m2+sS[ܐ,c^c?? z7틭J: P~[1LE{Nk?"x z$q(|5d dIMV2Rڞoކ|CZWi%É CVl0Kw;Ӂ|g2)Oh nN;mP>TOr;`^h*vJ\2'E]GGU/S̠SFhۈzs4L'1wvcB;a7o >ײ5v=oFgwgnte>@Ey`b [߱ &@˘b/U~7lfXe1^Ȅ)Wq;n:?{1 - w*&b°lPg#8= C?f=L0Ӡ")(6#))TGq礁%ɣ"nA@$OQXɑB딌XqG_:eKq+@P2=KeI0I q~4Grz/bij Y1ރΙW?5'=s MjuUEr]q!<{NZq '5>s\3.IrqZc6Ct+@7jVؗs&UOW-ici좮j8[8?p5W5tQ0'4KLLq7"Ma,UE)WfS;*3R=xTAhm(x`N%G^J{Gp{R˥xGD=OZ`/}ʭ7ÿ wǵci<_G\̳&%{Gc%yЮ_բE ?KZ7q ?h$-F:\ڔVPy J)*Tiw'(KD `Z.X d1ŷ<6[4`[Nͣ#hЪ @RcæL}Ӛ0Ɔ9 KS w2PI NscjӲn܁FPU/v;enGf -mMRg`dP[e TwCȘt%nGxŻRJug tج*lG}׻xֽVރoł~Gss'WOw-fMN.: > ^N&XlL0"yR/8)0oi]}g Z|qBzm" 9}oV)cE0wbDY9ִهῴ8}2455{Wudpqpې)LS߯t֋@^aQ˯qZh.h+/e3!NG խG@%:bN:#ktjڡ7f2aw8V1y 7KERsSe̩7y3/=^iU^Kߑ BѰ]@fRNWha2 8Sx0I@N aSEIu3ٟ(.azlN3E: u:Pw)PlBv(a5 DKtn24]ϒ=u] 1jְ]O֔pI<盛 ޻(?w8Aqs^`oZ|\a%'^xR wxځ ,jqT Eu㗿Pwd :Ф|m/,X%F{X7f8Z C9ǎ2cFFxYW~c 9anv%ziFF1 :4:;&+GζC5nri#]O>7 t'?K.Opn7둡;K1+1Czw|B%|fa=*GW_fyw^}o~)+q'ME5JJFS57Br^z7iBz![Wk8{!v{qjl=Fs;vf GfF9D1+Uūy+z4F7o Lz8rK @8/ fPrᡘ>*yg_M7`}= ;qj `'1 U;ggΚzsgCE/g3M^%ro\%/Lf#uJ%+&'t1*LnTHά SQc?t*lR׾g Ą^]`?jRgFqW%K^hj%,>:hn*Ia3ғӷbp(QՌR^uQqO=Rp8&5MEل'm--:o6EU+,EׁDFY#,#ecC`{8#}x:_MYavlG+?{߯|X^OΏbjz+AB/Ff.ZqeAroY*rW$ _H<meL|AvB`¤Ċ)[ @k+|Rj2vdU6̦$`F9 cwoiaM5k3\Tm˺ lyaZ65W8 #uQzvZwW6laæFun!b\o1D|ˑ)? " |KzO;|yy\tN^ߢ*^hᙉ:jf$!Jϵ^5_ #T2o:Au*Gd.9!ogߨhWNS =5OWۮ v՜x U2VӮ@#" ovx*QML(4f\,{en NxJzt_ze#}(6Cr3 GQ lLYR/gbn3љzCe`pNnuBw>mXz$̖c i7MzuQP {\h wFABsfaiY5&uوw4قHfڒ`jGt)-(oKLkJ"v6tr/IJpp'brs1RBTw]ҬaMw闎^LcxG4!zTUWǦN»-R Z@gfj kc8W=[w `8GD |;cnj 1۱8|4a y ^ QO*D#I5wLoe;gݫݺANw"gtJpO '0_>@Πy B*uSԙW`ht[lxzcE516}M+b-GLlѰA+#+n̸ UHW ƟfzC%%NL=9B'_`̌Sh2ȷR鈦{Rǡ]/W?ϕPhx,R3mlfukϩxLW\W*5xHc}n>mϏHq|5qz<5FxI5HXLZ kAZ*!-Yy7~cT@_,Sx FdP2Ż ?:4}ܗݘ4;et1kkv`5;eRQm7(_즧1wCqw^)6F8T}nsUfȵ,JAb}ޢ>AKů:,$z{XT۞)q.*r^R=ǝu"q0w;~SQ8k}x>{gֻ97{y#ܛCG3}]cznv<@x3l"MUHKYUYӳ6@I~*W|0?2"o[fѴR3Dm̬7Vxi1mdV֫iԖ]1znr.^7[}1oY@Xb!foh{l~pAݗXAy ד6x2TM0a -j oIH;KwyY^SѲLؕ=:U$z!,k"yG8Rd+,V\;Nxu{)Bbp]˂r͞J=,GښVU0ȭ0s+Fh۵*ke]."ЍO3l\aM֤v[ IkSs5B&u=`XQf}NQ\{+Mq^4o5WNgwq{\7?w?gpteU010-cG+U4)V7](oʁ"\=Ą;Dw3-*6MvNOԗesEzyU) 7UQP@1:E{N9nƫP&yJ|9;Yvi:e2'ť?Kw+x88l8bz-1b6DD*۾^&f?@/F:%ھjGǪ*Ua3Y7]~oXwM0~0x?yf<{Zlh{_+Ν'uz  {'8g.6YQQKx{&197aR}Q#s7e ajMGIB7QY8Hi0c]3?~βs[Bh쟌nRVXCF>qTm|~p.)B^ (9ƩdbR'~00k'Th8hܝ.3̕rF-SشOT`b˙mY;as 20asf?T)ď?\[c̆j/&Rԭl߉ֽփW}>MQLD ]t!֠Z΃=<2!D<}s tV]ߋ[QS]@DndcvZ}Ao?FIHG f2JȂE0 1mӤF]k}F u5 Ol`tO:sqa WArEF*ē:DP/[5H2=+D)9e?24M")񂅩݆-͑%$] " ldI MVoIjfY8mZ6ٿvsa,w{8-}={or9e;௜Z:G&+i.q ,s-l}]!! 7=U0;wɠvk޵A6瀘%P.!gk x$7RzJ e&(mN q\X|&a&g2rZ&2Xk-0tF4l< w&%#=Q==F.dݴCk~_H܄FƾkiN0/)9%,23L.;vZ-K~wW\kio W,myh.so}E[}i=EL"0uh0yPM`H1 _ |sq"F",׊)YEwm*hUmIgePC=;D߹.0J%0ܦm0Jұ́iAkׂ]VwX*g5T(})~M  q% rt3M#Ŧyk%4'w#PQ2[ᛡ+?备Y~ V8Gy )|NG/gFE~u󴽁&+gw{9,ŋ Z .Řr./b"l-5$œ/7,Z.*jS9U,CXq!שEQ*ck<);]BhQ*VHmˈl"gՓMqt喣>RМ?[tn+ifJkK3%#BX=pN]#k*JD=;i{˷ IDATN W2| fkh,oDgvҕ$@WM>7'I|+x ~l/ݘsx]s&mP;8;-5KjeZ10Thꀤ/"E[0kI@G=f)h ,ocFna4@uiD`>.+n›޴7TKܳ3f[FIBw6l`E6i$q^97IdDiHmBrA5hZ'HCi`/}[5g+]q|-%W;oyw4)`œXFaA?qpOCQʔ C@_ک9 0z41}5ʌPL _^?m5"SV X%fn?DC']+K\q#uLU1st//pAfZuWʨ:/( _te+Jvgv0w&7!`Aކ0>{B <*:<;_-<l~i 0b@Nnx(mȈIؔqj?=1;*Xh3Mr]Ahwz!ת;'T?Te(Wݱ6#jo=h9v(]oe= ;Ge? ]jYyHwXhˌ Ԣ1;e8h,m2\Ls.^5UtBkNq,/ ʫ}$ھo1=>y ⽮_Vn+>r'9uMk@xRaR|Pun]L?p9K3&x7LvzfJ;`Jߞ3sJ&n;'tY=ubWߌ3cϿ =3,x?f?x68AE"fTϙo pKY㞔"/R3h_ @DɵLyyYyՄz_x3)N%c9 O<GSSdu5p;x$4%'$W^q r33FA}mCnc,Ǽ0U?%Tׅ]B!*NG-Ϩ9\<;+O ^;k4|0}gں6{!vȭťu^+:s|zW3υ4q~1<r]'#ݺ[˛~hcc f Ӭ@T4:~WvJ1Yզk_ PI8dbZi6tl5bQ$NhԽMf 3![8c^]EdZ:-*ΣO+2ə c$ s1^²|'u%HP,}猔vw 0elZ\+M Zߚ~»:{G_̟{ޘ҃3mr ǁKpu|*"8݇;$̫ey0 fzESzBEL&hHŨ4_n,>)gAT/SGKޭ:uw)fV?ٲnr+D-A3Uc[WW{=7f4Ox@uo5+SmRJZɯﴤaqI°{951G\XԚmnP$62:{E1NjNaHvqR.GXfʟS}G|Z\ܬNø*āJV.Meu,{9r?c~4Sf&n`?8~|D+ bLCC\ !O _lW$VkA1Cg ?9,3)5 "Um;"bOJX9$!gƍ[C{LPh0pȯ4x1agg0L"4 h I4^Q%>swƾuV+M.^hmG!7lm-:9|jW ɮ_iƢX1w+3{8`tW+> :Ou5~ѲUtƁcNJދ@#{~a Apy۞r0WCwG6\ePF!.f>s׳O3#:jT]Z= 0'R&9Nkf5? n39('98\ņo?1;b\l.̮v.4U:vr;͟ \)sRq9c6C&YlIuXBs!V FϟFN@!7k3jr_pϙ[˵})}7[9Sa|cxuOZ+3ᓿ1*c~Oܨxܤ6G?A*; ˫GHEK?B珍sm@$AL t -&&p== ͊Gp Ih6j!T 6•jÅƐ؜ R]{fP.).ZHNÐ 7.uZRri cTG'wȾJv+ⳛhMQͣ Y5n P&)rԉ;&TB p sv׸_Y'^m3)8o :a>oxtb{OZ|ѕ~6ɽVRO0 bcZV5ROLA #W+ޯ7b_'u$E`@0Gq7j>c>[D-*az(mI8c\.l] dSiBG,]&iަqrZ<mKr lQƵ{]jZߊ^XwR(oc`+xQKOIѣqu?Q4.i|zE"T:Q<~ iMK&2ɾ#go~fv]9vCM;F1xNA9qSW*H`Rr 5jNJ)Lw/F"Xu` o%IFLI).oiev.CjfFa(ugG،]O6H;zB40 0pĊL[,:# VffC-*7&7uM75( Mt2.heH=_{3gETʬ~?.$k`!;zwjT(z}^9=w+zUqzrEyuI-k#Ճ='6a)i4Lڭ6F˖K,S*挋1CqVLDNJډS)AQQw݈ R/ => k3>F.#Fbn6s % aDnv\4ٟ 6lgAN'mU7 @K;01&صi̓|K3L'3\*$ɤFTOԮ{)YSHF%Rudɡsc!:ss}-~7{f_D{3>9m?.F{M t/yg73F. J-nô4%d:e{(q4fr]B[kURһ.k2ě7* %]4](|KY17{p5n}Vqu۷84y}Kt'\"w&ߝ?Uf:mԪwD߰c=q[f8k+)ܠOfތskG;Uߒ;Wu?-[.aqY?wD hmj>D wEox'w9oXc}oww`oMl]^[7+`f4Dsލg4P\q7L]z l'uYqO 2gKB/fI{4T+"TPD̺EC͂dó*H?N)XTmRcӇdU=CW=uݮB}w{Ec}6n/=nqo\(qyr ̴tLϧ8!t[z4ۢKJW8q&;Pjaf +juWXzdW!V@*e8:5b\\ WVi*GhS\ly5 9O$5Ä*k` M-dh΁ԫew$T= ;9_mּTBeK[9S/>vfqn]}^./5d|4S;VXk}CZF%84gݞd]KoC+Q@/'bnA`s.ڼM0U̪>'Ph~d^bpb屋ȃ9opIhPax~Ek0yd).+8rd 4:鷔`4+/c ͯHW:鴎ANߗ|̈́%"OR^Ԫ6 [o⓳"ѠOI၍FdpNCDɆlH7Eg{9ln߮'MFj4:]x+-׷z#B g}WH8^Yii=lx%<k8Z/CGW+14_O4۲ꢊAL6չn|<ϘVc6?U1@ah[yef8ZDi0\w?1]k7Ϟpk(Ӥ͂>s/z6&}s ?7!F+",ȣOߠʩ .Dq!h[eSaH>FRmT)lHӅx ?9>w} 6 ⚌ݯ:K&%'{:Xp'a=o\Ur]On%&@B{d'PT`ٲO_c odXp7̿5`=~.7G#]9߱*O̠EMވ"1V;F;'nkoZU=~1ٽ={ZЖz )B%؆YL~  mVU /v`Fa2ƎVpE1hOx!c/i|eS*]p`:%{.&&@ȘR2/8P^a@ÿ-;ۤaVv OМ8녝S ] ^ppJ ٘^9N{I3WYIìe O\/n^r$_*+z} SKjL.q`ε/fVWFvȬfs@c,DWiD0*mFmk6:qڊ.x ~ ~s]nso4I;_R%yQ{'c3 TuF#O gBJiBLa@0˝LFFክUTZ\/ tL帬 Sx@xLO[6jcj;h s4>^D&IO/Sߗ'՟EU./X7)nnp08jW>]\S]TJ1l|(<&6M2)?A`UX tA,]JB!'"r!"H~PX !@nIaH u qG3@\;}7O64lnWE_ ^4w osg{P^w3xv7Nin(n<Ww31I2iN;(:DEЄem^426*!%>M6ht8Nh6raubh+1UK H(Xڟ՘NdNJUጱʷ4PŽBا_t6[#_\jP@Q&|rN©ApoM10gu$ETyN5Vt9y?xɭJd]F!yܦk6@[4lO-%żڊo7ЀfS><0r"QO Pa{RFŤ.Yv0 URƽ}BTqr=4 ,y؁&/rA)o9+,T>NUط+JVd>bvOͤ9U-4? _#c|H3:"n =88_{ԛEI>V0P^aƉbX=r( GJ纮UW;4dCh/A={28xh [/#v%p6 ¬9۩1SI!kiU}|7]'+a=9{_bazb Dh?$mBȣ,vDE8u*V-ƚw|}6? v@{R |l+*d0>ҴʷBؑQK 'P˕{Y}" xQ;\ҁgwMA_Z%}=O5_3{]kxLJ0ME+p?{r裭i,y3ֲRq<%عh2A#Q-hPd%p"I4FG0STKe9(P5MYZkKQ7k\1 ؼLƯ t[s/sfZ y`*=NHIlW9]Cll=tW:2ė|WLurfx~Lctu} A7];bFn<+ɞzu2->M5Ϫ^4:q[OCt?.B0Akeݟ"ݑ9tѼy1+6K4"ZuQ%">E@/'wL }J_);& m'PY5c|k CQq#^S<].Qr*R|`3Ѥi_Œ @*˯R'=y0{'޲|.AQCv7_beb'ol5:VXY Yێ9yE9Zi Sf,Õ冽 #:c5pƼ~}vv!:}V2/ٟ4b-k?H7;NS2 eS Z4x\s+p0Imc06'xI( - Nwu œ$' XoY@mJAK5^)ci,_UA mB^ +u G xjɤJI2jn>מ .8se8~X)yEod˛yooS@q:ǟc3D)*)Nu^+'퐟uzd×;4B,6Wܫ f^76|8L":􂨶^[?R] 1jdk1'89r7VCWCڊCMcOR"hӂGPsuˣ&S+*e%r{0t[cB23g0`ޮ,&㭯N*IRxook4ci6|ySzƸԠdط?w0hGtGY.+J}+ON ^ {ŧS_"qeg3#b:8cD䒗Pϼj,`3fi"E+dN5<1187ˆH!x)[PBMY>͵+\AScr4x{^>Ql_x:޵#&Hx]mDp5NT3g޽ H֑qcz-\X1 W/ E,YxT*ud_#|d…_W+c?R[+0~YZ K=eF}#LsBkp '@> ̴:JMC;k`5r_k)B73U%WO% AERGwa^6 T|T%VvHTw"+i`Xx]#CtUla&H= DKQe2t99`!dgλ~sEs-d1|'Rya2XKL\K̋]:4qoߜnǻ#,#P|Z!]a0 Vk8n}سAq{F[ _l}NQsp` بy,X PXDg8YZ2;ORiմBi:r\D?UvfJg&DpNO ^bL%B1TX2:1rܼil3w-G ͈St)`wʡ,kc ,7oF5 PpY+SfgU1>2 OsqQQ9qց)1j1F'h"ŏ{A,VVJ|\2KJu]!h| P ١`v`zFwxo F@#,3PzyΙ#& _&GcApߗ$؊Z RlM%5S7xYӡm"JWD 5FRˊK T9#1``N6tYlrŌ5y T*f+XU[XvN2J'kY y%2E&' oB*Wv[0!n٭2sՋS._WvGPUո{Ӝ *-s(4-zSh;'Q7_NbtphqPT:nH*X8QWXWY;Sǂ=JFSO}/ϞKp]=O6Xi0kQ1ǻg5"A) 8O"޵P-ӠsCp"]F.@Fy68Ű.c_3F:w]xуagŌ28ZdD=9H\9tq88 MLF}lM"~HUMuWkG"˚Έ:J_sj5ʠ+ӱ3zV`=~wx9<خSGH P㐅>,z3}SJs},y0fQ t+y#шkŶz_(`Y.&j+]wռP 2³pM"rB&zI!KXgbN~JܖbVrU'6댺kkaANT^7{~ mWY0|m\"¿@ 4 Ԫ]`<V@Ngs*ZT+j00V_ (}ѥ+C7V9n_W!0J;#ş Gei=/chfڇ:jP $\.|9甕3[.}gni TW|l1Eܗ|;¾> q;<>F2ˍ3O>8|SX:TX \2*ܿN+Z)zsuV,i6;C e`SY~/cV/Y7l וpi/;?%Đ^#䝔~Ń26Pܭ9|00B_0(2v}YcTcJR(2@#~!HX]Ъ@T$JsVYϒ*,v]P7UC'zTlk_kq@>Vloސ8b7t [.A6޺lOSa M6ō,:j_1J-ȩ>AZ(Pk?-k$21O5!Џ tEx?jE`o"I5 Jh-#%:af+Fț!/ŌhXU/,E\g_P1B&vA:ک#tfUw(iͼӳ>a=^+/bE#ۏS.x#'"n)i\G;7y [ I?Le(v먍{z q&P &vD{e{d$,QMF}hfTT]QCߧVf]V Uauֺ؍ݴ7ʟjIDATl;ۄ WlHf 8 !W(Y{TS11V###X{&l?o6lpA1Kq \3oe{ Q0 R*V~^X\Y\!OXp;ѣ;87>|TAW6sG Dso\ځߧ#FD(bRGt5FX B?$M3 /B}3T8 n,%& g=)uGd9/-͑@YK2ri\XXLw,{ʨi(HMY1s V(\WsQg+D}ʱǂFw" 01J @b&C{`;lΓ|{|;3Ҹ1>;}TN`yvb`AKNwՒCI'_.0lEpT4Q(:F; VAFacr.>x.Y<-IJx:fxzB:oͰNt7¹Hn"lpqrE9KsIJdsJ?GШ7(ɸx*6ܣd1 `JF`ʲr3{c^*uA^)0)+0͞ ?ep]BG 2\cM>ss|#tq_M 8$Zt WNNO_]ɷ{=n#r{0Doe^.ѥ|uUJ 96kJ/S;?5r(bb>c5R_XA\*,ۀėV[ eVHF?ZqjQf$Nn2 /L{^tc`ĵ/1 [PHshT!0VeY5#3 ?F {әY'k_FJs޷fmw\FJ۩އdB(1/[ k{LD!EBÅ^,>W"-B]8'W2^Vq>"HQa(%]'_YtV4f 8T:=3 %2 ݽӨL<_NRUܹTX'j3O۪`V6wp/# \XC S 妁 ЏKnH*7*2q9|V|f͑S]5SE.Dt*1[/)S_YM85PgckSءC4flISѹ";s}O=ϭAqdĕ3ƬLccfGϟh?fWuOcg_Meü׊^Yi'dA2Qӌ|Iׄ.g~Fer-2\oSM" 㿍ST͹34<&Fce>R q0]sϹY4U/r9GيMZhrFDŽVhѧ~ ){BqS\nF{"(\i<>թY\#ϥ3>* hgt|O-q%ObVMZ<ǽeid46dOpee0QWaЁzhGaN Y]I I=|pQ\ qoƘ2;SsV,C 6N3ES/](3@e3nNsSkW=nG gsmMlNx{t%p>sTS~X.@oZI~܈\L F ҉>n_yՑ|O 0l"~7r-uZ\^a VkXwF d Ҿ"nWZfi7܋>[k,~ϋO7 (5(f5U1}LNQ+KRwPew I %C}]׌}O/&; PUʇV> H72B>1#GR}0G{C6r|=>м*\~pGH[Xs K?%-H cx[|Jw]4ĪE?8e λr$VDB2_6 Ϛgj f7cyjj)\w6\Ia$/ #%]U{#LF3~;YM'7o 2(Yߍgs'<o:Fy{5צḦ́<w/Tϻ(fR~:sLs*'10ptpxXD4[nhy *t$ܠ[ [P#23A܈Pw5We<<8[A($)8yU8~␊R.! Qʑr>FJ@'݄ƌ&{ym]rU[234Q N#h Lpp,f $5R6a{X_&>_Ox ε@?ض" j ymzFՍψ(_/e/?tC^zځ5GO9^q~ߊPBs\njtITpDQ *_WL" 'MH g25ڸoՒ+_[\Po^WU[VY wن ߐ(Ns->u ZSћ[(")(d(zyUu3{(gX}(^(g*VJ种p\b}$تط~wav|l ۜ4{V: Q!Nunqz] ZviI˿ &yldFuxww>sǽ, mF,q{TL$WuX*ژi#B7ǯ{UXS8MRD M1P} ]9QӪ{̽O\}4gEhΚۢ7cdw/Μ ʞKY!g#Twtї\ikj9LeZU:jCi%?%dw?4G֪sQ:͎|@,ё> T5U5=$44U Ϗx&tekV<ʰƱHڏ FP>Qk zAa91tFHT}:ˆVNV B岫 K^:)QWʟ⊁IĪm2UvY|PY kHqpU1Ło̓X6J(/*r4US:3A }]æVxnj_s tEǹp?tn!) Bs{G荙wɟWpMsZ{P+,c>Tօ ϡՊ#L+i @Ǡ(tDʒtoK53 TLSBxFS%dSD( #IzƬ_S@A祏#Ca~r 6(Ϩ{pQnja~ܪhd#w*/"& 18"wڻ8LF D4~*vqur[9I}PEj]swŝlsw`8Zwy:6^tF&x}8w:"96~g4"E%iʣf)#XogFJS0a N[+]Yۄ5&(-l1~|z"Jjh4M.= D c&\{+yMQa*[2hc!U`oQ\ٌvF <+EX(wk-crқ_gmG7 1cxPZl]󳹻Pޘ{e'Ʌ}c:c*9jY,2}*:Y#~)j׭*y/EΨ"}W2#dZMbV}2"EpΎᝩԒ/ =(Flƺp +|fӍtvջCqTBUEǭ[/Oүc8-#B Q/xQ옼!EPۂY(}Ok#NfYbډfAI3U{!{)${@ <-f.+nN01XJSܩ @o=cJ954FE#UD.F4wE#휆YpJ 7Ci#?ձ+q;~~ZgM%@o$ω>}  9y`3~F $XN'VG-[4#GsWR0 ֓^ ..]bxKZ`݊&+r#ј#F#_uT~VSڳq3S[7݆iaMU.dRuj=# 5ZH>ERZ^Q8o켦 gQG74P&9.kZ_H[B63m[1hvpa3 8r(Rvvoit8᰸⣖ ߪ3v]Mk[IENDB`corebird-1.7.4/data/org.baedert.corebird.appdata.xml.in000066400000000000000000000114361324604713000227560ustar00rootroot00000000000000 org.baedert.corebird.desktop Corebird Twitter Client CC0-1.0 corebird

Corebird is a native GTK+ twitter client that provides vital features such as Direct Messages (DMs), tweet notifications, conversation views.

Additional features include local viewing of videos, multiple inline images, Lists, Filters, multiple accounts, etc.

https://corebird.baedert.org/ https://corebird.baedert.org/appdata/1.5/1.png Generic timeline view when using Corebird https://corebird.baedert.org/appdata/1.5/2.png Typical Twitter profile https://corebird.baedert.org/appdata/1.5/3.png Account settings can be configured AppMenu HiDpiIcon ModernToolkit ryanlerch@fedoraproject.org GPL-3.0+ Timm Bäder https://github.com/baedert/corebird/issues https://flattr.com/profile/baedert

Changes in Corebird 1.7.3:

  • Increase maximum tweet length to 280 characters
  • Increase maximum name length to 50 characters and improve certain parts of the UI to cope better with longer names
  • Fix the emoji button not showing up in the compose window
  • Update translations

Changes in Corebird 1.7.2:

  • Fix window cancel button label mixup
  • Fix potential crash when composing a tweet (#779)
  • Fix abort when trying to detect emoji support (#781)
  • Fix spell checking
  • Update translations

Changes in Corebird 1.7:

  • Hashtags and Mentions in profile desriptions are now clickable
  • The mention-completion when composing a new tweet now fetches unknown users from the twitter server
  • Videos larger than the screen size are now getting scaled down while playing
  • Profiles now indicate when an account is suspended
  • Profiles handle protected accounts better when trying to access followers/following users, etc.
  • The compose window now allows tweets with just media attached but not text
  • Improve the hashtag/mention/link detection when composing a new tweet
  • The compose dialog now shows an emoji chooser. This is only available starting GTK+ 3.22.19 and the emojis will only be rendered with color if you have the needed cairo version installed, as well as an emoji font.
  • Fixed a bug that led to wrong Direct Message info being inserted into the database
  • Fixed a bug that resulted in broken files when downloading instagram images

Changes in Corebird 1.5:

    Media attached to tweets can be downloaded using Right Click and selecting "save as"
    Profiles use the profile background color set in the Twitter settings if no banner is set
    Support sending Direct Messages to oneself
    The tweet compose window now features a "favorite image" view that allows users to save often sent images and quickly add them to tweets
    The media dialog now shows Previous/Next buttons to quickly switch between multiple media attachments of a tweet>
    The Vine support has been removed since the project is discontinued
    Allow text selection in Direct Messages
    New --account parameter allows opening the window for the given account only
    Support tweets with up to 50 replied-to users.
    Add back verified icons next to user avatars
    Redesigned account creation UI
corebird
corebird-1.7.4/data/org.baedert.corebird.desktop.in000066400000000000000000000007361324604713000222170ustar00rootroot00000000000000[Desktop Entry] Name=Corebird GenericName=Twitter Client Comment=Use Twitter from within a normal desktop application # TRANSLATORS: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! Keywords=twitter; Exec=corebird Type=Application # TRANSLATORS: Do NOT translate or transliterate this text (this is an icon file name)! Icon=corebird Categories=Network;GTK; DBusActivatable=true StartupWMClass=corebird corebird-1.7.4/data/org.baedert.corebird.gschema.xml000066400000000000000000000104741324604713000223470ustar00rootroot00000000000000 false Unused "Never" If/when the user should be notified about new tweets true Whether the user should be notified when another user mentions them in a tweet true Whether the user should be notified when another user writes them a direct message [""] List of accounts which will be opened when Corebird is started. false 2.0 Unused true Whether to show the topbar or not "<Control>T" The accelerator to press in order to bring the ComposeTweetWindow up. Must be parseable with gtk_accelerator_parse "<Control><Shift>S" Accelerator to press in order to show/hide the sidebar "<Control><Shift>P" Accelerator to show the settings window "<Control>P" Accelerator to show the account dialog "<Control>K" Accelerator to show the account list (popover) {} (0,0,500,300) Size and position of the settings dialog "0rvHLdbzRULZd5dz6X1TUA" The application's oauth consumer key. "oGrvd6654nWLhzLcJywSW3pltUfkhP4BnraPPVNhHtY" The application's oauth consumer secret true false Whether or not to use double-click activation in tweet timelines 1 Bitfield for text transformations true Whether to hide images marked as inappropriate by default or not "Show" When to show media attached to tweets corebird-1.7.4/data/org.baedert.corebird.service.in000066400000000000000000000001301324604713000221720ustar00rootroot00000000000000[D-BUS Service] Name=org.baedert.corebird Exec=@bindir@/corebird --gapplication-service corebird-1.7.4/data/play.png000066400000000000000000000021051324604713000156760ustar00rootroot00000000000000PNG  IHDR szzsBIT|d pHYs:tEXtSoftwarewww.inkscape.org<IDATX[lUoY[kZL8uM-ƤӬԤ$-Mkkki)p h@cT$2!R-K^R綻sL.lOa|g_Μ`mC,/,FqPQHw`Yl}4>E^|)T pٳ/:냽EYV#Iw\aspYVt[2i>EQ{p@ c=PfU rW|DS twWUΟqW|ㅧ]zfD0k?-,S9[SL?,Bjvg<[+>BNtw~ɬY]*I+ 7r7!L!(z5K?e9^~qwWPeE9z9ěw };j{HIf^<`JCCSbfiې$* m{R'`(WC$dlHr"tN [UD>EhNy< =p8$ޘJąEШj~$Oq˲ɯvHn4cccf(#H4?j|?m߿ |?6;*?o 5n_s9^ahk4 <w࠿ JIENDB`corebird-1.7.4/data/play.svg000066400000000000000000000050631324604713000157170ustar00rootroot00000000000000 image/svg+xml corebird-1.7.4/data/play@2.png000066400000000000000000000040151324604713000160620ustar00rootroot00000000000000PNG  IHDR@@iqsBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<IDATxݛ{P? "V*Ab5;kզf̫i'I[ǘ&ؘXM}ĘGUAAMl1I;DnVuɈFJ A ]w=9sfWp!Jr8xT@*.Á}($G;|*mW0K@1q1Ebbr %;Y7PJ]El1&@H~ LG_s\p{)Sd> &O0\ s29C 8*Q % `СGkذ6CZ䭢$  - 7ɓ$(Jr%ڄ.(#11$ok/ r`/DI(ɡ=& t/1=0|'> |ڃ˗H8 Ǣ$o%yφ-ƋzI8SXh!aa/EI^"JU XPzqGA_f$c_c3h:|ZFT0ֽ:6'zH"JFnE3O=IHHH8!J<[`*UBCCYgg0U0(Q;,|1I\ADDQQWlK*A+x9e3' (wtdHd$_[7$O$ç3l1Qj//п(/ҳshllij_s$xɩҎx*Ӷ;ǏpKK ;vRZW\.t~/= ّڀ `*84K)luugQT ,T倵N(wPPTBӵkj CSxzHLqPs괧iҪJv$@3uܮ.Tڀ嫀ýGCC#5`MUZ| .,N֜")c EMUj|f,/*a׻l\j]Di#=>Ԍ,.7.k4UlUvb֞3x $;Q%_kAQTW'O8[{;ƌnpuJ))}sݧ_4{uv;0?:zXƸX>dgueܾ<[_2`P .Q%IENDB`corebird-1.7.4/data/render-icons.sh000077500000000000000000000003651324604713000171600ustar00rootroot00000000000000#!/bin/bash # Oh god I hate bash. sizes=(16 24 32 48 64 96 128 256) for size in ${sizes[@]} do rsvg-convert ./corebird.svg --width="${size}" --height="${size}" \ --format=png -o "./hicolor/${size}x${size}/corebird.png" done corebird-1.7.4/data/symbolic/000077500000000000000000000000001324604713000160465ustar00rootroot00000000000000corebird-1.7.4/data/symbolic/apps/000077500000000000000000000000001324604713000170115ustar00rootroot00000000000000corebird-1.7.4/data/symbolic/apps/corebird-compose-symbolic.svg000066400000000000000000000054371324604713000246160ustar00rootroot00000000000000 image/svg+xml corebird-1.7.4/data/symbolic/apps/corebird-conversation-symbolic.svg000066400000000000000000000050611324604713000256540ustar00rootroot00000000000000 image/svg+xml corebird-1.7.4/data/symbolic/apps/corebird-dms-symbolic.svg000066400000000000000000000113241324604713000237240ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme corebird-1.7.4/data/symbolic/apps/corebird-edit-find-symbolic.svg000066400000000000000000000111161324604713000250030ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme corebird-1.7.4/data/symbolic/apps/corebird-filter-symbolic.svg000066400000000000000000000047671324604713000244430ustar00rootroot00000000000000 image/svg+xml corebird-1.7.4/data/symbolic/apps/corebird-mentions-symbolic.svg000066400000000000000000000117131324604713000247770ustar00rootroot00000000000000 image/svg+xml corebird-1.7.4/data/symbolic/apps/corebird-new-window-symbolic.svg000066400000000000000000000064511324604713000252440ustar00rootroot00000000000000 image/svg+xml corebird-1.7.4/data/symbolic/apps/corebird-profile-symbolic.svg000066400000000000000000000043611324604713000246040ustar00rootroot00000000000000 image/svg+xml corebird-1.7.4/data/symbolic/apps/corebird-retweet-symbolic.svg000066400000000000000000000116641324604713000246270ustar00rootroot00000000000000 image/svg+xml corebird-1.7.4/data/symbolic/apps/corebird-user-home-symbolic.svg000066400000000000000000000140231324604713000250440ustar00rootroot00000000000000 image/svg+xml Gnome Symbolic Icon Theme Gnome Symbolic Icon Theme go-home corebird-1.7.4/data/verified-large.png000066400000000000000000000023771324604713000176310ustar00rootroot00000000000000PNG  IHDRcsBIT|d pHYs;>tEXtSoftwarewww.inkscape.org<|IDATHKLTW߹ 2( h}`EMh Qm¤.&mĤtӅ҅ DvUc0܁_=;w9GXFLt25(@?*=+R"'R>Xcew¡1zh&ГvL&mE\~_Ŧ+BBχ0] J<18qF w[dM/;f('+kRX SkknTe>J22#g)tEXtSoftwarewww.inkscape.org< xIDAThkpS{$$c[X6lC MB;y5MgL4̔ N3Igڤt&h$M3l?elK,JdIgݳg^Avϑ5+[ *b oz %ک721XrŮB< Txn:LזT:T)t&I?MU9%"{5+$5X,ydega0y3^&'=|H /^;+Yے&R'AdAt``݆VDvNV¾f\o"j6='}ٴ=;!suK6V([a~tvtİP!/%'!M4sp^mՈŋ0$gw?:yligǵzQׯ厲Uv{ &qdž7KKs;}CUMyG&?lw4UpwH&l:m7J([d5|S_5 ;|~6zENzUɼqPmwԣ(:x3em >mGȼ.4nBcr؛fVXL4X@]nӷ R]j@vHX5 Qw0$[,yFL -cW? "\K+WwY[xfGnX^U/z>\/c.eeҎ˺fn.`Y6abʏV0 X#Bj3àE.U/[&@mY"/,,:"  ,qP[ x<8_$]^6bPMrD).ocbVX$v7D>] `Hu#U|><^3L՞ydd0~eT"_;)pu`_H%/tdy |9B&߸~'Dl1$pa_L7ħ>CB牗6f~n)5M'-\J<5HǗдDX*qM4{rNw; GW}47<8|/Irx %؋B"F\c 08/Ic=g&c_JQ6Hd2b䒕 {_8Ujx){fT^z%=hP9TT9&g)&NRS J&C}@B6 e5BT""H&"e7B\YE;ށL?7ej5IENDB`corebird-1.7.4/data/verified-small.png000066400000000000000000000007601324604713000176410ustar00rootroot00000000000000PNG  IHDR Vu\sBIT|d pHYs a aJ%tEXtSoftwarewww.inkscape.org<mIDAT(ҽJPsonb (? QOIK+4JEQ^.=Y!B@fdE.PB Qr! sJ Bd6c㦙ftMW߆}xAv+$E_F$P%8g\_U b"MI%pfQ\6.!DLcpN .)MWN`21C~y1gh.)1lHNi0T(mZ>._}r,N=!Qڠ\Cv7&®lێDž8^߉ƌ`\}C- ­_dƒ_IENDB`corebird-1.7.4/data/verified-small@2.png000066400000000000000000000015751324604713000200300ustar00rootroot00000000000000PNG  IHDRw=sBIT|d pHYspMBtEXtSoftwarewww.inkscape.org<IDATHNQs3 觩bӪ`Y`Hl5ԍԅ+jxcP76A(X`Lghe@[)T.{~9g)%,;p]he=ƴdbCJ ;W˥JzN%"dגBA x GZULsC1]sK@L<+BhD9r4(lgRJf5fҲ,%~Bd'O˶Z F0+N B'X>b';BH`f(_h=Q~~7r耏hD$A9t x9+l#y:^|fxxĹ#?Wɚ(I‘VW)@ކ=|:={I K].*+|Kp:;nv7rR6 \ 4w6/D}#wL,Ԛ?U~֦V:\hn*'J#L.vW2kEWs=|\޻n33H%4W_0ӷ p\.iA:y e2W ~`Ra00]ĩKRIOFl.d>LDŽ, =ܬpݒ8!IENDB`corebird-1.7.4/data/verified.svg000066400000000000000000000056341324604713000165530ustar00rootroot00000000000000 image/svg+xml corebird-1.7.4/examples/000077500000000000000000000000001324604713000151325ustar00rootroot00000000000000corebird-1.7.4/examples/.gitignore000066400000000000000000000000321324604713000171150ustar00rootroot00000000000000tweetstates accountdialog corebird-1.7.4/examples/Makefile.am000066400000000000000000000022461324604713000171720ustar00rootroot00000000000000 AM_CPPFLAGS = \ $(CB_CFLAGS) \ -I$(top_builddir)/src \ -I$(top_srcdir)/src \ -include $(CONFIG_HEADER) \ -DDATADIR=\"$(datadir)\" \ -DPKGDATADIR=\"$(pkgdatadir)\" \ -D DEBUG AM_VALAFLAGS = \ $(CB_VALA_FLAGS) \ --enable-checking \ --enable-experimental \ --vapidir $(top_builddir)/src \ --vapidir $(top_srcdir)/vapi \ --pkg corebird-internal \ --pkg corebird \ --enable-deprecated \ --gresources $(top_srcdir)/corebird.gresource.xml \ -C \ -g resource_deps = $(shell $(GLIB_COMPILE_RESOURCES) --generate-dependencies --sourcedir=$(top_srcdir) $(top_srcdir)/corebird.gresource.xml) corebird-resources.c: $(top_srcdir)/corebird.gresource.xml $(resource_deps) Makefile XMLLINT=$(XMLLINT) $(GLIB_COMPILE_RESOURCES) --target $@ --generate --sourcedir=$(top_srcdir) --c-name corebird $< noinst_bin_PROGRAMS = \ tweetstates \ accountdialog noinst_bindir = $(abs_top_builddir) tweetstates_SOURCES = tweetstates.vala corebird-resources.c tweetstates_LDADD = $(CB_LIBS) $(top_builddir)/src/libcorebird.la accountdialog_SOURCES = accountdialog.vala corebird-resources.c accountdialog_LDADD = $(CB_LIBS) $(top_builddir)/src/libcorebird.la CLEANFILES = corebird-resources.c corebird-1.7.4/examples/accountdialog.vala000066400000000000000000000003711324604713000206140ustar00rootroot00000000000000 void main (string[] args) { Gtk.init (ref args); Utils.load_custom_css (); Settings.init (); var acc = new Account (1337, "Some Screen Name", "Some Name"); var dialog = new AccountDialog (acc); dialog.show_all (); Gtk.main (); } corebird-1.7.4/examples/meson.build000066400000000000000000000004371324604713000173000ustar00rootroot00000000000000executable('tweetstates', 'tweetstates.vala', cb_resources, dependencies: cb_dep, vala_args: [ meson.source_root() + '/vapi/corebird-internal.vapi', meson.source_root() + '/vapi/rest-0.7.vapi', '--gresources=' + meson.source_root() + '/corebird.gresource.xml', ] ) corebird-1.7.4/examples/tweetstates.vala000066400000000000000000003414531324604713000203650ustar00rootroot00000000000000 Cb.Tweet parse_tweet (string input) { var parser = new Json.Parser (); try { parser.load_from_data (input); } catch (Error e) { error (e.message); } Cb.Tweet tweet = new Cb.Tweet (); tweet.load_from_json (parser.get_root (), 0, new GLib.DateTime.now_local ()); return tweet; } void main (string[] args) { Gtk.init (ref args); Settings.init (); Utils.load_custom_css (); Utils.load_custom_icons (); Utils.init_soup_session (); Twitter.get ().init (); var window = new Gtk.Window (); window.delete_event.connect (() => {Gtk.main_quit (); return true; }); var list = new Gtk.ListBox (); list.selection_mode = Gtk.SelectionMode.NONE; var list2 = new Gtk.ListBox (); list2.selection_mode = Gtk.SelectionMode.NONE; var list3 = new Gtk.ListBox (); list3.selection_mode = Gtk.SelectionMode.NONE; var scroller = new Gtk.ScrolledWindow (null, null); scroller.hscrollbar_policy = Gtk.PolicyType.NEVER; var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); box.homogeneous = true; // Ensure types new LazyMenuButton (); var fake_acount = new Account (1337, "baedert", "Foo Bar Baedert"); { // Normal tweet. var tweet = parse_tweet (NORMAL_TWEET); var row = new TweetListEntry (tweet, null, fake_acount); list.add (row); } { // Retweet var tweet = parse_tweet (RETWEET); var row = new TweetListEntry (tweet, null, fake_acount); list.add (row); } { // Normal, but with media attached var tweet = parse_tweet (NORMAL_WITH_MEDIA); var row = new TweetListEntry (tweet, null, fake_acount); list.add (row); } { // Normal quote var tweet = parse_tweet (NORMAL_QUOTE); var row = new TweetListEntry (tweet, null, fake_acount); list.add (row); } { // Quote with Media var tweet = parse_tweet (QUOTE_WITH_MEDIA); var row = new TweetListEntry (tweet, null, fake_acount); list2.add (row); } { // Retweet with media var tweet = parse_tweet (RETWEET_WITH_MEDIA); var row = new TweetListEntry (tweet, null, fake_acount); list2.add (row); } { // Empty (no text) tweet with media var tweet = parse_tweet (EMPTY_TWEET_WITH_MEDIA); var row = new TweetListEntry (tweet, null, fake_acount); list.add (row); } { // Retweet with media but no text var tweet = parse_tweet (EMPTY_RETWEET_WITH_MEDIA); var row = new TweetListEntry (tweet, null, fake_acount); list3.add (row); } { // Video + Thumbnail of that video, but should only show video var tweet = parse_tweet (VIDEO_AND_THUMBNAIL); var row = new TweetListEntry (tweet, null, fake_acount); list3.add (row); } { // Reply to ONE user. var tweet = parse_tweet (REPLY); var row = new TweetListEntry (tweet, null, fake_acount); list3.add (row); } { // Reply to TWO users. var tweet = parse_tweet (REPLY_TO_TWO); var row = new TweetListEntry (tweet, null, fake_acount); list3.add (row); } { // Reply to MORE users. var tweet = parse_tweet (REPLY2); var row = new TweetListEntry (tweet, null, fake_acount); list3.add (row); } { // Retweet of a tweet that replies to multiple users (2!) var tweet = parse_tweet (REPLY3); var row = new TweetListEntry (tweet, null, fake_acount); list3.add (row); } list.set_size_request (500, -1); list2.set_size_request (500, -1); list2.set_size_request (500, -1); box.add (list); box.add (list2); box.add (list3); scroller.add (box); window.add (scroller); window.show_all (); window.resize (1500, 900); Gtk.main (); } const string NORMAL_TWEET = """ { "created_at" : "Wed Aug 03 04:06:47 +0000 2016", "id" : 760688337610997764, "id_str" : "760688337610997764", "text" : "My dick was already out BEFORE Harambe died.", "truncated" : false, "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ ], "urls" : [ ] }, "source" : "Twitter Web Client", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 21369740, "id_str" : "21369740", "name" : "Rob DenBleyker", "screen_name" : "RobDenBleyker", "location" : "Dallas", "description" : "I'm not Rob Dyrdek. Don't follow me.", "url" : "https://t.co/5jh1OQTAOO", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/5jh1OQTAOO", "expanded_url" : "http://www.explosm.net", "display_url" : "explosm.net", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 126455, "friends_count" : 800, "listed_count" : 1284, "created_at" : "Fri Feb 20 03:26:24 +0000 2009", "favourites_count" : 1452, "utc_offset" : -18000, "time_zone" : "Central Time (US & Canada)", "geo_enabled" : true, "verified" : false, "statuses_count" : 8088, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "49585E", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/751256809873281024/-FuDkY2p_normal.jpg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/751256809873281024/-FuDkY2p_normal.jpg", "profile_link_color" : "0C90F5", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : false, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 111, "favorite_count" : 335, "favorited" : false, "retweeted" : false, "lang" : "en" } """; const string RETWEET = """ { "created_at" : "Tue Aug 02 17:25:04 +0000 2016", "id" : 760526843699007488, "id_str" : "760526843699007488", "text" : "RT @wilw: OH: \"All fucking dentists are in the pocket of big floss!\"", "truncated" : false, "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "wilw", "name" : "Wil Wheaton", "id" : 1183041, "id_str" : "1183041", "indices" : [ 3, 8 ] } ], "urls" : [ ] }, "source" : "Echofon", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 18948541, "id_str" : "18948541", "name" : "Seth MacFarlane", "screen_name" : "SethMacFarlane", "location" : "Los Angeles", "description" : "The Official Twitter Page of Seth MacFarlane - new album No One Ever Tells You available now on iTunes https://t.co/gLePVn5Mho", "url" : "https://t.co/o4miqWAHnW", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/o4miqWAHnW", "expanded_url" : "http://www.facebook.com/pages/Seth-MacFarlane/14105972607?ref=ts", "display_url" : "facebook.com/pages/Seth-Mac…", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ { "url" : "https://t.co/gLePVn5Mho", "expanded_url" : "http://itun.es/us/Vx9p-", "display_url" : "itun.es/us/Vx9p-", "indices" : [ 103, 126 ] } ] } }, "protected" : false, "followers_count" : 10520286, "friends_count" : 377, "listed_count" : 0, "created_at" : "Tue Jan 13 19:04:37 +0000 2009", "favourites_count" : 0, "utc_offset" : -25200, "time_zone" : "Pacific Time (US & Canada)", "geo_enabled" : false, "verified" : true, "statuses_count" : 5847, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : true, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/477598819715395585/g0lGqC_J_normal.jpeg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/477598819715395585/g0lGqC_J_normal.jpeg", "profile_link_color" : "0084B4", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : true, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "retweeted_status" : { "created_at" : "Tue Aug 02 17:18:45 +0000 2016", "id" : 760525254489890818, "id_str" : "760525254489890818", "text" : "OH: \"All fucking dentists are in the pocket of big floss!\"", "truncated" : false, "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ ], "urls" : [ ] }, "source" : "Twitter Web Client", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 1183041, "id_str" : "1183041", "name" : "Wil Wheaton", "screen_name" : "wilw", "location" : "Los Angeles", "description" : "Barrelslayer. Time Lord. Fake geek girl. On a good day I am charming as fuck.", "url" : "http://t.co/UAYYOhbijM", "entities" : { "url" : { "urls" : [ { "url" : "http://t.co/UAYYOhbijM", "expanded_url" : "http://wilwheaton.net/2009/02/what-to-expect-if-you-follow-me-on-twitter-or-how-im-going-to-disappoint-you-in-6-quick-steps/", "display_url" : "wilwheaton.net/2009/02/what-t…", "indices" : [ 0, 22 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 3050329, "friends_count" : 355, "listed_count" : 39190, "created_at" : "Wed Mar 14 21:25:33 +0000 2007", "favourites_count" : 557, "utc_offset" : -25200, "time_zone" : "Pacific Time (US & Canada)", "geo_enabled" : false, "verified" : true, "statuses_count" : 65696, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "022330", "profile_background_image_url" : "http://pbs.twimg.com/profile_background_images/871683408/62c85b46792dfe6bfd16420b71646cdb.png", "profile_background_image_url_https" : "https://pbs.twimg.com/profile_background_images/871683408/62c85b46792dfe6bfd16420b71646cdb.png", "profile_background_tile" : true, "profile_image_url" : "http://pbs.twimg.com/profile_images/660891140418236416/7zeCwT9K_normal.png", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/660891140418236416/7zeCwT9K_normal.png", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/1183041/1368668860", "profile_link_color" : "F6101E", "profile_sidebar_border_color" : "000000", "profile_sidebar_fill_color" : "C0DFEC", "profile_text_color" : "333333", "profile_use_background_image" : true, "has_extended_profile" : true, "default_profile" : false, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 249, "favorite_count" : 1418, "favorited" : false, "retweeted" : false, "lang" : "en" }, "is_quote_status" : false, "retweet_count" : 249, "favorite_count" : 0, "favorited" : false, "retweeted" : false, "lang" : "en" } """; const string NORMAL_WITH_MEDIA = """ { "created_at" : "Wed Aug 03 11:18:39 +0000 2016", "id" : 760797019908739072, "id_str" : "760797019908739072", "text" : "Someone went and ruined the last fun thing we had left https://t.co/a9Fc65NpIn", "truncated" : false, "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ ], "urls" : [ ], "media" : [ { "id" : 760796983409901568, "id_str" : "760796983409901568", "indices" : [ 55, 78 ], "media_url" : "http://pbs.twimg.com/media/Co7k01ZWAAAO7XA.jpg", "media_url_https" : "https://pbs.twimg.com/media/Co7k01ZWAAAO7XA.jpg", "url" : "https://t.co/a9Fc65NpIn", "display_url" : "pic.twitter.com/a9Fc65NpIn", "expanded_url" : "http://twitter.com/internetofshit/status/760797019908739072/photo/1", "type" : "photo", "sizes" : { "medium" : { "w" : 1200, "h" : 646, "resize" : "fit" }, "small" : { "w" : 680, "h" : 366, "resize" : "fit" }, "large" : { "w" : 1972, "h" : 1062, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" } } } ] }, "extended_entities" : { "media" : [ { "id" : 760796983409901568, "id_str" : "760796983409901568", "indices" : [ 55, 78 ], "media_url" : "http://pbs.twimg.com/media/Co7k01ZWAAAO7XA.jpg", "media_url_https" : "https://pbs.twimg.com/media/Co7k01ZWAAAO7XA.jpg", "url" : "https://t.co/a9Fc65NpIn", "display_url" : "pic.twitter.com/a9Fc65NpIn", "expanded_url" : "http://twitter.com/internetofshit/status/760797019908739072/photo/1", "type" : "photo", "sizes" : { "medium" : { "w" : 1200, "h" : 646, "resize" : "fit" }, "small" : { "w" : 680, "h" : 366, "resize" : "fit" }, "large" : { "w" : 1972, "h" : 1062, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" } } } ] }, "source" : "TweetDeck", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 3356531254, "id_str" : "3356531254", "name" : "Internet of Shit", "screen_name" : "internetofshit", "location" : "In your stuff", "description" : "Obviously the best thing to do is put a chip in it. Tips: internetofshit@gmail.com / Also on FB: https://t.co/VhThiGNgOo", "url" : null, "entities" : { "description" : { "urls" : [ { "url" : "https://t.co/VhThiGNgOo", "expanded_url" : "https://www.facebook.com/internetofshit", "display_url" : "facebook.com/internetofshit", "indices" : [ 97, 120 ] } ] } }, "protected" : false, "followers_count" : 126017, "friends_count" : 92, "listed_count" : 1630, "created_at" : "Fri Jul 03 09:04:06 +0000 2015", "favourites_count" : 2190, "utc_offset" : -25200, "time_zone" : "Pacific Time (US & Canada)", "geo_enabled" : true, "verified" : false, "statuses_count" : 2254, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/616895706150797312/ol4PeiHz_normal.png", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/616895706150797312/ol4PeiHz_normal.png", "profile_link_color" : "0084B4", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : true, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 103, "favorite_count" : 84, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "possibly_sensitive_appealable" : false, "lang" : "en" } """; const string NORMAL_QUOTE = """ { "created_at" : "Wed Aug 03 07:15:48 +0000 2016", "id" : 760735908287119360, "id_str" : "760735908287119360", "text" : "Gute Nachrichten zum Morgen: @EMMUREmusic haben die Aufnahmen zu ihrem neuen Album abgeschlossen! https://t.co/zGwh0FXMJi", "truncated" : false, "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "EMMUREmusic", "name" : "EMMURE", "id" : 42820699, "id_str" : "42820699", "indices" : [ 29, 41 ] } ], "urls" : [ { "url" : "https://t.co/zGwh0FXMJi", "expanded_url" : "https://twitter.com/FrankiePalmeri/status/760334690737790976", "display_url" : "twitter.com/FrankiePalmeri…", "indices" : [ 98, 121 ] } ] }, "source" : "Twitter Web Client", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 51725030, "id_str" : "51725030", "name" : "impericon_de", "screen_name" : "impericon_de", "location" : "Leipzig/ Germany", "description" : "check out http://t.co/RbUklLeWkv for further information", "url" : "http://t.co/RbUklLeWkv", "entities" : { "url" : { "urls" : [ { "url" : "http://t.co/RbUklLeWkv", "expanded_url" : "http://www.impericon.com", "display_url" : "impericon.com", "indices" : [ 0, 22 ] } ] }, "description" : { "urls" : [ { "url" : "http://t.co/RbUklLeWkv", "expanded_url" : "http://www.impericon.com", "display_url" : "impericon.com", "indices" : [ 10, 32 ] } ] } }, "protected" : false, "followers_count" : 8077, "friends_count" : 1226, "listed_count" : 46, "created_at" : "Sun Jun 28 13:07:50 +0000 2009", "favourites_count" : 2657, "utc_offset" : 7200, "time_zone" : "Berlin", "geo_enabled" : true, "verified" : false, "statuses_count" : 17025, "lang" : "de", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "611222", "profile_background_image_url" : "http://pbs.twimg.com/profile_background_images/868968985/73b34a4e0d724f8c8f61c06d76dcf925.jpeg", "profile_background_image_url_https" : "https://pbs.twimg.com/profile_background_images/868968985/73b34a4e0d724f8c8f61c06d76dcf925.jpeg", "profile_background_tile" : true, "profile_image_url" : "http://pbs.twimg.com/profile_images/1374777540/ic_germany_normal.png", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/1374777540/ic_germany_normal.png", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/51725030/1470217813", "profile_link_color" : "611222", "profile_sidebar_border_color" : "000000", "profile_sidebar_fill_color" : "DDFFCC", "profile_text_color" : "333333", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : true, "quoted_status_id" : 760334690737790976, "quoted_status_id_str" : "760334690737790976", "quoted_status" : { "created_at" : "Tue Aug 02 04:41:31 +0000 2016", "id" : 760334690737790976, "id_str" : "760334690737790976", "text" : "Finished tracking the record. 🖒", "truncated" : false, "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ ], "urls" : [ ] }, "source" : "Twitter for Android", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 208089519, "id_str" : "208089519", "name" : "⛧", "screen_name" : "FrankiePalmeri", "location" : "World Warrior ", "description" : "シャドルー Best known as the singer of @EMMUREmusic\nFor booking/features-GodOfRue@Gmail.com\n#MVC2", "url" : null, "entities" : { "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 6843, "friends_count" : 0, "listed_count" : 17, "created_at" : "Tue Oct 26 16:20:47 +0000 2010", "favourites_count" : 6444, "utc_offset" : -18000, "time_zone" : "Central Time (US & Canada)", "geo_enabled" : true, "verified" : true, "statuses_count" : 7346, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/744425940789276672/qOmnwRUv_normal.jpg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/744425940789276672/qOmnwRUv_normal.jpg", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/208089519/1466846528", "profile_link_color" : "0084B4", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : true, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : { "id" : "3b77caf94bfc81fe", "url" : "https://api.twitter.com/1.1/geo/id/3b77caf94bfc81fe.json", "place_type" : "city", "name" : "Los Angeles", "full_name" : "Los Angeles, CA", "country_code" : "US", "country" : "United States", "contained_within" : [ ], "bounding_box" : { "type" : "Polygon", "coordinates" : [ [ [ -118.668404, 33.704537999999999 ], [ -118.15540900000001, 33.704537999999999 ], [ -118.15540900000001, 34.337040999999999 ], [ -118.668404, 34.337040999999999 ] ] ] }, "attributes" : { } }, "contributors" : null, "is_quote_status" : false, "retweet_count" : 38, "favorite_count" : 136, "favorited" : false, "retweeted" : false, "lang" : "en" }, "retweet_count" : 1, "favorite_count" : 7, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "possibly_sensitive_appealable" : false, "lang" : "de" } """; const string QUOTE_WITH_MEDIA = """ { "created_at" : "Wed Aug 03 09:48:12 +0000 2016", "id" : 760774260315004928, "id_str" : "760774260315004928", "text" : "Wir sind am Wochenende übrigens wieder beim Wacken Open Air am Start - wer noch? https://t.co/xEEtAO37jF", "truncated" : false, "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ ], "urls" : [ { "url" : "https://t.co/xEEtAO37jF", "expanded_url" : "https://twitter.com/Wacken/status/760774020631556096", "display_url" : "twitter.com/Wacken/status/…", "indices" : [ 81, 104 ] } ] }, "source" : "Twitter Web Client", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 51725030, "id_str" : "51725030", "name" : "impericon_de", "screen_name" : "impericon_de", "location" : "Leipzig/ Germany", "description" : "check out http://t.co/RbUklLeWkv for further information", "url" : "http://t.co/RbUklLeWkv", "entities" : { "url" : { "urls" : [ { "url" : "http://t.co/RbUklLeWkv", "expanded_url" : "http://www.impericon.com", "display_url" : "impericon.com", "indices" : [ 0, 22 ] } ] }, "description" : { "urls" : [ { "url" : "http://t.co/RbUklLeWkv", "expanded_url" : "http://www.impericon.com", "display_url" : "impericon.com", "indices" : [ 10, 32 ] } ] } }, "protected" : false, "followers_count" : 8077, "friends_count" : 1226, "listed_count" : 46, "created_at" : "Sun Jun 28 13:07:50 +0000 2009", "favourites_count" : 2657, "utc_offset" : 7200, "time_zone" : "Berlin", "geo_enabled" : true, "verified" : false, "statuses_count" : 17025, "lang" : "de", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "611222", "profile_background_image_url" : "http://pbs.twimg.com/profile_background_images/868968985/73b34a4e0d724f8c8f61c06d76dcf925.jpeg", "profile_background_image_url_https" : "https://pbs.twimg.com/profile_background_images/868968985/73b34a4e0d724f8c8f61c06d76dcf925.jpeg", "profile_background_tile" : true, "profile_image_url" : "http://pbs.twimg.com/profile_images/1374777540/ic_germany_normal.png", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/1374777540/ic_germany_normal.png", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/51725030/1470217813", "profile_link_color" : "611222", "profile_sidebar_border_color" : "000000", "profile_sidebar_fill_color" : "DDFFCC", "profile_text_color" : "333333", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : true, "quoted_status_id" : 760774020631556096, "quoted_status_id_str" : "760774020631556096", "quoted_status" : { "created_at" : "Wed Aug 03 09:47:15 +0000 2016", "id" : 760774020631556096, "id_str" : "760774020631556096", "text" : "Business as usual. Work on main stages continues. All is well. #Wacken https://t.co/6oNpNgSFmx", "truncated" : false, "entities" : { "hashtags" : [ { "text" : "Wacken", "indices" : [ 64, 71 ] } ], "symbols" : [ ], "user_mentions" : [ ], "urls" : [ ], "media" : [ { "id" : 760773998955421696, "id_str" : "760773998955421696", "indices" : [ 72, 95 ], "media_url" : "http://pbs.twimg.com/media/Co7P69oXYAAcTYR.jpg", "media_url_https" : "https://pbs.twimg.com/media/Co7P69oXYAAcTYR.jpg", "url" : "https://t.co/6oNpNgSFmx", "display_url" : "pic.twitter.com/6oNpNgSFmx", "expanded_url" : "http://twitter.com/Wacken/status/760774020631556096/photo/1", "type" : "photo", "sizes" : { "medium" : { "w" : 1200, "h" : 675, "resize" : "fit" }, "small" : { "w" : 680, "h" : 383, "resize" : "fit" }, "large" : { "w" : 2048, "h" : 1152, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" } } } ] }, "extended_entities" : { "media" : [ { "id" : 760773998955421696, "id_str" : "760773998955421696", "indices" : [ 72, 95 ], "media_url" : "http://pbs.twimg.com/media/Co7P69oXYAAcTYR.jpg", "media_url_https" : "https://pbs.twimg.com/media/Co7P69oXYAAcTYR.jpg", "url" : "https://t.co/6oNpNgSFmx", "display_url" : "pic.twitter.com/6oNpNgSFmx", "expanded_url" : "http://twitter.com/Wacken/status/760774020631556096/photo/1", "type" : "photo", "sizes" : { "medium" : { "w" : 1200, "h" : 675, "resize" : "fit" }, "small" : { "w" : 680, "h" : 383, "resize" : "fit" }, "large" : { "w" : 2048, "h" : 1152, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" } } } ] }, "source" : "Twitter for Android", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 15475088, "id_str" : "15475088", "name" : "Wacken Open Air", "screen_name" : "Wacken", "location" : "Wacken, Germany", "description" : "Welcome to the official W:O:A Twitter account! Impressum: https://t.co/0Nvmi1UOVI", "url" : "http://t.co/eklJ4O4IqD", "entities" : { "url" : { "urls" : [ { "url" : "http://t.co/eklJ4O4IqD", "expanded_url" : "http://www.wacken.com/", "display_url" : "wacken.com", "indices" : [ 0, 22 ] } ] }, "description" : { "urls" : [ { "url" : "https://t.co/0Nvmi1UOVI", "expanded_url" : "http://bit.ly/1xx5ODO", "display_url" : "bit.ly/1xx5ODO", "indices" : [ 60, 83 ] } ] } }, "protected" : false, "followers_count" : 61074, "friends_count" : 259, "listed_count" : 602, "created_at" : "Thu Jul 17 22:46:52 +0000 2008", "favourites_count" : 314, "utc_offset" : 7200, "time_zone" : "Ljubljana", "geo_enabled" : true, "verified" : false, "statuses_count" : 3660, "lang" : "de", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "000000", "profile_background_image_url" : "http://pbs.twimg.com/profile_background_images/436070844293345280/3EWufvsr.jpeg", "profile_background_image_url_https" : "https://pbs.twimg.com/profile_background_images/436070844293345280/3EWufvsr.jpeg", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/627856569699598340/OsQ1UOLl_normal.png", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/627856569699598340/OsQ1UOLl_normal.png", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/15475088/1438527965", "profile_link_color" : "5BD006", "profile_sidebar_border_color" : "000000", "profile_sidebar_fill_color" : "EFEFEF", "profile_text_color" : "333333", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : { "id" : "b442982971a0c2b5", "url" : "https://api.twitter.com/1.1/geo/id/b442982971a0c2b5.json", "place_type" : "city", "name" : "Wacken", "full_name" : "Wacken, Deutschland", "country_code" : "DE", "country" : "Germany", "contained_within" : [ ], "bounding_box" : { "type" : "Polygon", "coordinates" : [ [ [ 9.3435539999999992, 54.009207000000004 ], [ 9.4074019999999994, 54.009207000000004 ], [ 9.4074019999999994, 54.038155000000003 ], [ 9.3435539999999992, 54.038155000000003 ] ] ] }, "attributes" : { } }, "contributors" : null, "is_quote_status" : false, "retweet_count" : 7, "favorite_count" : 15, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "possibly_sensitive_appealable" : false, "lang" : "en" }, "retweet_count" : 0, "favorite_count" : 0, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "possibly_sensitive_appealable" : false, "lang" : "de" } """; const string RETWEET_WITH_MEDIA = """ { "created_at" : "Wed Aug 03 01:22:42 +0000 2016", "id" : 760647046835544064, "id_str" : "760647046835544064", "text" : "RT @shutupmikeginn: Hey @ScottAdamsSays, @eedrk & I are big fans so we recut Dilbert to deal with contemporary social issues! Please RT! ht…", "truncated" : false, "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "shutupmikeginn", "name" : "shut up, mike", "id" : 246394886, "id_str" : "246394886", "indices" : [ 3, 18 ] }, { "screen_name" : "ScottAdamsSays", "name" : "Scott Adams", "id" : 2853461537, "id_str" : "2853461537", "indices" : [ 24, 39 ] }, { "screen_name" : "eedrk", "name" : "derek", "id" : 1350600582, "id_str" : "1350600582", "indices" : [ 41, 47 ] } ], "urls" : [ ], "media" : [ { "id" : 760627464456462336, "id_str" : "760627464456462336", "indices" : [ 143, 144 ], "media_url" : "http://pbs.twimg.com/ext_tw_video_thumb/760627464456462336/pu/img/ZmNeZzX7qcwzsuon.jpg", "media_url_https" : "https://pbs.twimg.com/ext_tw_video_thumb/760627464456462336/pu/img/ZmNeZzX7qcwzsuon.jpg", "url" : "https://t.co/Dth5YBHXeu", "display_url" : "pic.twitter.com/Dth5YBHXeu", "expanded_url" : "http://twitter.com/shutupmikeginn/status/760628917837312000/video/1", "type" : "photo", "sizes" : { "medium" : { "w" : 600, "h" : 411, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" }, "large" : { "w" : 700, "h" : 480, "resize" : "fit" }, "small" : { "w" : 340, "h" : 233, "resize" : "fit" } }, "source_status_id" : 760628917837312000, "source_status_id_str" : "760628917837312000", "source_user_id" : 246394886, "source_user_id_str" : "246394886" } ] }, "extended_entities" : { "media" : [ { "id" : 760627464456462336, "id_str" : "760627464456462336", "indices" : [ 143, 144 ], "media_url" : "http://pbs.twimg.com/ext_tw_video_thumb/760627464456462336/pu/img/ZmNeZzX7qcwzsuon.jpg", "media_url_https" : "https://pbs.twimg.com/ext_tw_video_thumb/760627464456462336/pu/img/ZmNeZzX7qcwzsuon.jpg", "url" : "https://t.co/Dth5YBHXeu", "display_url" : "pic.twitter.com/Dth5YBHXeu", "expanded_url" : "http://twitter.com/shutupmikeginn/status/760628917837312000/video/1", "type" : "video", "sizes" : { "medium" : { "w" : 600, "h" : 411, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" }, "large" : { "w" : 700, "h" : 480, "resize" : "fit" }, "small" : { "w" : 340, "h" : 233, "resize" : "fit" } }, "source_status_id" : 760628917837312000, "source_status_id_str" : "760628917837312000", "source_user_id" : 246394886, "source_user_id_str" : "246394886", "video_info" : { "aspect_ratio" : [ 35, 24 ], "duration_millis" : 50017, "variants" : [ { "content_type" : "application/dash+xml", "url" : "https://video.twimg.com/ext_tw_video/760627464456462336/pu/pl/WgyGZ2bR67CZKbIe.mpd" }, { "bitrate" : 320000, "content_type" : "video/mp4", "url" : "https://video.twimg.com/ext_tw_video/760627464456462336/pu/vid/262x180/bMwQb2LGoymiuNBF.mp4" }, { "content_type" : "application/x-mpegURL", "url" : "https://video.twimg.com/ext_tw_video/760627464456462336/pu/pl/WgyGZ2bR67CZKbIe.m3u8" }, { "bitrate" : 832000, "content_type" : "video/mp4", "url" : "https://video.twimg.com/ext_tw_video/760627464456462336/pu/vid/524x360/akoT29AzRB-K7Tvf.mp4" } ] }, "additional_media_info" : { "monetizable" : false, "source_user" : { "id" : 246394886, "id_str" : "246394886", "name" : "shut up, mike", "screen_name" : "shutupmikeginn", "location" : "Los Angeles, CA", "description" : "writer (left handed) // shutupmikeginn @ gmail . com // @midnight", "url" : "https://t.co/JLpcO66Txj", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/JLpcO66Txj", "expanded_url" : "http://www.shutupmikeginn.com", "display_url" : "shutupmikeginn.com", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 155598, "friends_count" : 757, "listed_count" : 1675, "created_at" : "Wed Feb 02 18:21:51 +0000 2011", "favourites_count" : 58329, "utc_offset" : null, "time_zone" : null, "geo_enabled" : false, "verified" : false, "statuses_count" : 9001, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "000000", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme15/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme15/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/523668808020422656/szD5CZyb_normal.jpeg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/523668808020422656/szD5CZyb_normal.jpeg", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/246394886/1460523073", "profile_link_color" : "4A913C", "profile_sidebar_border_color" : "000000", "profile_sidebar_fill_color" : "000000", "profile_text_color" : "000000", "profile_use_background_image" : false, "has_extended_profile" : true, "default_profile" : false, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false } } } ] }, "source" : "Twitter for iPhone", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 21369740, "id_str" : "21369740", "name" : "Rob DenBleyker", "screen_name" : "RobDenBleyker", "location" : "Dallas", "description" : "I'm not Rob Dyrdek. Don't follow me.", "url" : "https://t.co/5jh1OQTAOO", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/5jh1OQTAOO", "expanded_url" : "http://www.explosm.net", "display_url" : "explosm.net", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 126457, "friends_count" : 800, "listed_count" : 1284, "created_at" : "Fri Feb 20 03:26:24 +0000 2009", "favourites_count" : 1452, "utc_offset" : -18000, "time_zone" : "Central Time (US & Canada)", "geo_enabled" : true, "verified" : false, "statuses_count" : 8088, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "49585E", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/751256809873281024/-FuDkY2p_normal.jpg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/751256809873281024/-FuDkY2p_normal.jpg", "profile_link_color" : "0C90F5", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : false, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "retweeted_status" : { "created_at" : "Wed Aug 03 00:10:40 +0000 2016", "id" : 760628917837312000, "id_str" : "760628917837312000", "text" : "Hey @ScottAdamsSays, @eedrk & I are big fans so we recut Dilbert to deal with contemporary social issues! Please RT! https://t.co/Dth5YBHXeu", "truncated" : false, "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "ScottAdamsSays", "name" : "Scott Adams", "id" : 2853461537, "id_str" : "2853461537", "indices" : [ 4, 19 ] }, { "screen_name" : "eedrk", "name" : "derek", "id" : 1350600582, "id_str" : "1350600582", "indices" : [ 21, 27 ] } ], "urls" : [ ], "media" : [ { "id" : 760627464456462336, "id_str" : "760627464456462336", "indices" : [ 121, 144 ], "media_url" : "http://pbs.twimg.com/ext_tw_video_thumb/760627464456462336/pu/img/ZmNeZzX7qcwzsuon.jpg", "media_url_https" : "https://pbs.twimg.com/ext_tw_video_thumb/760627464456462336/pu/img/ZmNeZzX7qcwzsuon.jpg", "url" : "https://t.co/Dth5YBHXeu", "display_url" : "pic.twitter.com/Dth5YBHXeu", "expanded_url" : "http://twitter.com/shutupmikeginn/status/760628917837312000/video/1", "type" : "photo", "sizes" : { "medium" : { "w" : 600, "h" : 411, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" }, "large" : { "w" : 700, "h" : 480, "resize" : "fit" }, "small" : { "w" : 340, "h" : 233, "resize" : "fit" } } } ] }, "extended_entities" : { "media" : [ { "id" : 760627464456462336, "id_str" : "760627464456462336", "indices" : [ 121, 144 ], "media_url" : "http://pbs.twimg.com/ext_tw_video_thumb/760627464456462336/pu/img/ZmNeZzX7qcwzsuon.jpg", "media_url_https" : "https://pbs.twimg.com/ext_tw_video_thumb/760627464456462336/pu/img/ZmNeZzX7qcwzsuon.jpg", "url" : "https://t.co/Dth5YBHXeu", "display_url" : "pic.twitter.com/Dth5YBHXeu", "expanded_url" : "http://twitter.com/shutupmikeginn/status/760628917837312000/video/1", "type" : "video", "sizes" : { "medium" : { "w" : 600, "h" : 411, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" }, "large" : { "w" : 700, "h" : 480, "resize" : "fit" }, "small" : { "w" : 340, "h" : 233, "resize" : "fit" } }, "video_info" : { "aspect_ratio" : [ 35, 24 ], "duration_millis" : 50017, "variants" : [ { "content_type" : "application/dash+xml", "url" : "https://video.twimg.com/ext_tw_video/760627464456462336/pu/pl/WgyGZ2bR67CZKbIe.mpd" }, { "bitrate" : 320000, "content_type" : "video/mp4", "url" : "https://video.twimg.com/ext_tw_video/760627464456462336/pu/vid/262x180/bMwQb2LGoymiuNBF.mp4" }, { "content_type" : "application/x-mpegURL", "url" : "https://video.twimg.com/ext_tw_video/760627464456462336/pu/pl/WgyGZ2bR67CZKbIe.m3u8" }, { "bitrate" : 832000, "content_type" : "video/mp4", "url" : "https://video.twimg.com/ext_tw_video/760627464456462336/pu/vid/524x360/akoT29AzRB-K7Tvf.mp4" } ] }, "additional_media_info" : { "monetizable" : false } } ] }, "source" : "Twitter Web Client", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 246394886, "id_str" : "246394886", "name" : "shut up, mike", "screen_name" : "shutupmikeginn", "location" : "Los Angeles, CA", "description" : "writer (left handed) // shutupmikeginn @ gmail . com // @midnight", "url" : "https://t.co/JLpcO66Txj", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/JLpcO66Txj", "expanded_url" : "http://www.shutupmikeginn.com", "display_url" : "shutupmikeginn.com", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 155598, "friends_count" : 757, "listed_count" : 1675, "created_at" : "Wed Feb 02 18:21:51 +0000 2011", "favourites_count" : 58329, "utc_offset" : null, "time_zone" : null, "geo_enabled" : false, "verified" : false, "statuses_count" : 9001, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "000000", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme15/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme15/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/523668808020422656/szD5CZyb_normal.jpeg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/523668808020422656/szD5CZyb_normal.jpeg", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/246394886/1460523073", "profile_link_color" : "4A913C", "profile_sidebar_border_color" : "000000", "profile_sidebar_fill_color" : "000000", "profile_text_color" : "000000", "profile_use_background_image" : false, "has_extended_profile" : true, "default_profile" : false, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 128, "favorite_count" : 523, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "possibly_sensitive_appealable" : false, "lang" : "en" }, "is_quote_status" : false, "retweet_count" : 128, "favorite_count" : 0, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "possibly_sensitive_appealable" : false, "lang" : "en" } """; const string EMPTY_TWEET_WITH_MEDIA = """ { "created_at" : "Wed Aug 03 02:48:12 +0000 2016", "id" : 760668562323189761, "id_str" : "760668562323189761", "text" : "https://t.co/ffI4jeND7m", "truncated" : false, "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ ], "urls" : [ ], "media" : [ { "id" : 760668555239010304, "id_str" : "760668555239010304", "indices" : [ 0, 23 ], "media_url" : "http://pbs.twimg.com/media/Co5wBVLUsAASfCW.jpg", "media_url_https" : "https://pbs.twimg.com/media/Co5wBVLUsAASfCW.jpg", "url" : "https://t.co/ffI4jeND7m", "display_url" : "pic.twitter.com/ffI4jeND7m", "expanded_url" : "http://twitter.com/RobDenBleyker/status/760668562323189761/photo/1", "type" : "photo", "sizes" : { "large" : { "w" : 820, "h" : 300, "resize" : "fit" }, "small" : { "w" : 680, "h" : 249, "resize" : "fit" }, "medium" : { "w" : 820, "h" : 300, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" } } } ] }, "extended_entities" : { "media" : [ { "id" : 760668555239010304, "id_str" : "760668555239010304", "indices" : [ 0, 23 ], "media_url" : "http://pbs.twimg.com/media/Co5wBVLUsAASfCW.jpg", "media_url_https" : "https://pbs.twimg.com/media/Co5wBVLUsAASfCW.jpg", "url" : "https://t.co/ffI4jeND7m", "display_url" : "pic.twitter.com/ffI4jeND7m", "expanded_url" : "http://twitter.com/RobDenBleyker/status/760668562323189761/photo/1", "type" : "photo", "sizes" : { "large" : { "w" : 820, "h" : 300, "resize" : "fit" }, "small" : { "w" : 680, "h" : 249, "resize" : "fit" }, "medium" : { "w" : 820, "h" : 300, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" } } } ] }, "source" : "Twitter Web Client", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 21369740, "id_str" : "21369740", "name" : "Rob DenBleyker", "screen_name" : "RobDenBleyker", "location" : "Dallas", "description" : "I'm not Rob Dyrdek. Don't follow me.", "url" : "https://t.co/5jh1OQTAOO", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/5jh1OQTAOO", "expanded_url" : "http://www.explosm.net", "display_url" : "explosm.net", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 126457, "friends_count" : 800, "listed_count" : 1284, "created_at" : "Fri Feb 20 03:26:24 +0000 2009", "favourites_count" : 1452, "utc_offset" : -18000, "time_zone" : "Central Time (US & Canada)", "geo_enabled" : true, "verified" : false, "statuses_count" : 8088, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "49585E", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/751256809873281024/-FuDkY2p_normal.jpg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/751256809873281024/-FuDkY2p_normal.jpg", "profile_link_color" : "0C90F5", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : false, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 90, "favorite_count" : 238, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "possibly_sensitive_appealable" : false, "lang" : "und" } """; const string EMPTY_RETWEET_WITH_MEDIA = """ { "created_at" : "Wed Aug 03 01:22:42 +0000 2016", "id" : 760647046835544064, "id_str" : "760647046835544064", "text" : "RT @shutupmikeginn: Hey @ScottAdamsSays, @eedrk & I are big fans so we recut Dilbert to deal with contemporary social issues! Please RT! ht…", "truncated" : false, "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "shutupmikeginn", "name" : "shut up, mike", "id" : 246394886, "id_str" : "246394886", "indices" : [ 3, 18 ] }, { "screen_name" : "ScottAdamsSays", "name" : "Scott Adams", "id" : 2853461537, "id_str" : "2853461537", "indices" : [ 24, 39 ] }, { "screen_name" : "eedrk", "name" : "derek", "id" : 1350600582, "id_str" : "1350600582", "indices" : [ 41, 47 ] } ], "urls" : [ ], "media" : [ { "id" : 760627464456462336, "id_str" : "760627464456462336", "indices" : [ 143, 144 ], "media_url" : "http://pbs.twimg.com/ext_tw_video_thumb/760627464456462336/pu/img/ZmNeZzX7qcwzsuon.jpg", "media_url_https" : "https://pbs.twimg.com/ext_tw_video_thumb/760627464456462336/pu/img/ZmNeZzX7qcwzsuon.jpg", "url" : "https://t.co/Dth5YBHXeu", "display_url" : "pic.twitter.com/Dth5YBHXeu", "expanded_url" : "http://twitter.com/shutupmikeginn/status/760628917837312000/video/1", "type" : "photo", "sizes" : { "medium" : { "w" : 600, "h" : 411, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" }, "large" : { "w" : 700, "h" : 480, "resize" : "fit" }, "small" : { "w" : 340, "h" : 233, "resize" : "fit" } }, "source_status_id" : 760628917837312000, "source_status_id_str" : "760628917837312000", "source_user_id" : 246394886, "source_user_id_str" : "246394886" } ] }, "extended_entities" : { "media" : [ { "id" : 760627464456462336, "id_str" : "760627464456462336", "indices" : [ 143, 144 ], "media_url" : "http://pbs.twimg.com/ext_tw_video_thumb/760627464456462336/pu/img/ZmNeZzX7qcwzsuon.jpg", "media_url_https" : "https://pbs.twimg.com/ext_tw_video_thumb/760627464456462336/pu/img/ZmNeZzX7qcwzsuon.jpg", "url" : "https://t.co/Dth5YBHXeu", "display_url" : "pic.twitter.com/Dth5YBHXeu", "expanded_url" : "http://twitter.com/shutupmikeginn/status/760628917837312000/video/1", "type" : "video", "sizes" : { "medium" : { "w" : 600, "h" : 411, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" }, "large" : { "w" : 700, "h" : 480, "resize" : "fit" }, "small" : { "w" : 340, "h" : 233, "resize" : "fit" } }, "source_status_id" : 760628917837312000, "source_status_id_str" : "760628917837312000", "source_user_id" : 246394886, "source_user_id_str" : "246394886", "video_info" : { "aspect_ratio" : [ 35, 24 ], "duration_millis" : 50017, "variants" : [ { "content_type" : "application/dash+xml", "url" : "https://video.twimg.com/ext_tw_video/760627464456462336/pu/pl/WgyGZ2bR67CZKbIe.mpd" }, { "bitrate" : 320000, "content_type" : "video/mp4", "url" : "https://video.twimg.com/ext_tw_video/760627464456462336/pu/vid/262x180/bMwQb2LGoymiuNBF.mp4" }, { "content_type" : "application/x-mpegURL", "url" : "https://video.twimg.com/ext_tw_video/760627464456462336/pu/pl/WgyGZ2bR67CZKbIe.m3u8" }, { "bitrate" : 832000, "content_type" : "video/mp4", "url" : "https://video.twimg.com/ext_tw_video/760627464456462336/pu/vid/524x360/akoT29AzRB-K7Tvf.mp4" } ] }, "additional_media_info" : { "monetizable" : false, "source_user" : { "id" : 246394886, "id_str" : "246394886", "name" : "shut up, mike", "screen_name" : "shutupmikeginn", "location" : "Los Angeles, CA", "description" : "writer (left handed) // shutupmikeginn @ gmail . com // @midnight", "url" : "https://t.co/JLpcO66Txj", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/JLpcO66Txj", "expanded_url" : "http://www.shutupmikeginn.com", "display_url" : "shutupmikeginn.com", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 155598, "friends_count" : 757, "listed_count" : 1675, "created_at" : "Wed Feb 02 18:21:51 +0000 2011", "favourites_count" : 58329, "utc_offset" : null, "time_zone" : null, "geo_enabled" : false, "verified" : false, "statuses_count" : 9001, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "000000", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme15/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme15/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/523668808020422656/szD5CZyb_normal.jpeg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/523668808020422656/szD5CZyb_normal.jpeg", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/246394886/1460523073", "profile_link_color" : "4A913C", "profile_sidebar_border_color" : "000000", "profile_sidebar_fill_color" : "000000", "profile_text_color" : "000000", "profile_use_background_image" : false, "has_extended_profile" : true, "default_profile" : false, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false } } } ] }, "source" : "Twitter for iPhone", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 21369740, "id_str" : "21369740", "name" : "Rob DenBleyker", "screen_name" : "RobDenBleyker", "location" : "Dallas", "description" : "I'm not Rob Dyrdek. Don't follow me.", "url" : "https://t.co/5jh1OQTAOO", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/5jh1OQTAOO", "expanded_url" : "http://www.explosm.net", "display_url" : "explosm.net", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 126457, "friends_count" : 800, "listed_count" : 1284, "created_at" : "Fri Feb 20 03:26:24 +0000 2009", "favourites_count" : 1452, "utc_offset" : -18000, "time_zone" : "Central Time (US & Canada)", "geo_enabled" : true, "verified" : false, "statuses_count" : 8088, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "49585E", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/751256809873281024/-FuDkY2p_normal.jpg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/751256809873281024/-FuDkY2p_normal.jpg", "profile_link_color" : "0C90F5", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : false, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "retweeted_status" : { "created_at" : "Wed Aug 03 00:10:40 +0000 2016", "id" : 760628917837312000, "id_str" : "760628917837312000", "text" : "", "truncated" : false, "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "ScottAdamsSays", "name" : "Scott Adams", "id" : 2853461537, "id_str" : "2853461537", "indices" : [ 4, 19 ] }, { "screen_name" : "eedrk", "name" : "derek", "id" : 1350600582, "id_str" : "1350600582", "indices" : [ 21, 27 ] } ], "urls" : [ ], "media" : [ { "id" : 760627464456462336, "id_str" : "760627464456462336", "indices" : [ 121, 144 ], "media_url" : "http://pbs.twimg.com/ext_tw_video_thumb/760627464456462336/pu/img/ZmNeZzX7qcwzsuon.jpg", "media_url_https" : "https://pbs.twimg.com/ext_tw_video_thumb/760627464456462336/pu/img/ZmNeZzX7qcwzsuon.jpg", "url" : "https://t.co/Dth5YBHXeu", "display_url" : "pic.twitter.com/Dth5YBHXeu", "expanded_url" : "http://twitter.com/shutupmikeginn/status/760628917837312000/video/1", "type" : "photo", "sizes" : { "medium" : { "w" : 600, "h" : 411, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" }, "large" : { "w" : 700, "h" : 480, "resize" : "fit" }, "small" : { "w" : 340, "h" : 233, "resize" : "fit" } } } ] }, "extended_entities" : { "media" : [ { "id" : 760627464456462336, "id_str" : "760627464456462336", "indices" : [ 121, 144 ], "media_url" : "http://pbs.twimg.com/ext_tw_video_thumb/760627464456462336/pu/img/ZmNeZzX7qcwzsuon.jpg", "media_url_https" : "https://pbs.twimg.com/ext_tw_video_thumb/760627464456462336/pu/img/ZmNeZzX7qcwzsuon.jpg", "url" : "https://t.co/Dth5YBHXeu", "display_url" : "pic.twitter.com/Dth5YBHXeu", "expanded_url" : "http://twitter.com/shutupmikeginn/status/760628917837312000/video/1", "type" : "video", "sizes" : { "medium" : { "w" : 600, "h" : 411, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" }, "large" : { "w" : 700, "h" : 480, "resize" : "fit" }, "small" : { "w" : 340, "h" : 233, "resize" : "fit" } }, "video_info" : { "aspect_ratio" : [ 35, 24 ], "duration_millis" : 50017, "variants" : [ { "content_type" : "application/dash+xml", "url" : "https://video.twimg.com/ext_tw_video/760627464456462336/pu/pl/WgyGZ2bR67CZKbIe.mpd" }, { "bitrate" : 320000, "content_type" : "video/mp4", "url" : "https://video.twimg.com/ext_tw_video/760627464456462336/pu/vid/262x180/bMwQb2LGoymiuNBF.mp4" }, { "content_type" : "application/x-mpegURL", "url" : "https://video.twimg.com/ext_tw_video/760627464456462336/pu/pl/WgyGZ2bR67CZKbIe.m3u8" }, { "bitrate" : 832000, "content_type" : "video/mp4", "url" : "https://video.twimg.com/ext_tw_video/760627464456462336/pu/vid/524x360/akoT29AzRB-K7Tvf.mp4" } ] }, "additional_media_info" : { "monetizable" : false } } ] }, "source" : "Twitter Web Client", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 246394886, "id_str" : "246394886", "name" : "shut up, mike", "screen_name" : "shutupmikeginn", "location" : "Los Angeles, CA", "description" : "writer (left handed) // shutupmikeginn @ gmail . com // @midnight", "url" : "https://t.co/JLpcO66Txj", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/JLpcO66Txj", "expanded_url" : "http://www.shutupmikeginn.com", "display_url" : "shutupmikeginn.com", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 155598, "friends_count" : 757, "listed_count" : 1675, "created_at" : "Wed Feb 02 18:21:51 +0000 2011", "favourites_count" : 58329, "utc_offset" : null, "time_zone" : null, "geo_enabled" : false, "verified" : false, "statuses_count" : 9001, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "000000", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme15/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme15/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/523668808020422656/szD5CZyb_normal.jpeg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/523668808020422656/szD5CZyb_normal.jpeg", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/246394886/1460523073", "profile_link_color" : "4A913C", "profile_sidebar_border_color" : "000000", "profile_sidebar_fill_color" : "000000", "profile_text_color" : "000000", "profile_use_background_image" : false, "has_extended_profile" : true, "default_profile" : false, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 128, "favorite_count" : 523, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "possibly_sensitive_appealable" : false, "lang" : "en" }, "is_quote_status" : false, "retweet_count" : 128, "favorite_count" : 0, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "possibly_sensitive_appealable" : false, "lang" : "en" } """; const string VIDEO_AND_THUMBNAIL = """ { "created_at" : "Sat Nov 05 19:41:06 +0000 2016", "id" : 794987926157410308, "id_str" : "794987926157410308", "full_text" : "RT @mileysdrive: Truly iconic https://t.co/8gdSKk0Zx2", "truncated" : false, "display_text_range" : [ 0, 53 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "mileysdrive", "name" : "NSA agent jim", "id" : 2624180621, "id_str" : "2624180621", "indices" : [ 3, 15 ] } ], "urls" : [ ], "media" : [ { "id" : 793976804549754880, "id_str" : "793976804549754880", "indices" : [ 30, 53 ], "media_url" : "http://pbs.twimg.com/media/CwTHbn3XgAEcDEr.jpg", "media_url_https" : "https://pbs.twimg.com/media/CwTHbn3XgAEcDEr.jpg", "url" : "https://t.co/8gdSKk0Zx2", "display_url" : "pic.twitter.com/8gdSKk0Zx2", "expanded_url" : "https://twitter.com/LateNightSeth/status/793982502008397825/video/1", "type" : "photo", "sizes" : { "large" : { "w" : 1273, "h" : 715, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" }, "small" : { "w" : 680, "h" : 382, "resize" : "fit" }, "medium" : { "w" : 1200, "h" : 674, "resize" : "fit" } }, "source_status_id" : 793982502008397825, "source_status_id_str" : "793982502008397825", "source_user_id" : 570290656, "source_user_id_str" : "570290656" } ] }, "extended_entities" : { "media" : [ { "id" : 793976804549754880, "id_str" : "793976804549754880", "indices" : [ 30, 53 ], "media_url" : "http://pbs.twimg.com/media/CwTHbn3XgAEcDEr.jpg", "media_url_https" : "https://pbs.twimg.com/media/CwTHbn3XgAEcDEr.jpg", "url" : "https://t.co/8gdSKk0Zx2", "display_url" : "pic.twitter.com/8gdSKk0Zx2", "expanded_url" : "https://twitter.com/LateNightSeth/status/793982502008397825/video/1", "type" : "video", "sizes" : { "large" : { "w" : 1273, "h" : 715, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" }, "small" : { "w" : 680, "h" : 382, "resize" : "fit" }, "medium" : { "w" : 1200, "h" : 674, "resize" : "fit" } }, "source_status_id" : 793982502008397825, "source_status_id_str" : "793982502008397825", "source_user_id" : 570290656, "source_user_id_str" : "570290656", "video_info" : { "aspect_ratio" : [ 16, 9 ], "duration_millis" : 72440, "variants" : [ { "bitrate" : 320000, "content_type" : "video/mp4", "url" : "https://video.twimg.com/amplify_video/793976804549754880/vid/320x180/wO9JSutxwKtnr-4M.mp4" }, { "bitrate" : 2176000, "content_type" : "video/mp4", "url" : "https://video.twimg.com/amplify_video/793976804549754880/vid/1280x720/zMwtzVr0k_5SYkOG.mp4" }, { "content_type" : "application/x-mpegURL", "url" : "https://video.twimg.com/amplify_video/793976804549754880/pl/Vj9EDa2m-8tTq332.m3u8" }, { "bitrate" : 832000, "content_type" : "video/mp4", "url" : "https://video.twimg.com/amplify_video/793976804549754880/vid/640x360/1WAEEC98fuAilBVX.mp4" }, { "content_type" : "application/dash+xml", "url" : "https://video.twimg.com/amplify_video/793976804549754880/pl/Vj9EDa2m-8tTq332.mpd" } ] }, "additional_media_info" : { "title" : "", "description" : "", "call_to_actions" : { "visit_site" : { "url" : "https://www.youtube.com/user/LateNightSeth/featured" } }, "embeddable" : true, "monetizable" : false, "source_user" : { "id" : 570290656, "id_str" : "570290656", "name" : "Late Night", "screen_name" : "LateNightSeth", "location" : "Studio 8G - Rockefeller Center", "description" : "Official Twitter handle for Late Night with @SethMeyers, airing weeknights at 12:35/11:35c on @NBC. #LNSM", "url" : "http://t.co/af2p12D0GI", "entities" : { "url" : { "urls" : [ { "url" : "http://t.co/af2p12D0GI", "expanded_url" : "http://latenightseth.com", "display_url" : "latenightseth.com", "indices" : [ 0, 22 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 291188, "friends_count" : 1743, "listed_count" : 1467, "created_at" : "Thu May 03 21:08:00 +0000 2012", "favourites_count" : 2440, "utc_offset" : -18000, "time_zone" : "Eastern Time (US & Canada)", "geo_enabled" : true, "verified" : true, "statuses_count" : 8635, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://pbs.twimg.com/profile_background_images/438333165292097536/Z0HtuqUc.jpeg", "profile_background_image_url_https" : "https://pbs.twimg.com/profile_background_images/438333165292097536/Z0HtuqUc.jpeg", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/781548560944865280/9dpThu5h_normal.jpg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/781548560944865280/9dpThu5h_normal.jpg", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/570290656/1476483213", "profile_link_color" : "0084B4", "profile_sidebar_border_color" : "FFFFFF", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" } } } ] }, "source" : "Twitter for iPhone", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 21369740, "id_str" : "21369740", "name" : "Rob DenBleyker", "screen_name" : "RobDenBleyker", "location" : "Dallas", "description" : "I'm not Rob Dyrdek. Don't follow me.", "url" : "https://t.co/5jh1OQTAOO", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/5jh1OQTAOO", "expanded_url" : "http://www.explosm.net", "display_url" : "explosm.net", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 126727, "friends_count" : 815, "listed_count" : 1287, "created_at" : "Fri Feb 20 03:26:24 +0000 2009", "favourites_count" : 1543, "utc_offset" : -18000, "time_zone" : "Central Time (US & Canada)", "geo_enabled" : true, "verified" : false, "statuses_count" : 8276, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "49585E", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/769014291756441601/b0axYzlg_normal.jpg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/769014291756441601/b0axYzlg_normal.jpg", "profile_link_color" : "0C90F5", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : false, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "retweeted_status" : { "created_at" : "Thu Nov 03 19:59:57 +0000 2016", "id" : 794267893257170944, "id_str" : "794267893257170944", "full_text" : "Truly iconic https://t.co/8gdSKk0Zx2", "truncated" : false, "display_text_range" : [ 0, 36 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ ], "urls" : [ ], "media" : [ { "id" : 793976804549754880, "id_str" : "793976804549754880", "indices" : [ 13, 36 ], "media_url" : "http://pbs.twimg.com/media/CwTHbn3XgAEcDEr.jpg", "media_url_https" : "https://pbs.twimg.com/media/CwTHbn3XgAEcDEr.jpg", "url" : "https://t.co/8gdSKk0Zx2", "display_url" : "pic.twitter.com/8gdSKk0Zx2", "expanded_url" : "https://twitter.com/LateNightSeth/status/793982502008397825/video/1", "type" : "photo", "sizes" : { "large" : { "w" : 1273, "h" : 715, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" }, "small" : { "w" : 680, "h" : 382, "resize" : "fit" }, "medium" : { "w" : 1200, "h" : 674, "resize" : "fit" } }, "source_status_id" : 793982502008397825, "source_status_id_str" : "793982502008397825", "source_user_id" : 570290656, "source_user_id_str" : "570290656" } ] }, "extended_entities" : { "media" : [ { "id" : 793976804549754880, "id_str" : "793976804549754880", "indices" : [ 13, 36 ], "media_url" : "http://pbs.twimg.com/media/CwTHbn3XgAEcDEr.jpg", "media_url_https" : "https://pbs.twimg.com/media/CwTHbn3XgAEcDEr.jpg", "url" : "https://t.co/8gdSKk0Zx2", "display_url" : "pic.twitter.com/8gdSKk0Zx2", "expanded_url" : "https://twitter.com/LateNightSeth/status/793982502008397825/video/1", "type" : "video", "sizes" : { "large" : { "w" : 1273, "h" : 715, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" }, "small" : { "w" : 680, "h" : 382, "resize" : "fit" }, "medium" : { "w" : 1200, "h" : 674, "resize" : "fit" } }, "source_status_id" : 793982502008397825, "source_status_id_str" : "793982502008397825", "source_user_id" : 570290656, "source_user_id_str" : "570290656", "video_info" : { "aspect_ratio" : [ 16, 9 ], "duration_millis" : 72440, "variants" : [ { "bitrate" : 320000, "content_type" : "video/mp4", "url" : "https://video.twimg.com/amplify_video/793976804549754880/vid/320x180/wO9JSutxwKtnr-4M.mp4" }, { "bitrate" : 2176000, "content_type" : "video/mp4", "url" : "https://video.twimg.com/amplify_video/793976804549754880/vid/1280x720/zMwtzVr0k_5SYkOG.mp4" }, { "content_type" : "application/x-mpegURL", "url" : "https://video.twimg.com/amplify_video/793976804549754880/pl/Vj9EDa2m-8tTq332.m3u8" }, { "bitrate" : 832000, "content_type" : "video/mp4", "url" : "https://video.twimg.com/amplify_video/793976804549754880/vid/640x360/1WAEEC98fuAilBVX.mp4" }, { "content_type" : "application/dash+xml", "url" : "https://video.twimg.com/amplify_video/793976804549754880/pl/Vj9EDa2m-8tTq332.mpd" } ] }, "additional_media_info" : { "title" : "", "description" : "", "call_to_actions" : { "visit_site" : { "url" : "https://www.youtube.com/user/LateNightSeth/featured" } }, "embeddable" : true, "monetizable" : false, "source_user" : { "id" : 570290656, "id_str" : "570290656", "name" : "Late Night", "screen_name" : "LateNightSeth", "location" : "Studio 8G - Rockefeller Center", "description" : "Official Twitter handle for Late Night with @SethMeyers, airing weeknights at 12:35/11:35c on @NBC. #LNSM", "url" : "http://t.co/af2p12D0GI", "entities" : { "url" : { "urls" : [ { "url" : "http://t.co/af2p12D0GI", "expanded_url" : "http://latenightseth.com", "display_url" : "latenightseth.com", "indices" : [ 0, 22 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 291188, "friends_count" : 1743, "listed_count" : 1467, "created_at" : "Thu May 03 21:08:00 +0000 2012", "favourites_count" : 2440, "utc_offset" : -18000, "time_zone" : "Eastern Time (US & Canada)", "geo_enabled" : true, "verified" : true, "statuses_count" : 8635, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://pbs.twimg.com/profile_background_images/438333165292097536/Z0HtuqUc.jpeg", "profile_background_image_url_https" : "https://pbs.twimg.com/profile_background_images/438333165292097536/Z0HtuqUc.jpeg", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/781548560944865280/9dpThu5h_normal.jpg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/781548560944865280/9dpThu5h_normal.jpg", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/570290656/1476483213", "profile_link_color" : "0084B4", "profile_sidebar_border_color" : "FFFFFF", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" } } } ] }, "source" : "Twitter for iPhone", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 2624180621, "id_str" : "2624180621", "name" : "NSA agent jim", "screen_name" : "mileysdrive", "location" : "", "description" : "cristina yang", "url" : null, "entities" : { "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 16939, "friends_count" : 124, "listed_count" : 48, "created_at" : "Thu Jun 19 23:54:07 +0000 2014", "favourites_count" : 7577, "utc_offset" : 7200, "time_zone" : "Bucharest", "geo_enabled" : true, "verified" : false, "statuses_count" : 5639, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/794467416331927552/LcPQANP8_normal.jpg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/794467416331927552/LcPQANP8_normal.jpg", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/2624180621/1467532442", "profile_link_color" : "1DA1F2", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : true, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 7523, "favorite_count" : 8028, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "possibly_sensitive_appealable" : false, "lang" : "en" }, "is_quote_status" : false, "retweet_count" : 7523, "favorite_count" : 0, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "possibly_sensitive_appealable" : false, "lang" : "en" } """; const string REPLY = """ { "created_at" : "Sat Jan 28 09:23:50 +0000 2017", "id" : 825273167560208384, "id_str" : "825273167560208384", "full_text" : "@explodingwalrus \"definitely\" as in \"xdg-settings get default-url-scheme-handler https\" returns chrome's desktop file?", "truncated" : false, "display_text_range" : [ 17, 118 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "explodingwalrus", "name" : "Carl Draper", "id" : 44823886, "id_str" : "44823886", "indices" : [ 0, 16 ] } ], "urls" : [ ] }, "source" : "Twitter Web Client", "in_reply_to_status_id" : 825269440556081153, "in_reply_to_status_id_str" : "825269440556081153", "in_reply_to_user_id" : 44823886, "in_reply_to_user_id_str" : "44823886", "in_reply_to_screen_name" : "explodingwalrus", "user" : { "id" : 2877682863, "id_str" : "2877682863", "name" : "Corebird", "screen_name" : "corebirdclient", "location" : "", "description" : "If there's no bug report for it, it doesn't exist.\n\nThis account is run by a highly-paid intern, not @baedert himself.\n\nStill not CoreBird. Or @Corebird.", "url" : "https://t.co/yGvX7Nf6i3", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/yGvX7Nf6i3", "expanded_url" : "http://corebird.baedert.org", "display_url" : "corebird.baedert.org", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 430, "friends_count" : 3, "listed_count" : 24, "created_at" : "Sat Nov 15 08:08:32 +0000 2014", "favourites_count" : 22, "utc_offset" : 7200, "time_zone" : "Ljubljana", "geo_enabled" : false, "verified" : false, "statuses_count" : 376, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "FAB81E", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/533595923679432704/bIWqAMTk_normal.png", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/533595923679432704/bIWqAMTk_normal.png", "profile_link_color" : "000000", "profile_sidebar_border_color" : "000000", "profile_sidebar_fill_color" : "000000", "profile_text_color" : "000000", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 0, "favorite_count" : 0, "favorited" : false, "retweeted" : false, "lang" : "en" } """; const string REPLY_TO_TWO = """ { "created_at" : "Mon Apr 17 13:01:46 +0000 2017", "id" : 853956655020748801, "id_str" : "853956655020748801", "full_text" : "@kaidjohnson @gnome Not everyone feels as confident switching to Linux. UKUI gives them a comfort blanket of familiarity.", "truncated" : false, "display_text_range" : [ 20, 121 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "kaidjohnson", "name" : "Kai Johnson", "id" : 34798045, "id_str" : "34798045", "indices" : [ 0, 12 ] }, { "screen_name" : "gnome", "name" : "GNOME", "id" : 12579252, "id_str" : "12579252", "indices" : [ 13, 19 ] } ], "urls" : [ ] }, "source" : "Twitter Web Client", "in_reply_to_status_id" : 853956134323048452, "in_reply_to_status_id_str" : "853956134323048452", "in_reply_to_user_id" : 34798045, "in_reply_to_user_id_str" : "34798045", "in_reply_to_screen_name" : "kaidjohnson", "user" : { "id" : 72915446, "id_str" : "72915446", "name" : "OMG! UBUNTU!", "screen_name" : "omgubuntu", "location" : "US/UK", "description" : "Your #1 source for the latest #Ubuntu Linux news, apps, tips, gaming & more. Got news? joey[at]ohso.io", "url" : "https://t.co/E0SDJKEG9w", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/E0SDJKEG9w", "expanded_url" : "http://www.omgubuntu.co.uk", "display_url" : "omgubuntu.co.uk", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 94994, "friends_count" : 54, "listed_count" : 3816, "created_at" : "Wed Sep 09 18:26:36 +0000 2009", "favourites_count" : 2689, "utc_offset" : 3600, "time_zone" : "London", "geo_enabled" : true, "verified" : false, "statuses_count" : 12699, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "4D1F41", "profile_background_image_url" : "http://pbs.twimg.com/profile_background_images/458275648939622400/g2-00Dua.png", "profile_background_image_url_https" : "https://pbs.twimg.com/profile_background_images/458275648939622400/g2-00Dua.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/627831364692389888/L3tujQ89_normal.png", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/627831364692389888/L3tujQ89_normal.png", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/72915446/1485460019", "profile_link_color" : "009999", "profile_sidebar_border_color" : "FFFFFF", "profile_sidebar_fill_color" : "FFFFFF", "profile_text_color" : "303030", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false, "translator_type" : "regular" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 0, "favorite_count" : 1, "favorited" : false, "retweeted" : false, "lang" : "en" } """; const string REPLY2 = """ { "created_at" : "Mon Apr 17 15:16:18 +0000 2017", "id" : 853990508326252550, "id_str" : "853990508326252550", "full_text" : "@jjdesmond @_UBRAS_ @franalsworth @4Apes @katy4apes @theAliceRoberts @JaneGoodallUK @Jane_Goodall @JaneGoodallInst And here's the link for tickets again ... https://t.co/a9lOVMouNK", "truncated" : false, "display_text_range" : [ 115, 180 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "jjdesmond", "name" : "Jimmy Jenny Desmond", "id" : 21278482, "id_str" : "21278482", "indices" : [ 0, 10 ] }, { "screen_name" : "_UBRAS_", "name" : "Roots and Shoots UOB", "id" : 803329927974096896, "id_str" : "803329927974096896", "indices" : [ 11, 19 ] }, { "screen_name" : "franalsworth", "name" : "Fran", "id" : 776983919287754752, "id_str" : "776983919287754752", "indices" : [ 20, 33 ] }, { "screen_name" : "4Apes", "name" : "Ian Redmond", "id" : 155889035, "id_str" : "155889035", "indices" : [ 34, 40 ] }, { "screen_name" : "katy4apes", "name" : "Katy Jedamzik", "id" : 159608654, "id_str" : "159608654", "indices" : [ 41, 51 ] }, { "screen_name" : "theAliceRoberts", "name" : "Prof Alice Roberts", "id" : 260211154, "id_str" : "260211154", "indices" : [ 52, 68 ] }, { "screen_name" : "JaneGoodallUK", "name" : "Roots & Shoots UK", "id" : 423423823, "id_str" : "423423823", "indices" : [ 69, 83 ] }, { "screen_name" : "Jane_Goodall", "name" : "Jane Goodall", "id" : 235157216, "id_str" : "235157216", "indices" : [ 84, 97 ] }, { "screen_name" : "JaneGoodallInst", "name" : "JaneGoodallInstitute", "id" : 39822897, "id_str" : "39822897", "indices" : [ 98, 114 ] } ], "urls" : [ { "url" : "https://t.co/a9lOVMouNK", "expanded_url" : "https://www.eventbrite.com/e/working-with-apes-tickets-33089771397", "display_url" : "eventbrite.com/e/working-with…", "indices" : [ 157, 180 ] } ] }, "source" : "Twitter for iPhone", "in_reply_to_status_id" : 853925036696141824, "in_reply_to_status_id_str" : "853925036696141824", "in_reply_to_user_id" : 21278482, "in_reply_to_user_id_str" : "21278482", "in_reply_to_screen_name" : "jjdesmond", "user" : { "id" : 415472140, "id_str" : "415472140", "name" : "Ben Garrod", "screen_name" : "Ben_garrod", "location" : "Bristol&Norfolk", "description" : "Monkey-chaser, TV-talker, bone geek and Teaching Fellow at @AngliaRuskin https://t.co/FXbftdxxTJ", "url" : "https://t.co/1B9SDHfWoF", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/1B9SDHfWoF", "expanded_url" : "http://www.josarsby.com/ben-garrod", "display_url" : "josarsby.com/ben-garrod", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ { "url" : "https://t.co/FXbftdxxTJ", "expanded_url" : "http://www.anglia.ac.uk/science-and-technology/about/life-sciences/our-staff/ben-garrod", "display_url" : "anglia.ac.uk/science-and-te…", "indices" : [ 73, 96 ] } ] } }, "protected" : false, "followers_count" : 6526, "friends_count" : 1016, "listed_count" : 128, "created_at" : "Fri Nov 18 11:30:48 +0000 2011", "favourites_count" : 25292, "utc_offset" : 3600, "time_zone" : "London", "geo_enabled" : true, "verified" : true, "statuses_count" : 17224, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://pbs.twimg.com/profile_background_images/590945579024257024/2F1itaGz.jpg", "profile_background_image_url_https" : "https://pbs.twimg.com/profile_background_images/590945579024257024/2F1itaGz.jpg", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/615498558385557505/cwSloac3_normal.jpg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/615498558385557505/cwSloac3_normal.jpg", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/415472140/1477223840", "profile_link_color" : "0084B4", "profile_sidebar_border_color" : "FFFFFF", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : false, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 6, "favorite_count" : 7, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "lang" : "en" } """; const string REPLY3 = """ { "created_at" : "Mon Apr 24 11:40:53 +0000 2017", "id" : 856473014560591872, "id_str" : "856473014560591872", "full_text" : "RT @corebirdgtk: @baedert and @corebirdclient so?", "truncated" : false, "display_text_range" : [ 0, 49 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "corebirdgtk", "name" : "Z??!@*(&*²³¤²³¤", "id" : 993713617, "id_str" : "993713617", "indices" : [ 3, 15 ] }, { "screen_name" : "baedert", "name" : "Schupp & Wupp", "id" : 118055879, "id_str" : "118055879", "indices" : [ 17, 25 ] }, { "screen_name" : "corebirdclient", "name" : "Corebird", "id" : 2877682863, "id_str" : "2877682863", "indices" : [ 30, 45 ] } ], "urls" : [ ] }, "source" : "Twitter Web Client", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 993713617, "id_str" : "993713617", "name" : "Z??!@*(&*²³¤²³¤", "screen_name" : "corebirdgtk", "location" : "", "description" : "<asdf", "url" : null, "entities" : { "description" : { "urls" : [ ] } }, "protected" : true, "followers_count" : 3, "friends_count" : 4, "listed_count" : 0, "created_at" : "Thu Dec 06 19:47:16 +0000 2012", "favourites_count" : 9, "utc_offset" : 7200, "time_zone" : "Amsterdam", "geo_enabled" : false, "verified" : false, "statuses_count" : 909, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : true, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/655270693341417472/h6BbZKJy_normal.png", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/655270693341417472/h6BbZKJy_normal.png", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/993713617/1413106147", "profile_link_color" : "1DA1F2", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : true, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "retweeted_status" : { "created_at" : "Mon Apr 24 11:35:17 +0000 2017", "id" : 856471602883686400, "id_str" : "856471602883686400", "full_text" : "@baedert and @corebirdclient so?", "truncated" : false, "display_text_range" : [ 9, 32 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "baedert", "name" : "Schupp & Wupp", "id" : 118055879, "id_str" : "118055879", "indices" : [ 0, 8 ] }, { "screen_name" : "corebirdclient", "name" : "Corebird", "id" : 2877682863, "id_str" : "2877682863", "indices" : [ 13, 28 ] } ], "urls" : [ ] }, "source" : "Twitter Web Client", "in_reply_to_status_id" : 853198126106148864, "in_reply_to_status_id_str" : "853198126106148864", "in_reply_to_user_id" : 993713617, "in_reply_to_user_id_str" : "993713617", "in_reply_to_screen_name" : "corebirdgtk", "user" : { "id" : 993713617, "id_str" : "993713617", "name" : "Z??!@*(&*²³¤²³¤", "screen_name" : "corebirdgtk", "location" : "", "description" : "<asdf", "url" : null, "entities" : { "description" : { "urls" : [ ] } }, "protected" : true, "followers_count" : 3, "friends_count" : 4, "listed_count" : 0, "created_at" : "Thu Dec 06 19:47:16 +0000 2012", "favourites_count" : 9, "utc_offset" : 7200, "time_zone" : "Amsterdam", "geo_enabled" : false, "verified" : false, "statuses_count" : 909, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : true, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/655270693341417472/h6BbZKJy_normal.png", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/655270693341417472/h6BbZKJy_normal.png", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/993713617/1413106147", "profile_link_color" : "1DA1F2", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : true, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 1, "favorite_count" : 0, "favorited" : false, "retweeted" : false, "lang" : "en" }, "is_quote_status" : false, "retweet_count" : 1, "favorite_count" : 0, "favorited" : false, "retweeted" : false, "lang" : "en" } """; corebird-1.7.4/meson.build000066400000000000000000000152021324604713000154560ustar00rootroot00000000000000project('Corebird', ['vala', 'c'], version: '1.7.4', default_options: [ 'buildtype=debug' ]) gnome = import('gnome') srcdir = include_directories('src') restdir = include_directories('src/rest/') min_glib_version = '2.44' glib_dep = dependency('glib-2.0', version: '>=' + min_glib_version) gtk_dep = dependency('gtk+-3.0', version: '>=3.20') json_dep = dependency('json-glib-1.0') sql_dep = dependency('sqlite3') soup_dep = dependency('libsoup-2.4') cb_deps = [ glib_dep, gtk_dep, json_dep, sql_dep, soup_dep ] # Options enable_debug = get_option('buildtype') == 'debug' or get_option('buildtype') == 'debugoptimized' enable_video = get_option('video') enable_spellcheck = get_option('spellcheck') enable_examples = get_option('examples') # Project arguments add_project_arguments('-DGETTEXT_PACKAGE="corebird"', language: 'c') add_project_arguments('-DDATADIR="' + get_option('datadir') + '"', language: 'c') add_project_arguments('-DG_LOG_DOMAIN="corebird"', language: 'c') add_project_arguments('--enable-deprecated', language: 'vala') add_project_arguments('-D', 'GTK322', language: 'vala') if (enable_debug) add_project_arguments('-g', language: 'c') add_project_arguments('-g', language: 'vala') add_project_arguments('-DDEBUG', language: 'c') #add_project_arguments('-DG_DISABLE_CAST_CHECKS', language: 'c') add_project_arguments('-D', 'DEBUG', language: 'vala') endif if (enable_video) add_project_arguments('-DVIDEO', language: 'c') add_project_arguments('-D', 'VIDEO', language: 'vala') cb_deps += [dependency('gstreamer-video-1.0', version: '>= 1.6')] endif if (enable_spellcheck) add_project_arguments('-DSPELLCHECK', language: 'c') add_project_arguments('-D', 'SPELLCHECK', language: 'vala') cb_deps += [dependency('gspell-1', version: '>=1.0')] endif corebird_lib_sources = files([ 'src/Account.vala', 'src/Corebird.vala', 'src/DMManager.vala', 'src/DMPage.vala', 'src/DMThreadsPage.vala', 'src/DefaultTimeline.vala', 'src/FavoritesTimeline.vala', 'src/FilterPage.vala', 'src/HomeTimeline.vala', 'src/IPage.vala', 'src/ListStatusesPage.vala', 'src/ListsPage.vala', 'src/MainWidget.vala', 'src/MainWindow.vala', 'src/MentionsTimeline.vala', 'src/NotificationManager.vala', 'src/ProfilePage.vala', 'src/SearchPage.vala', 'src/Settings.vala', 'src/TweetInfoPage.vala', 'src/Twitter.vala', 'src/UserEventReceiver.vala', 'src/async/Collect.vala', 'src/list/AddListEntry.vala', 'src/list/DMListEntry.vala', 'src/list/DMThreadEntry.vala', 'src/list/FilterListEntry.vala', 'src/list/ListListEntry.vala', 'src/list/NewListEntry.vala', 'src/list/SnippetListEntry.vala', 'src/list/StartConversationEntry.vala', 'src/list/TweetListEntry.vala', 'src/list/UserFilterEntry.vala', 'src/list/UserListEntry.vala', 'src/list/FavImageRow.vala', 'src/model/DMThreadsModel.vala', 'src/sql/Database.vala', 'src/sql/InsertStatement.vala', 'src/sql/SelectStatement.vala', 'src/sql/UpdateStatement.vala', 'src/util/Benchmark.vala', 'src/util/Dirs.vala', 'src/util/TweetUtils.vala', 'src/util/UserCompletion.vala', 'src/util/UserUtils.vala', 'src/util/Utils.vala', 'src/widgets/AccountCreateWidget.vala', 'src/widgets/AddImageButton.vala', 'src/widgets/AspectImage.vala', 'src/widgets/AvatarBannerWidget.vala', 'src/widgets/AvatarWidget.vala', 'src/widgets/BadgeRadioButton.vala', 'src/widgets/CompletionTextView.vala', 'src/widgets/ComposeImageManager.vala', 'src/widgets/CropWidget.vala', 'src/widgets/DMPlaceholderBox.vala', 'src/widgets/DoubleTapButton.vala', 'src/widgets/FollowButton.vala', 'src/widgets/ImpostorWidget.vala', 'src/widgets/LazyMenuButton.vala', 'src/widgets/MaxSizeContainer.vala', 'src/widgets/MediaButton.vala', 'src/widgets/MultiMediaWidget.vala', 'src/widgets/PixbufButton.vala', 'src/widgets/ReplyEntry.vala', 'src/widgets/ReplyIndicator.vala', 'src/widgets/ScrollWidget.vala', 'src/widgets/TextButton.vala', 'src/widgets/TweetListBox.vala', 'src/widgets/UserListsWidget.vala', 'src/widgets/FavImageView.vala', 'src/window/AboutDialog.vala', 'src/window/AccountDialog.vala', 'src/window/ComposeTweetWindow.vala', 'src/window/MediaDialog.vala', 'src/window/ModifyFilterDialog.vala', 'src/window/ModifySnippetDialog.vala', 'src/window/SettingsDialog.vala', 'src/window/UserListDialog.vala', # C Sources 'src/CbTweet.c', 'src/CbTextTransform.c', 'src/CbMedia.c', 'src/CbMediaDownloader.c', 'src/CbTypes.c', 'src/CbUserCounter.c', 'src/CbFilter.c', 'src/CbAvatarCache.c', 'src/CbMediaImageWidget.c', 'src/CbTweetModel.c', 'src/CbTwitterItem.c', 'src/CbDeltaUpdater.c', 'src/CbUtils.c', 'src/CbBundle.c', 'src/CbBundleHistory.c', 'src/CbSnippetManager.c', 'src/CbSurfaceProgress.c', 'src/CbMediaVideoWidget.c', 'src/CbUserStream.c', 'src/CbMessageReceiver.c', 'src/CbComposeJob.c', 'src/CbUserCompletionModel.c', 'src/CbEmojiChooser.c', # Rest sources 'src/rest/rest/rest-param.c', 'src/rest/rest/rest-params.c', 'src/rest/rest/rest-proxy.c', 'src/rest/rest/rest-proxy-call.c', 'src/rest/rest/rest-main.c', 'src/rest/rest/oauth-proxy.c', 'src/rest/rest/oauth-proxy-call.c', 'src/rest/rest/sha1.c', # libtweetlength sources (TODO: Should be a meson subproject) # https://github.com/baedert/libtweetlength 'src/libtl/libtweetlength.c', # Vapi files 'vapi/corebird-internal.vapi', 'vapi/rest-0.7.vapi', 'vapi/libtl.vapi' ]) # glib resources cb_resources = gnome.compile_resources( 'corebird_resources', 'corebird.gresource.xml', ) # config.h cdata = configuration_data() configure_file( input : 'config.h.meson', output: 'config.h', configuration: cdata ) # library (for unit tests) cb_lib = static_library( 'corebird', corebird_lib_sources, dependencies: cb_deps, include_directories: [srcdir, restdir], vala_args: [ meson.source_root() + '/vapi/config.vapi', '--target-glib=' + min_glib_version, '--vapidir=' + meson.source_root() + '/vapi/', '--gresources=' + meson.source_root() + '/corebird.gresource.xml', ], c_args: [ '-Werror=implicit-function-declaration' ] ) cb_dep = declare_dependency( link_with: cb_lib, dependencies: cb_deps, include_directories: [srcdir, restdir], ) # actual executable executable( 'corebird', 'src/main.vala', cb_resources, dependencies: cb_dep, include_directories: srcdir, vala_args: [ meson.source_root() + '/vapi/corebird-internal.vapi', meson.source_root() + '/vapi/rest-0.7.vapi', meson.source_root() + '/vapi/libtl.vapi', ], install: true ) subdir('data') subdir('po') subdir('tests') if enable_examples subdir('examples') endif corebird-1.7.4/meson_options.txt000066400000000000000000000002401324604713000167450ustar00rootroot00000000000000option('video', type: 'boolean', value: 'true') option('spellcheck', type: 'boolean', value: 'true') option('examples', type: 'boolean', value: 'false') corebird-1.7.4/po/000077500000000000000000000000001324604713000137325ustar00rootroot00000000000000corebird-1.7.4/po/.gitignore000066400000000000000000000001041324604713000157150ustar00rootroot00000000000000_*.pot *.gmo stamp-it Makefile.in.in .intltool-merge-cache POTFILES corebird-1.7.4/po/LINGUAS000066400000000000000000000002241324604713000147550ustar00rootroot00000000000000ar ast ca ca@valencia de en_AU eo es es_419 es_MX es_VE fa fi fr ga gl hi hu id it ja ko lt nb nl pl pt pt_BR ro ru sr sr@latin tr uk_UA zh_CN zh_TWcorebird-1.7.4/po/Makevars000066400000000000000000000065411324604713000154340ustar00rootroot00000000000000# Makefile variables for PO directory in any package using GNU gettext. # Usually the message domain is the same as the package name. DOMAIN = $(PACKAGE) # These two variables depend on the location of this directory. subdir = po top_builddir = .. # These options get passed to xgettext. XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ --from-code=UTF-8 # This is the copyright holder that gets inserted into the header of the # $(DOMAIN).pot file. Set this to the copyright holder of the surrounding # package. (Note that the msgstr strings, extracted from the package's # sources, belong to the copyright holder of the package.) Translators are # expected to transfer the copyright for their translations to this person # or entity, or to disclaim their copyright. The empty string stands for # the public domain; in this case the translators are expected to disclaim # their copyright. COPYRIGHT_HOLDER = Timm Bäder # This tells whether or not to prepend "GNU " prefix to the package # name that gets inserted into the header of the $(DOMAIN).pot file. # Possible values are "yes", "no", or empty. If it is empty, try to # detect it automatically by scanning the files in $(top_srcdir) for # "GNU packagename" string. PACKAGE_GNU = no # This is the email address or URL to which the translators shall report # bugs in the untranslated strings: # - Strings which are not entire sentences, see the maintainer guidelines # in the GNU gettext documentation, section 'Preparing Strings'. # - Strings which use unclear terms or require additional context to be # understood. # - Strings which make invalid assumptions about notation of date, time or # money. # - Pluralisation problems. # - Incorrect English spelling. # - Incorrect formatting. # It can be your email address, or a mailing list address where translators # can write to without being subscribed, or the URL of a web page through # which the translators can contact you. MSGID_BUGS_ADDRESS = https://github.com/baedert/corebird/issues/new # This is the list of locale categories, beyond LC_MESSAGES, for which the # message catalogs shall be used. It is usually empty. EXTRA_LOCALE_CATEGORIES = # This tells whether the $(DOMAIN).pot file contains messages with an 'msgctxt' # context. Possible values are "yes" and "no". Set this to yes if the # package uses functions taking also a message context, like pgettext(), or # if in $(XGETTEXT_OPTIONS) you define keywords with a context argument. USE_MSGCTXT = yes # These options get passed to msgmerge. # Useful options are in particular: # --previous to keep previous msgids of translated messages, # --quiet to reduce the verbosity. MSGMERGE_OPTIONS = # These options get passed to msginit. # If you want to disable line wrapping when writing PO files, add # --no-wrap to MSGMERGE_OPTIONS, XGETTEXT_OPTIONS, and # MSGINIT_OPTIONS. MSGINIT_OPTIONS = # This tells whether or not to regenerate a PO file when $(DOMAIN).pot # has changed. Possible values are "yes" and "no". Set this to no if # the POT file is checked in the repository and the version control # program ignores timestamps. PO_DEPENDS_ON_POT = no # This tells whether or not to forcibly update $(DOMAIN).pot and # regenerate PO files on "make dist". Possible values are "yes" and # "no". Set this to no if the POT file and PO files are maintained # externally. DIST_DEPENDS_ON_UPDATE_PO = no corebird-1.7.4/po/POTFILES.in000066400000000000000000000031001324604713000155010ustar00rootroot00000000000000# List of source files containing translatable strings. # Please keep this file sorted alphabetically. data/org.baedert.corebird.appdata.xml.in data/org.baedert.corebird.desktop.in src/CbUtils.c src/DMPage.vala src/DMThreadsPage.vala src/DefaultTimeline.vala src/FavoritesTimeline.vala src/FilterPage.vala src/HomeTimeline.vala src/ListStatusesPage.vala src/ListsPage.vala src/MainWindow.vala src/MentionsTimeline.vala src/ProfilePage.vala src/SearchPage.vala src/TweetInfoPage.vala src/UserEventReceiver.vala src/list/DMThreadEntry.vala src/list/FavImageRow.vala src/list/TweetListEntry.vala src/util/Utils.vala src/widgets/AccountCreateWidget.vala src/widgets/AvatarBannerWidget.vala src/widgets/CompletionTextView.vala src/widgets/FavImageView.vala src/widgets/FollowButton.vala src/widgets/MediaButton.vala src/widgets/PixbufButton.vala src/widgets/TweetListBox.vala src/window/AccountDialog.vala src/window/ComposeTweetWindow.vala src/window/ModifyFilterDialog.vala src/window/ModifySnippetDialog.vala src/window/SettingsDialog.vala src/window/UserListDialog.vala ui/about-dialog.ui ui/account-create-widget.ui ui/account-dialog.ui ui/cb-emoji-chooser.ui ui/compose-window.ui ui/dm-page.ui ui/filter-list-entry.ui ui/filter-page.ui ui/list-list-entry.ui ui/list-statuses-page.ui ui/menus.ui ui/modify-filter-dialog.ui ui/modify-snippet-dialog.ui ui/new-list-entry.ui ui/profile-page.ui ui/search-page.ui ui/settings-dialog.ui ui/shortcuts-window.ui ui/start-conversation-entry.ui ui/tweet-info-page.ui ui/tweet-list-entry.ui ui/user-filter-entry.ui ui/user-list-entry.ui ui/user-lists-widget.ui corebird-1.7.4/po/POTFILES.skip000066400000000000000000000015611324604713000160520ustar00rootroot00000000000000# List of source files that should *not* be translated. # Please keep this file sorted alphabetically. src/DefaultTimeline.c src/DMPage.c src/DMThreadsPage.c src/FavoritesTimeline.c src/FilterPage.c src/HomeTimeline.c src/list/DMThreadEntry.c src/list/FavImageRow.c src/ListsPage.c src/ListStatusesPage.c src/list/TweetListEntry.c src/MainWindow.c src/MentionsTimeline.c src/ProfilePage.c src/SearchPage.c src/TweetInfoPage.c src/UserEventReceiver.c src/util/Utils.c src/widgets/AccountCreateWidget.c src/widgets/AvatarBannerWidget.c src/widgets/CompletionTextView.c src/widgets/FavImageView.c src/widgets/FollowButton.c src/widgets/MediaButton.c src/widgets/PixbufButton.c src/widgets/TweetListBox.c src/window/AccountDialog.c src/window/ComposeTweetWindow.c src/window/ModifyFilterDialog.c src/window/ModifySnippetDialog.c src/window/SettingsDialog.c src/window/UserListDialog.c corebird-1.7.4/po/ar.po000066400000000000000000000505201324604713000146760ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Med Touhami MAHDI , 2014 # mohammadA , 2016 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2018-01-08 14:04+0000\n" "Last-Translator: mohammadA \n" "Language-Team: Arabic (http://www.transifex.com/corebird/corebird/language/ar/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ar\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "عامل تويتر" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "كوربرد عميل تويتر GTK+ أصيل يوفّر ميزات حيوية مثل الرسائل المباشرة والإخطار بالتغريدات وعرض المحادثات." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "" #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "محادثة مباشرة" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" msgstr[4] "" msgstr[5] "" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "رسالة مباشرة جديدة من %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "رسائل مباشرة" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "المفضلة" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s أعاد تغريد %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s غرد" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" msgstr[4] "" msgstr[5] "" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "الرئيسية" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "قائمة" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "قوائم" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "كتابة تغريدة" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "أضف حسابًا جديدًا" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "الإشارات" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "ملف محمي" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "غرد لِ@%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "البحث" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "إعادات التغريدات" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "فتح في المتصفح." #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "المصدر" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "تفاصيل التغريدة" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" msgstr[4] "" msgstr[5] "" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "حذف" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "" #: src/util/Utils.vala:146 msgid "Now" msgstr "الآن" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dدقيقة" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dساعة" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "أنشئ واحدًا" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "تعذّر فتح %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "قن خاطئ" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "افتح" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "ألغ" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "تَتبٌع" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "إلغاء التتبُع" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "احفظ الفيديو" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "احفظ" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "…يحمّل" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "لم يتم العثور على أي شيء" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" msgstr[4] "" msgstr[5] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" msgstr[4] "" msgstr[5] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "يطابق" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "" #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "حول كوربرد" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "حساب جديد" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "تأكيد" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "" #: ui/account-dialog.ui:88 msgid "Name" msgstr "" #: ui/account-dialog.ui:117 msgid "Website" msgstr "" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "إرسال" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "المستخدمين" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "الإشتراك" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "إلغاء الإشتراك" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "المشتركين" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "الأعضاء" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "المنشئ" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "أنشئ في" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "تعديل" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "الوضع" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "خاص" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "عمومي" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "الإعدادات" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "" #: ui/menus.ui:15 msgid "About" msgstr "حول" #: ui/menus.ui:19 msgid "Quit" msgstr "الجروج" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "إنشاء قائمة جديدة" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "الإسم" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "إنشاء" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "كتابت رسالة مباشرة" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "الإضافة أو الحذف من القائمة" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "محضور" #: ui/profile-page.ui:24 msgid "Muted" msgstr "" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "يتَتبعك" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "تغريدات" #: ui/profile-page.ui:284 msgid "Followers" msgstr "المتتبعون" #: ui/profile-page.ui:326 msgid "Following" msgstr "يتتبع" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "الواجهة" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "العمليات" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "أبدا" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "كل" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "تجميع 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "تجميع 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "تجميع 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "تجميع 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "الإشعارات" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "" #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "بدأ محادثة جديدة" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "مع" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "التحدث" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "إقتباس" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "إعادت التغريد" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "تفضيل التغريدة" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "الرد على التغريدات" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "المزيد" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "تم الإنشاء" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "مشترك في" corebird-1.7.4/po/ast.po000066400000000000000000000550151324604713000150670ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # enolp , 2014-2017 # Xuacu Saturio , 2015 # Ḷḷumex03 , 2014 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2018-01-09 20:56+0000\n" "Last-Translator: Xuacu Saturio \n" "Language-Team: Asturian (http://www.transifex.com/corebird/corebird/language/ast/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ast\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Veceru pa Twitter" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird ye un veceru nativu pa Twitter en GTK+ qu'apurre carauterístiques vitales como mensaxes direutos (DM), notificaciones de tuits, visión de conversaciones." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Carauterístiques adicionales qu'inclúin visión llocal de videos, imáxenes en llinia múltiples, llistes, filtros, cuentes múltiples, etc." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Vista xenérica de llinia temporal al usar Corebird" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Perfil típicu de Twitter" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Puen configurase los axustes de cuentes" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "Timm Bäder" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Usa Twitter dende una aplicación d'escritoriu normal" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "Respondiendo a" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "y" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "y %d más" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Conversación direuta" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d mensaxe nuevu de %s" msgstr[1] "%d mensaxes nuevos de %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Mensaxe direutu nuevu de %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Mensaxes direutos" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "Nun pudieron cargase los tuits" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Favoritos" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Amestar peñera nueva" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Peñeres" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s retuiteó %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s tuiteó" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "¡%d tuit nuevu!" msgstr[1] "¡%d tuits nuevos!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Aniciu" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Llista" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Llistes" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Amosar cuentes configuraes" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Componer tuit" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Amestar cuenta nueva" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s mentó %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Menciones" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "Cuenta suspendida" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Perfil protexíu" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Tuitear a @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "Perfil protexíu" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Guetar" #: src/SearchPage.vala:401 msgid "Load More" msgstr "Cargar más" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Nun pudo amosase'l tuit" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Retuits" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Abrir nel restolador" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Fonte" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Detalles del tuit" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d ensin lleer)" msgstr[1] "(%d ensin lleer)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Desaniciar" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Bloquiar a %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Esti tuit contién imáxenes marcaes como inapropiaes" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Amosar de toes toes" #: src/util/Utils.vala:146 msgid "Now" msgstr "Agora" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "¿Entá nun tienes una cuenta de Twitter?" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Crea una" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "Nun autorizáu. La mayor parte de les veces, esto significa que daqué funciona mal nos sirvidores de Twitter y tendríes de tentar sero" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Nun pudo abrise %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "PIN incorreutu" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "La cuenta yá ta n'usu" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "Nun s'alcontraron usuarios" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Esbillar imaxe" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Abrir" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Encaboxar" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Siguir" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Dexar de siguir" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "Copiar URL" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "Guardar como..." #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "Guardar videu" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "Guardar imaxe" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Guardar" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "Cargando..." #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Nun s'alcontraron entraes" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Reintentar" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "Seleiciona la imaxe del cartel" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "La imaxe nun tien el tamañu mínimu riquíu:" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "Anchor mínimu: %d pixel" msgstr[1] "Anchor mínimu: %d pixels" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "Altor mínimu: %d pixel" msgstr[1] "Altor mínimu: %d pixels" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "Escoyer" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Citar tuit" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "El ficheru seleicionáu nun ye una imaxe" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Atrás" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "La imaxe seleicionada ye demasiao grande. El tamañu máximu per imaxe ye de %'d MB" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "Sólo se permite un ficheru GIF por tweet" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "Inxertar Emoji" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Modificar filtru" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Coincidencies" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "Nun casa" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Editar fragmentu" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "El fragmentu nun puede tar vaciu" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "El reemplazu nun puede tar vaciu" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "El fragmentu nun puede contener espacios en blancu" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "El fragmentu yá existe" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Manín, comprueba esta versión nueva de #Corebird \\ (•◡•) / #Perbién #LoNuevoSiempresYeMeyor" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Amestar o desaniciar usuariu de la llista" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Nun tienes llistes." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Tocante a Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Cuenta nueva" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "P'autenticar Corebird,necesites un PIN de twitter.com cola cuenta que quiés añadir" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Pidir PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "Escribe abaxo'l PIN de twitter.com:" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "PIN" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Confirmar" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Axustes de la cuenta" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Nome" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Sitiu web" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Aniciu automáticu" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "¿De xuru que quies desaniciar esta cuenta?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "Fustaxes y xente" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "Cuerpu y vistíos" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "Animales y natura" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "Comida y bébora" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "Viaxes y llugares" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "Actividaes" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "Oxetos" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "Símbolos" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "Banderes" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "Nun s'alcontraron resultaos" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "Tenta una gueta distinta" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Unviar" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Amestar imaxe" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "Ver imaxes favorites" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Usuarios" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Soscribise" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Esborrase" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Soscriptores:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Miembros:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Creador:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Creáu a les:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Editar" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Mou:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Priváu" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Públicu" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Descripción" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Axustes" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Atayos" #: ui/menus.ui:15 msgid "About" msgstr "Tocante a" #: ui/menus.ui:19 msgid "Quit" msgstr "Colar" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Amestar filtru nuevu" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Añadir fragmentu nuevu" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Pallabra clave" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Troquéu" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Crear llista nueva" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Nome:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Crear" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Escribir mensaxe direutu nuevu" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Amestar a/Desaniciar de la llista" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Bloquiáu" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Slienciáu" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Retuits deshabilitaos" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Síguete" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tuits" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Siguidores" #: ui/profile-page.ui:326 msgid "Following" msgstr "Siguiendo" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "Ver multimedia en llinia" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Amosar siempres" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Anubrir siempres" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Anubrir na llinia temporal" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Desplazamientu automáticu en tuits nuevos" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Activación con clic doblu" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Interfaz" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "En tuits nuevos" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Aiciones" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "En menciones nueves" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "En mensaxes nuevos" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Enxamás" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Cada" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Pila de 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Pila de 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Pila de 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Pila de 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Notificaciones" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Avatares arredondiaos" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Desaniciar les etiquetes del final" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Desaniciar los enllaces a multimedia" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Anubrir conteníu inapropiáu" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Nun hai fragmentos configuraos." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Puedes activar los fragmentos escribiendo la clave y primiendo TAB." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Fragmentos" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Xeneral" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Componer tuit" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Ver les preferencies de la cuenta" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Ver la ventana de cuentes" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Ver les preferencies de la aplicación" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Alternar barra cimera" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Dir atrás" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Dir alantre" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Dir a la nª páxina" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Tuits" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Retuit" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Favoritu" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Responder" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Citar" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Amosar detalles" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Desaniciar" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "Componer" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "Ver selector d'emoji" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Entamar conversación nueva" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Con:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Dir" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Citar" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Retuitear tuit" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Conseñar tuit como favoritu" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Responder al tuit" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Más" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Favoritu" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Contestar" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Desbloquiar" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Amosar los axustes d'esta cuenta" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Abrir nuna ventana nueva" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Dir al perfil" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Creáu" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Soscribise a" corebird-1.7.4/po/ca.po000066400000000000000000000531331324604713000146620ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Estanislau Trepat , 2015 # Guillem Arias , 2016 # Pau Iranzo , 2014 # Sergi , 2015 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: Catalan (http://www.transifex.com/corebird/corebird/language/ca/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ca\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Client del Twitter" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "El Corebird és un client del twitter natiu GTK+ que proveeix característiques bàsiques com missatges directes, notificacions de piulades i visualització de converses. " #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Característiques addicionals inclouen vista local de vídeos, múltiples imatges inline, Llistes, Filtres, múltiples comptes, etc." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Timeline genèrica al utilitzar Corebird" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Perfil de Twitter tipic" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Els paràmetres del compte es poden configurar" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Utilitza Twitter en una aplicació d'escriptori normal" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Conversa directa" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d nou missatge de %s" msgstr[1] "%d nous missatges de %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Hi ha un missatge directe nou de %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Missatges directes" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "No s'han pogut carregar els tweets" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Preferits" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Afegir un nou Filtre" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Filtres" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s ha repiulat %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s ha repiulat" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d nou tweet!" msgstr[1] "%d nous tweets!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Inici" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Llista" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Llistes" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Mostra els comptes configurats" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Redacteu una piulada" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Afegir un compte nou" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s ha mencionat a %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Mencions" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Perfil protegit" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Piula a @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Cerca" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "No s'ha pogut mostrar el tweet" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Repiulades" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Obre al navegador" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Origen" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Detalls de la piulada" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d missatge no llegit)" msgstr[1] "(%d missatges no llegits)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Suprimeix" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Bloquejar a %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Aquest tweet conté imatges marcades com a inapropiades" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Mostrar de totes maneres" #: src/util/Utils.vala:146 msgid "Now" msgstr "Ara" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Creeu-ne un" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "No s'ha pogut obrir %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "PIN incorrecte" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Ja s'està utilitzant el compte" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "No s'han trobat usuaris" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Seleccionar imatge" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Obrir" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Cancel·la" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Segueix" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Deixa de seguir" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "Copiar URL" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Desa" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "No s'ha trobat cap resultat" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Prova de nou" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Citar tweet" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Enrere" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "La imatge seleccionada és massa gran. La mida máxima per imatge es %'d MB" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Modifica el filtre" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Coincideix" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Modificar Snippet" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "El Snippet no ha de contenir espai en blanc" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "El Snippet ja existeix" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Ei, prova aquesta nova versió de #Corebird! (•◡•) / #cool #newisalwaysbetter" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Afegeix o elimina un usuari de la llista" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "No tens cap llista" #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Quant al Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Compte nou" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Sol·licita un PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Confirma" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Paràmetres del compte" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Nom" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Lloc web" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Inici automàtic" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Voleu eliminar aquest compte?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Envia" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Afegir imatge" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Usuaris" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Subscriviu-vos" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Cancel·la la subscripció" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Subscriptors:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Membres:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Creador:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Creat el:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Edita" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Mode:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Privat" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Públic" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Descripció" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Paràmetres" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Accessos directes" #: ui/menus.ui:15 msgid "About" msgstr "Quant a" #: ui/menus.ui:19 msgid "Quit" msgstr "Surt" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Afegeix un filtre nou" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Afegir un nou Snippet" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Paraula clau" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Reemplaçament" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Crea una llista nova" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Nom:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Crea" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Escriu un missatge directe" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Afegeix/Elimina de la llista" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Blocat" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Slienciat" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "S'han desactivat les repiulades" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Us segueix" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Piulades" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Seguidors" #: ui/profile-page.ui:326 msgid "Following" msgstr "Seguint" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "Mostrar contingut inline" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Mostrar sempre" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Amagar sempre" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Amagar en el timeline" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Desplaça automàticament amb piulades noves" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Activació de doble clic" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Interfície" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Quan hi hagi piulades noves" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Accions" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Quan hi hagi mencions noves" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Quan hi hagi missatges nous" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Mai" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Tots els" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Stack 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Stack 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Stack 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Stack 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Notificacions" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Avatars arredonits" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Eliminar hashtags posteriors" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Eliminar enllaços" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Amagar contingut inapropiat" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "No s'han configurat snippets" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Pots activar snippets escrivint la paraula clau i pressionant TAB" #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Snippets" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "General" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Escriure Tweet" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Mostrar paràmetres del compte" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Mostrar finestra de comptes" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Mostrar paràmetres de l'aplicació" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Canviar Topbar" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Enrere" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Endevant" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Anar a la pàgina #" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Tweets" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Retwittejar" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "M'agrada" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Respondre" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Citar" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Mostrar detalls" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Eliminar" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Inicia una conversa nova" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Amb:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Vés" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Cita" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Repiula la piulada" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Marca com a preferit" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Respon a la piulada" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Més" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Favorit" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Contesta" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Desbloqueja " #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Mostra els paràmetres per a aquest compte" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Obre a una finestra nova" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Anar al perfil" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Creat" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Subscrit a" corebird-1.7.4/po/ca@valencia.po000066400000000000000000000475661324604713000165020ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Pau Iranzo , 2014 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: Catalan (Valencian) (http://www.transifex.com/corebird/corebird/language/ca%40valencia/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ca@valencia\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Client del Twitter" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "El Corebird és un client del twitter natiu GTK+ que proveeix característiques bàsiques com missatges directes, notificacions de piulades i visualització de converses. " #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "" #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Conversa directa" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "" msgstr[1] "" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Hi ha un missatge directe nou de %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Missatges directes" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Preferits" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s ha repiulat %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s ha repiulat" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "" msgstr[1] "" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Inici" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Llista" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Llistes" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Mostra els comptes configurats" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Redacteu una piulada" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Mencions" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Perfil protegit" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Piula a @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Cerca" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Repiulades" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Obri al navegador" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Origen" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Detalls de la piulada" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "" msgstr[1] "" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Suprimeix" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "" #: src/util/Utils.vala:146 msgid "Now" msgstr "Ara" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Creeu-ne un" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "PIN incorrecte" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Ja s'està utilitzant el compte" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Cancel·la" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Segueix" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Deixa de seguir" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Guarda" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "No s'ha trobat cap resultat" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Prova de nou" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Arrere" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Modifica el filtre" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Coincideix" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Afig o elimina un usuari de la llista" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "" #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Quant al Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Compte nou" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Sol·licita un PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Confirma" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Paràmetres del compte" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Nom" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Lloc web" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Inici automàtic" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Voleu eliminar este compte?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Envia" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Usuaris" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Subscriviu-vos" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Cancel·la la subscripció" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Subscriptors:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Membres:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Creador:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Creat el:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Edita" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Mode:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Privat" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Públic" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Paràmetres" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "" #: ui/menus.ui:15 msgid "About" msgstr "Quant a" #: ui/menus.ui:19 msgid "Quit" msgstr "Ix" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Afig un filtre nou" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Crea una llista nova" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Nom:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Crea" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Escriu un missatge directe" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Afig/Elimina de la llista" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Blocat" #: ui/profile-page.ui:24 msgid "Muted" msgstr "" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "S'han desactivat les repiulades" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Vos segueix" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Piulades" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Seguidors" #: ui/profile-page.ui:326 msgid "Following" msgstr "Seguint" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Desplaça automàticament amb piulades noves" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Interfície" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Quan hi haja piulades noves" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Accions" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Quan hi haja mencions noves" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Quan hi haja missatges nous" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Mai" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Tots els" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Stack 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Stack 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Stack 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Stack 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Notificacions" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "" #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Inicia una conversa nova" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Amb:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Vés" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Cita" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Repiula la piulada" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Marca com a preferit" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Respon a la piulada" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Més" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Mostra els paràmetres per a este compte" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Obri a una finestra nova" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Creat" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Subscrit a" corebird-1.7.4/po/corebird.pot000066400000000000000000000445201324604713000162540ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: corebird 1.6\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "" #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images, " "Lists, Filters, multiple accounts, etc." msgstr "" #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "" #. TRANSLATORS: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "" msgstr[1] "" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "" msgstr[1] "" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "" msgstr[1] "" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "" #: src/util/Utils.vala:146 msgid "Now" msgstr "" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with " "the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "" #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "" #: ui/account-dialog.ui:88 msgid "Name" msgstr "" #: ui/account-dialog.ui:117 msgid "Website" msgstr "" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "" #: ui/menus.ui:15 msgid "About" msgstr "" #: ui/menus.ui:19 msgid "Quit" msgstr "" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "" #: ui/profile-page.ui:24 msgid "Muted" msgstr "" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "" #: ui/profile-page.ui:284 msgid "Followers" msgstr "" #: ui/profile-page.ui:326 msgid "Following" msgstr "" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "" #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "" corebird-1.7.4/po/de.po000066400000000000000000000557451324604713000147020ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # baedert , 2014-2015 # brainscript , 2016 # encbladexp , 2014 # fgreinus , 2015 # Heiko Adams , 2016 # jupaja, 2014-2015 # Matthias Kellermann , 2016 # Michael , 2014 # Peter L., 2015-2017 # Peter Schwindt , 2015 # Philip Gillißen , 2015 # Richard Schwab , 2014 # Trolli Schmittlauch , 2016 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:24+0000\n" "Last-Translator: baedert \n" "Language-Team: German (http://www.transifex.com/corebird/corebird/language/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Twitter Client" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird ist ein nativer GTK+ Twitter-Client, der wesentliche Funktionen wie Direktnachrichten (DMs), Tweet-Benachrichtigungen und Konversationen bietet." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Weitere Funktionalitäten beinhalten die lokale Anzeige von Videos, mehrere eingebettete Bilder, Listen, Filter, mehrere Accounts, etc." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Allgemeine Verlaufsansicht bei Nutzung von Corebird" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Typisches Twitter-Profil" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Account-Einstellungen können konfiguriert werden" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "Timm Bäder" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Nutzen Sie Twitter in einer normalen Desktopanwendung" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "Antwortend auf" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "und" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "und %d andere" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Direktnachrichten" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d neue Nachricht von %s" msgstr[1] "%d neue Nachrichten von %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Neue Direktnachricht von %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Direktnachrichten" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "Konnte Tweets nicht laden" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Favoriten" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Neuen Filter hinzufügen" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Filter" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s retweetete %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s tweetete" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d neuer Tweet!" msgstr[1] "%d neue Tweets!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Timeline" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Liste" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Listen" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Zeige konfigurierte Accounts" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Tweet erstellen" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Neuen Account hinzufügen" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s erwähnte %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Erwähnungen" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "Suspendierter Account" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Geschütztes Profil" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Tweet an @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "Geschütztes Profil" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Suchen" #: src/SearchPage.vala:401 msgid "Load More" msgstr "Mehr laden" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Konnte Tweet nicht anzeigen" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Retweets" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Im Browser öffnen" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Quelle" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Tweet Details" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d ungelesen)" msgstr[1] "(%d ungelesene)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Löschen" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "%s blockieren" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Das eingebettete Medium könnte sensibles Material beinhalten" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Trotzdem zeigen" #: src/util/Utils.vala:146 msgid "Now" msgstr "Jetzt" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dM" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dS" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "Noch keinen Twitter Account?" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Erstelle einen" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "Nicht authorisiert. Dies bedeutet meistens dass mit dem Twitter Server etwas nicht stimmt und Sie es später erneut versuchen sollten." #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Konnte %s nicht öffnen" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "Falsche PIN" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Account schon in Benutzung" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "Keine Benutzer gefunden" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Bild auswählen" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Öffnen" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Abbrechen" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Folgen" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Entfolgen" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "URL kopieren" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "Speichern unter…" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "Speichere Video" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "Speichere Bild" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Speichern" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "Lade…" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Keine Einträge gefunden" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Erneut versuchen" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "Banner auswählen" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "Bild entspricht nicht der Minimalgröße:" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "Mindestbreite: %d Pixel" msgstr[1] "Mindestbreite: %d Pixel" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "Mindesthöhe: %d Pixel" msgstr[1] "Mindesthöhe: %d Pixel" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "Wähle" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Zitiere Tweet" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "Ausgewählte Datei ist kein Bild" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Zurück" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "Das gewählte Bild ist zu groß. Die maximale Größe pro Bild beträgt %'d MB" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "Nur eine GIF Datei pro Tweet is erlaubt" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "Emoji einfügen" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Filter ändern" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Passt" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "Keine Übereinstimmung" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Schnipsel bearbeiten" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "Schnipsel kann nicht leer sein" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "Ersetzung kann nicht leer sein" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "Der Schnipsel darf keinen whitespace (z. B. Leerzeichen) enthalten" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "Der Schnipsel existiert schon" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Hey, schau dir die neue #Corebird version an! \\ (•◡•) / #cool #newisalwaysbetter" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Nutzer zu Liste hinzufügen oder entfernen" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Du hast keine Listen." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Über Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Neuer Account" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "Um Corebird zu authentifizieren wird ein PIN von twitter.com mit dem Account benötigt, der verwendet werden soll." #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "PIN anfordern" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "PIN von twitter.com eingeben:" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "PIN" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Bestätigen" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Account Einstellungen" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Name" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Webseite" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Autostart" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Willst Du diesen Account wirklich löschen?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "Smileys & Menschen" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "Körper & Bekleidung" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "Tiere & Natur" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "Essen & Trinken" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "Reisen & Orte" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "Aktivitäten" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "Objekte" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "Symbole" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "Flaggen" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "Keine Resultate gefunden" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "Versuche einen anderen Suchbegriff" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Senden" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Bild anhängen" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "Zeige Lieblingsbilder" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Benutzer" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Abonnieren" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Abbestellen" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Abonnenten" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Mitglieder:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Ersteller:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Erstellt um:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Bearbeiten" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Modus:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Privat" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Öffentlich" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Beschreibung" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Einstellungen" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Tastaturkürzel" #: ui/menus.ui:15 msgid "About" msgstr "Über" #: ui/menus.ui:19 msgid "Quit" msgstr "Beenden" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Neuen Filter hinzufügen" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Neuen Schnipsel anlegen" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Stichwort" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Ersetzung" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Neue Liste erstellen" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Name:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Erstellen" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Direktnachricht schreiben" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Hinzufügen / Entfernen aus der Liste" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Blockiert" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Stummgeschaltet" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Retweets deaktiviert" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Folgt dir" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tweets" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Followers" #: ui/profile-page.ui:326 msgid "Following" msgstr "Folgt" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "Eingebettete Medien anzeigen" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Immer anzeigen" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Immer verstecken" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Aus Timeline ausblenden" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Automatisch scrollen bei neuen Tweets" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Aktivieren per Doppelklick" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Oberfläche" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Bei neuen Tweets" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Aktionen" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Bei neuen Erwähnungen:" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Bei neuen Nachrichten" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Niemals" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Jeder" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Sammle 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Sammle 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Sammle 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Sammle 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Benachrichtigungen" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Runde Avatare" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Entferne nachfolgende Hashtags" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Entferne Medienverweise" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Verstecke unangebrachte Inhalte" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Keine Schnipsel eingerichtet" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Du kannst Schnipsel auswählen, in dem du das Stichwort tippst und dann Tabulator drückst" #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Schnipsel" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Allgemein" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Tweet erstellen" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Öffne Konto-Einstellungen" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Öffne Konto-Popover" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Öffne Einstellungen" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Symbolleiste umschalten" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Zurück" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Weiter" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Gehe zu Seite n" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Tweets" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Retweeten" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Favoriten" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Antworten" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Zitieren" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Zeige Details" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Löschen" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "Verfassen" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "Zeige Emoji-Auswahl" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Neue Unterhaltung beginnen" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Mit:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Los" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Zitieren" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Tweet retweeten" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Favorisieren" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Auf Tweet antworten" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Mehr" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Favoriten" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Antworten" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Entsperren" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Zeige die Einstellungen dieses Accounts" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "In neuem Fenster öffnen" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Zum Profil gehen" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Erstellt" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Anmelden zu" corebird-1.7.4/po/en_AU.po000066400000000000000000000457161324604713000152760ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Ryan Lerch , 2014 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: English (Australia) (http://www.transifex.com/corebird/corebird/language/en_AU/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: en_AU\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "" #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "" #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "" msgstr[1] "" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Direct Messages" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Favourites" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s retweeted %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s tweeted" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "" msgstr[1] "" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Home" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Lists" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Compose Tweet" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Mentions" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Protected profile" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Tweet to @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Search" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Open in Browser" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Source" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "" msgstr[1] "" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Delete" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "" #: src/util/Utils.vala:146 msgid "Now" msgstr "Now" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "Wrong PIN" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Cancel" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Follow" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Unfollow" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Save" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "No entries found" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "" #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "About Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Confirm" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "" #: ui/account-dialog.ui:88 msgid "Name" msgstr "" #: ui/account-dialog.ui:117 msgid "Website" msgstr "" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Send" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Users" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Subscribe" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Unsubscribe" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Subscribers:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Members:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Creator:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Created at:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Edit" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Mode:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Private" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Public" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Settings" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "" #: ui/menus.ui:15 msgid "About" msgstr "About" #: ui/menus.ui:19 msgid "Quit" msgstr "Quit" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Create New List" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Name:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Create" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Write Direct Message" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Add to/Remove from List" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "" #: ui/profile-page.ui:24 msgid "Muted" msgstr "" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Follows you" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tweets" #: ui/profile-page.ui:284 msgid "Followers" msgstr "" #: ui/profile-page.ui:326 msgid "Following" msgstr "" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Actions" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Never" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Every" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Stack 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Stack 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Stack 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Stack 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "" #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Start new conversation" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "With:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Go" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Quote" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Retweet tweet" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Favorite tweet" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Reply to tweet" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "More" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Created" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Subscribed to" corebird-1.7.4/po/eo.po000066400000000000000000000541371324604713000147070ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Iris Ilexiris , 2017-2018 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2018-02-06 18:45+0000\n" "Last-Translator: Iris Ilexiris \n" "Language-Team: Esperanto (http://www.transifex.com/corebird/corebird/language/eo/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: eo\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Tviterokliento" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird estas denaska GTK+ kliento por Tvitero. Ĝi enhavas gravajn eblecojn—ekzemple tujmesaĝoj, tvitavizoj, kaj vidigo de interparoloj." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Aliaj eblecoj estas vidigi filmeton kaj plurajn bildojn loke. Ankaŭ, listoj, filtriloj, pluraj kontoj, ktp." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Malpreciza kronologovidigo kiam Corebird uzata" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Malpreciza Tviteroprofilo" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Konto agordoj povas agordanta" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "Timm Bäder" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Uzi Tviteron simila al normala komputerprogramo" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;tvitero;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "Respondas al" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "kaj" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "kaj %d aligentuloj" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Tujinterparolo" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d nova mesaĝo el %s" msgstr[1] "%d novaj mesaĝoj el %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Nova tujmesaĝo el %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Tujmesaĝoj" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "Nepovus elŝuti tvitojn" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Favoratoj" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Aldoni novan filtrilon" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Filtriloj" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s retvitis %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s tvitis" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d nova tvito!" msgstr[1] "%d novaj tvitoj!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Hejmo" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Listo" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Listoj" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Montri agordajn kontojn" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Skribi tviton" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Aldoni novan konton" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s menciis %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Mencioj" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "Ĉi tiu konto ĉesita" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Karakterizon malpublikita" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Tviti al @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "Karakterizon malpublikita" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Serĉi" #: src/SearchPage.vala:401 msgid "Load More" msgstr "Ŝargi pli" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Ne povis montri tviton" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Retvitoj" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Malfermi en retumilo" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Fonto" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Tvitodetaloj" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d nelegita)" msgstr[1] "(%d nelegitaj)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Forigi" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Blokigi %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Tio ĉi tvito povus havi malkonvenajn bildojn." #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Montri iel" #: src/util/Utils.vala:146 msgid "Now" msgstr "Nun" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "Ĉu vi ne havas Tviterokonton jam?" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Krei konton" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "Vi ne estas rajtigitan. Plejparto, ĉi tiu signifas eraro kun la Tvitero-serviloj. Provi denove malfruan." #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Ne povis malfermi %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "Malĝustan PIN-on" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Konto jam uzas" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "Uzantojn netrovitajn" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Elekti bildon" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Malfermi" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Nuligi" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Postiri" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Malpostiri" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "Kopii URL-on" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "Konservi kiel…" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "Konservi filmeton" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "Konservi bildon" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Konservi" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "Ŝargas…" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Nenio elementoj trovitaj" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Reprovi" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "Elekti gazetkapon" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "Bildo ne konsidas minimumajn grandobezonojn" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "Minimuma larĝo: %d bildero" msgstr[1] "Minimuma larĝo: %d bilderoj" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "Minimuma alto: %d bildero" msgstr[1] "Minimuma alto: %d bilderoj" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "Elekti" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Citi tviton" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "La elektita dosiero ne estas bildo." #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Reiri" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "La elektita bildon estas tro granda. La maksimuma dosierlargo por imagoj estas %'d MB." #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "Nur unu GIF po tvito permesitas." #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "Enmeti miensimbolo" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Redakti filtrilon" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Egalas" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "Ne egalas" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Redakti kodaĵon" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "Kodaĵo ne povas esti malplena" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "Anstataŭtigo ne povas esti malplena" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "Kodaĵo ne povas enhavi blankspacon" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "Kodaĵo ekzistas jam" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Saluton! Vidu ĉi tiun novan version de #Corebird! \\ (•◡•) / #mojose #novaĉiamplibonas" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Aldoni/forigi uzanton el la listo" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Vi havas nenio listojn." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Pri Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Nova konto" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "Por verigi Corebird-on, vi bezonas PIN-on el twitter.com por la konto vi deziras aldoni." #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Peti PIN-on" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "Entajpi PIN-on el twitter.com malsupre:" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "PIN" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Konfirmi" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Kontoagordoj" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Nomo" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Retpaĝo" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Aŭtomata komenci" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Ĉu vi vere volas forigi ĉi tiun konton?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "Miensimboloj kaj homoj" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "Korpo kaj vestoj" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "Bestoj kaj naturo" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "Nutraĵo" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "Vojaĝo kaj lokoj" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "Agadoj" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "Objektoj" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "Simboloj" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "Flagoj" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "Nenio trovitaj resultoj" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "Provu alian serĉon" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Sendi" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Aldoni bildon" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "Montri favoratbildojn" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Uzantoj" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Subskribi" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Malsubskribi" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Subskriboj:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Membroj:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Verkanto:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Kreita je:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Redakti" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Moduso:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Malpublika" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Publika" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Priskribo" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Agordoj" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Kurtvojoj" #: ui/menus.ui:15 msgid "About" msgstr "Pri" #: ui/menus.ui:19 msgid "Quit" msgstr "Ĉesi" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Aldoni novan filtrilon" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Aldoni novan kodaĵon" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Ĉefvorto" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Anstataŭigo" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Krei novan liston" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Nomo:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Krei" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Skribi tujmesaĝon" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Aldoni/forigi el listo" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Blokigita" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Mutigita" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Revtitoj malebligita" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Postiras vin" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tvitoj" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Postiroj" #: ui/profile-page.ui:326 msgid "Following" msgstr "Postiras" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "Montri enpaĝajn mediojn" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Montri ĉiam" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Kaŝi ĉiam" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Kaŝi en kronologio" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Rulumi automatan je novaj tvitoj" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Aktivigi per duoblan klakon" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Fasado" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Kiam okazas novan tviton" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Agoj" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Kiam okazas novan mencion" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Kiam okazas novan mesaĝon" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Neniam" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Ĉia" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Stakos 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Stakos 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Stakos 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Stakos 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Avizoj" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Rondajn profilbildojn" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Forigi malantaŭirajn haketojn" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Forigi retligojn por aŭdvidaĵoj" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Kaŝi malkonvenan enhavon" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Nenio kodaĵoj agordis." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Vi povas puŝu la taban klavon ekspansii kodaĵojn." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Kodaĵoj" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Ĝenerala" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Skribi tviton" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Montri kontagordojn" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Montri ŝvebajn kontojn konsilojn" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Montri programagordojn" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Baskuligi supronavigadon" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Reiri" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Antaŭiri" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Iri al la n-a paĝo" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Tvitoj" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Retviti" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Favorati" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Respondi" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Citi" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Montri detalojn" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Forigi" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "Skribi" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "Montri miensimbolilon" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Komenci novan interparolon" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Kun:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Iri" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Citi" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Retviti tviton" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Favorati tviton" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Respondi al tvito" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Pli" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Favorati" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Respondi" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Malblokigi" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Montri agordojn por ĉi tiu konto" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Malfermi en nova fenestro" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Eniri al karakterizo" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Kreita" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Subskribis al" corebird-1.7.4/po/es.po000066400000000000000000000534441324604713000147130ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Alejandro Moreno , 2016 # auroszx , 2014 # baedert , 2016 # Carlos C Soto , 2015 # Guillem Arias , 2016 # Jose G. Jimenez S. , 2014-2015 # Jose Manuel Calero Gonzalez , 2014-2015 # Sebastian Reyes , 2016 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: Spanish (http://www.transifex.com/corebird/corebird/language/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: es\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Cliente Twitter" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird es un cliente nativo GTK+ para Twitter que provee funciones útiles como Mensajes Directos (DMs), notificación de tweets, y ver conversaciones." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Las características adicionales incluyen la visualización local de los vídeos, múltiples imágenes en línea, listas, filtros, múltiples cuentas, etc." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Vista de timeline genérica al usar Corebird" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Perfil de Twitter típico" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Los ajustes de la cuenta pueden ser configurados" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Usa Twitter desde una aplicación de escritorio normal" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Conversación directa" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d nuevo mensaje de %s" msgstr[1] "%d nuevos mensajes de %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Nuevo mensaje directo de %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Mensajes directos" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "No se pudieron cargar los tweets" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Favoritos" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Añadir nuevo filtro" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Filtros" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s retuiteado %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s tuiteado" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d nuevo Tweet!" msgstr[1] "%d nuevos Tweets!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Inicio" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Lista" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Listas" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Mostrar cuentas configuradas" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Escribir Tweet" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Añadir nueva cuenta" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s mencionó a %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Menciones" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "perfil protegido" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Tweet para @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Buscar" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "No se puede mostrar este Tweet" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Retweets" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Abrir en navegador" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Fuente" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Detalles del tweet" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d sin leer)" msgstr[1] "(%d sin leer)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Eliminar" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Bloquear a %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Este tweet contiene imágenes que pueden herir la sensibilidad de algunas personas" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Mostrar de todas formas" #: src/util/Utils.vala:146 msgid "Now" msgstr "Ahora" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Crear una" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "No se puede abrir %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "PIN incorrecto" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "La cuenta ya está en uso" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "No se han encontrado usuarios" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Seleccionar imagen" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Abrir" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Cancelar" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Seguir" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Dejar de seguir" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "Copiar URL" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Guardar" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "No se han encontrado entradas" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Reintentar" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Citar tweet" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Atrás" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "La imagen seleccionada es demasiado grande. El tamaño máximo por imagen es %'d MB" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Modificar Filtro" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Concuerda" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Modificar snippet" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "El snippet puede no contener espacio en blanco" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "El snippet ya existe" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "¡Ey, revisa esta nueva versión de #Codebird! \\ (•◡•) / #cool #newisalwaysbetter" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Agregar o Quitar usuario desde la lista" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "No tienes listas." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Sobre Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Nueva cuenta" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Solicitar PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Confirmar" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Configuración de cuentas" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Nombre" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Página Web" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Autoiniciar" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "¿Seguro que quieres eliminar esta cuenta?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Enviar" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Añadir imagen" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Usuarios" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Suscribirse" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Anular suscripción" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Suscriptores:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Miembros:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Creador:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Creada el:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Editar" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Modo:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Privado" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Público" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Descripción" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Preferencias" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Atajos" #: ui/menus.ui:15 msgid "About" msgstr "Acerca de" #: ui/menus.ui:19 msgid "Quit" msgstr "Salir" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Añadir nuevo filtro" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Añadir nuevo fragmento" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Clave" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Reemplazo" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Crear lista nueva" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Nombre:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Crear" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Enviar mensaje directo" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Añadir/Eliminar de la lista" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Bloqueado" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Slienciado" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Retweets desactivados" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Te sigue" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tweets" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Seguidores" #: ui/profile-page.ui:326 msgid "Following" msgstr "Siguiendo" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "Mostrar media inline" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Mostrar siempre" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Ocultar siempre" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Esconder en la timeline" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Desplazar automaticamente al cargar nuevos tweets" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Activación Doble-click" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Interfaz" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Para tweets nuevos" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Acciones" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Para menciones nuevas" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Para mensajes nuevos" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Nunca" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Todos" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Cada 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Cada 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Cada 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Cada 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Notificaciones" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Avatares Redondeados" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Eliminar hashtags posteriores" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Eliminar enlaces" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Ocultar contenido que pueda herir la sensibilidad de algunas personas" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "No hay fragmentos configurados" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Puedes activar los fragmentos escribiendo la clave y presionando TAB" #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Fragmentos" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "General" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Escribir Tweet" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Mostrar ajustes de la cuenta" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Mostrar ventana de cuentas" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Mostrar ajustes de la aplicación" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Alternar TopBar" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Volver" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Continuar" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Ir a la página #" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Tweets" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Retwittear" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Me gusta" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Responder" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Citar" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Mostrar detalles" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Eliminar" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Iniciar conversación nueva" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Con:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Ir" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Citar" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Hacer retweet" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Añadir a favoritos" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Responder" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Mas" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Favorito" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Responder" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Desbloqueo" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Mostrar ajustes de la cuenta" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Abrir en una nueva ventana" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Ir al perfil" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Creada" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Suscrito a" corebird-1.7.4/po/es_419.po000066400000000000000000000470721324604713000153100ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Enrique GF , 2017 # esteban cap , 2016 # Guillermo Peralta , 2015 # Jose G. Jimenez S. , 2014 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: Spanish (Latin America) (http://www.transifex.com/corebird/corebird/language/es_419/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: es_419\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Cliente de Twitter" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebid es un cliente nativo GTK+ de twitter que permite realizar actividades diarias como enviar mensajes directos (MD), notificaciones de tweets, vista de menciones, y más." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Las características adicionales incluyen visualización de videos e imágenes en la aplicación, así como uso de listas, filtros, multiples cuentas, etc." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Vista general del timeline al usar Corebird" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Los ajustes de la cuenta pueden ser configurados." #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d nuevos Mensajes de %s" msgstr[1] "%d nuevos Mensajes de %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Nuevo mensaje directo de %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Mensajes Directos" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "No fue posible cargar los tweets" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Favoritos" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s retweeteados %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s tweeteado" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d nuevos Tweets!" msgstr[1] "%d nuevos Tweets!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Inicio" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Lista" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Listas" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Escribir Tweet" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Menciones" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Buscar" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Retweets" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Abrir en el Navegador" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Fuente" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "" msgstr[1] "" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "" #: src/util/Utils.vala:146 msgid "Now" msgstr "Ahora" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Crear una" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "PIN Incorrecto" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "La cuenta ya está en uso" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Cancelar" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Seguir" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Dejar de seguir" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Guardar" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "No se encontraron entradas" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Reintentar" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Modificar Filtro" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Coincidencias" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Agregar o Quitar usuario desde la lista" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Tu no tienes listas." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Acerca de Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "" #: ui/account-dialog.ui:88 msgid "Name" msgstr "" #: ui/account-dialog.ui:117 msgid "Website" msgstr "" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Enviar" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Configuración" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "" #: ui/menus.ui:15 msgid "About" msgstr "Acerca de" #: ui/menus.ui:19 msgid "Quit" msgstr "Salir" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "" #: ui/profile-page.ui:24 msgid "Muted" msgstr "" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tweets" #: ui/profile-page.ui:284 msgid "Followers" msgstr "" #: ui/profile-page.ui:326 msgid "Following" msgstr "" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Nunca" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Notificaciones" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "" #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Creado" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Subscrito a" corebird-1.7.4/po/es_MX.po000066400000000000000000000512721324604713000153140ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Enrique GF , 2017 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: Spanish (Mexico) (http://www.transifex.com/corebird/corebird/language/es_MX/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: es_MX\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Cliente de Twitter" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird es un cliente nativo de twitter el cual provee características vitales como Mensajes Directos (DM's), notificaciones de tuits, vistas de conversación." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Características adicionales incluye visualización de vídeos local, imágenes múltiples, Listas, Filtros, múltiples cuentas, etc." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Conversación Directa" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d nuevo mensaje de %s" msgstr[1] "%d nuevos mensajes de %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Nuevo mensaje directo de %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Mensajes Directos" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "No fue posible cargar los tweets" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Favoritos" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s retuiteado %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s tuiteado" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d nuevo tuit" msgstr[1] "%d nuevos tuits" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Inicio" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Lista" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Listas" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Mostrar cuentas configuradas" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Escribir Tuit" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s mencionó a %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Menciones" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Perfil protegido" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Tuitear a @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Buscar" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "No se puede mostrar el tuit" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Retuits" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Abrir en Navegador" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Origen" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Detalles de Tuit" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "%d sin leer" msgstr[1] "%d sin leer" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Borrar" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Bloquear a %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "" #: src/util/Utils.vala:146 msgid "Now" msgstr "Ahora" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Crear Una" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "No se puede abrir %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "PIN Incorrecto" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Cuenta en uso" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Cancelar" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Seguir" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Dejar de Seguir" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Guardar" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "No se encontraron entradas" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Reintentar" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Citar Tuit" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Regresar" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Modificar Filtro" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Coincidencias" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "¡Hey, revisa esta nueva versión de #Corebird! \\ (•◡•) / #cool #newisalwaysbetter" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Agregar o Quitar Usuario de la Lista" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "No tienes listas." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Acerca de Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Nueva Cuenta" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Solicitar PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Confirmar" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Parámetros de cuenta" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Nombre" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Sitio Web" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Autoiniciar" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "¿Realmente quieres borrar esta cuenta?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Enviar" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Agregar imágen" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Usuarios" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Suscribir" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "De suscribir " #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Suscriptores:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Mienbros:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Creador:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Creado en:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Editar" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Modo:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Privado" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Público" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Parámetros" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Atajos" #: ui/menus.ui:15 msgid "About" msgstr "Acerca de" #: ui/menus.ui:19 msgid "Quit" msgstr "Quitar" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Agregar nuevo filtro" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Agregar Nuevo Snippet" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Keyword" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Reemplazar" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "crear Nueva Lista" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Nombre:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Crear" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Escribir Mensaje Directo" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Agregar/quitar de la Lista" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Bloqueado" #: ui/profile-page.ui:24 msgid "Muted" msgstr "" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Retuits desactivados" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Siguiéndote" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tuits" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Seguidores" #: ui/profile-page.ui:326 msgid "Following" msgstr "Siguiendo" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Auto avance en nuevos tuits" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Activación doble click" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Interface" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Nuevos Tuits" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Acciones" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Nuevas Menciones" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Nuevos Mensajes" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Nunca" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Siempre" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Mostrar 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Mostrar 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Mostrar 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Mostrar 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Notificaciones" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Avatares redondos" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Remover hashtags de rastreo" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Remover links de contenido" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Ocultar contenido inapropidado" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "No hay snippets configurados" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Puedes activar snippets escribiendo una palabra y presionando la tecla TAB" #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Snippets" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "General" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Escribir Tuit" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Mostrar Parámetros de Cuenta" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Mostrar Cuentas" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Mostrar Parámetros de la Aplicación" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Cambiar Barra Superior" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "ir Atrás" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "ir Adelante" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "ir a Página" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Tuits" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Retuiteado" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Favorito" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Responder" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Mostrar Detalles" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Borrar" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "iniciar nueva conversación" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Con:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "ir" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Citar" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Retuitear" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Favorito" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Responder" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Más" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Favorito" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Respuesta" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Desbloquear" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Mostrar parámetros de esta cuenta" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Abrir en nueva ventana" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Creado" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Suscribir a" corebird-1.7.4/po/es_VE.po000066400000000000000000000510641324604713000153010ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Jerry Anselmi , 2015 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: Spanish (Venezuela) (http://www.transifex.com/corebird/corebird/language/es_VE/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: es_VE\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Cliente de Twitter" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird es un cliente nativo GTK+ de Twitter que proporciona funciones vitales tales como mensajes directos (DM), notificaciones, vistas de conversación." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Las características adicionales incluyen la visión local de los vídeos, múltiples imágenes en línea, listas, filtros, múltiples cuentas, etc." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Conversación directa" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d nuevo Mensaje de %s" msgstr[1] "%d nuevos Mensajes de %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Nuevo mensaje directo de %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Mensajes directos" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Favoritos" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s retweeteado %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s tweeteado" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d nuevo Tweet!" msgstr[1] "%d nuevos Tweets!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Inicio" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Lista" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Listas" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Mostrar cuentas configuradas" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Hacer un Tweet" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Menciones" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Perfil protegido" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Tweet a @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Buscar" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "No se pudo mostrar el tweet" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Retweets" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Abrir en un navegador" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Fuente" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Detalles del tweet" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d sin leer)" msgstr[1] "(%d sin leer)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Borrar" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "" #: src/util/Utils.vala:146 msgid "Now" msgstr "Ahora" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Crear una" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "No se pudo abrir %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "PIN incorrecto" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "La cuenta ya esta siendo usada" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Cancelar" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Seguir" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Dejar de seguir" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Guardar" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "No se encontraron entradas" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Reintentar" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Cita de tweet" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Volver" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Modificar filtro" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Coincidencias" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Hey, echa un vistazo a esta nueva versión #Corebird! \\ (• ◡ •) / #cool #newisalwaysbetter" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Agregar o eliminar usuario de la lista" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "No tienes listas." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Acerca de Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Nueva cuenta" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Solicitud de PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Confirmar" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Configuraciones de la cuenta" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Nombre" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Sitio Web" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "inicio automatico" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "¿Estas seguro de que quieres eliminar esta cuenta?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Enviar" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "usuarios" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Suscribirse" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Darse de baja" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Suscriptores:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Miembros:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Creador:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Creado en:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Editar" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Modo:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Privado" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Publico" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Ajustes" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "" #: ui/menus.ui:15 msgid "About" msgstr "Acerca de" #: ui/menus.ui:19 msgid "Quit" msgstr "Salir" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Agregar un nuevo filtro" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Agregar un nuevo fragmento" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Palabra clave" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Reemplazo" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Crear una nueva lista" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Nombre:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Crear" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Escribir un mensaje directo" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Agregar/Eliminar de la lista" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Bloqueado" #: ui/profile-page.ui:24 msgid "Muted" msgstr "" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Retweets desabilitado" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Le sigue" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "trailing hashtags" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Seguidores" #: ui/profile-page.ui:326 msgid "Following" msgstr "Siguiendo" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Desplazamiento automático con nuevos tweets" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Activación con doble clic" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Interfaz" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "En nuevos Tweets" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Acciones" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "En nuevas menciones" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "En nuevos mensajes" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Nunca" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Cada" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Grupo de 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Grupo de 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Grupo de 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Grupo de 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Notificaciones" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Redondear aavaataares" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Eliminar trailing hashtags" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Eliminar enlaces de imagenes, videos y audios" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "No hay fragmentos configurados." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Puede activar fragmentos escribiendo la palabra clave y presionar TAB." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Fragmento" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Iniciar una nueva conversación" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Con:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Ir" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Cita" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Retweet tweet" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Tweet favorito" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Responder tweet" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Más" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Favorito" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Responder" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Desbloquear" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Mostrar los ajustes de esta cuenta" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Abrir en una nueva ventana" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Creado" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Suscrito a" corebird-1.7.4/po/fa.po000066400000000000000000000473401324604713000146700ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # amraei , 2014 # Morteza Parvini , 2016 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: Persian (http://www.transifex.com/corebird/corebird/language/fa/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fa\n" "Plural-Forms: nplurals=1; plural=0;\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "" #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "" #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "پیام مستقیم" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "علاقه مندی ها" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s توییت شد" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "خانه" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "لیست ها" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "توییت کنید" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "اشاره ها" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "پروفایل محافظت شده" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "جستجو" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "توییت های مجدد" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "توی مرورگر باز بشه" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "منبع" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "حذف" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "" #: src/util/Utils.vala:146 msgid "Now" msgstr "اکنون" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "PIN اشتباهه" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "انصراف" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "دنبال کردن" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "دنبال نکنید" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "ذخیره سازی" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "چیزی یافت نشد!" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "بازگشت" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "مطابقت داره" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "اضافه کردن به/ حذف از لیست" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "" #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "درباره Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "تایید" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "تنظیمات حساب" #: ui/account-dialog.ui:88 msgid "Name" msgstr "نام" #: ui/account-dialog.ui:117 msgid "Website" msgstr "وب‌سایت" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "آیا از پاک‌کردن این حساب، اطمینان دارید؟" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "ارسال کن" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "کاربران" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "مشترک شو" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "لغو اشتراک" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "مشترکین:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "اعضا:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "ایجاد کننده" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "ایجاد شده در:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "ویرایش" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "حالت:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "خصوصی" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "عمومی" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "تنظیمات" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "" #: ui/menus.ui:15 msgid "About" msgstr "درباره" #: ui/menus.ui:19 msgid "Quit" msgstr "خروج" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "کلیدواژه" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "یه لیست جدید ایجاد کنید" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "نام" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "ایجاد کنید" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "یه پیام مستقیم بنویسید" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "اضافه کردن به/ حذف از لیست" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "بلاک شده" #: ui/profile-page.ui:24 msgid "Muted" msgstr "" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "شمال رو دنبال می کنه" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "توییت ها" #: ui/profile-page.ui:284 msgid "Followers" msgstr "" #: ui/profile-page.ui:326 msgid "Following" msgstr "" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "رابط کاربری" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "هرگز" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "هرکسی" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "اعلان ها" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "" #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "عمومی" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "توییت کنید" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "نمایش تنظیمات حساب" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "نمایش تنظیمات برنامه" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "بازتوییت" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "پاسخ دادن" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "جزییات" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "همراه با:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "برو" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "نقل قول" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "بازنشر توییت" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "توییت های مورد علاقه" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "پاسخ به توییت" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "بیشتر" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "ایجاد شد" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "مشترک شد" corebird-1.7.4/po/fi.po000066400000000000000000000547571324604713000147120ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Jiri Grönroos , 2014-2017 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 17:13+0000\n" "Last-Translator: Jiri Grönroos \n" "Language-Team: Finnish (http://www.transifex.com/corebird/corebird/language/fi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fi\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Twitter-sovellus" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird on GTK+:lla toteutettu Twitter-sovellus, joka tarjoaa muun muassa yksityisviestit, ilmoitukset twiiteistä ja keskustelunäkymän." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Lisäominaisuuksiin kuuluu muun muassa videoiden katselu suoraan sovelluksesta, useat upotetut kuvat, listat, suodattimet, useat tilit jne." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Yleinen aikajana Corebirdissä" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Tyypillinen Twitter-profiili" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Tilin asetuksia on mahdollista muuttaa" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "Timm Bäder" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Käytä Twitteriä tavallisella työpöytäsovelluksella" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "Vastataan taholle" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "ja" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "ja %d muulle" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Yksityiskeskustelu" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d uusi viesti käyttäjältä %s" msgstr[1] "%d uutta viestiä käyttäjältä %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Uusi yksityinen viesti käyttäjältä %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Yksityisviestit" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "Twiittien lataaminen epäonnistui" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Suosikit" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Lisää uusi suodatin" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Suodattimet" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s twiittasi uudelleen %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s twiittasi" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d uusi twiitti!" msgstr[1] "%d uutta twiittiä!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Koti" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Lista" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Listat" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Näytä määritetyt tilit" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Kirjoita twiitti" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Lisää uusi tili" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s mainitsi %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Maininnat" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "Jäädytetty tili" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Suojattu profiili" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Twiittaa käyttäjälle @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "Suojattu profiili" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Etsi" #: src/SearchPage.vala:401 msgid "Load More" msgstr "Lataa lisää" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Twiitin näyttäminen epäonnistui" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Uudelleentwiittaukset" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Avaa selaimessa" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Lähde" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Twiitin tiedot" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d lukematon)" msgstr[1] "(%d lukematonta)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Poista" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Estä %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Tämä twiitti sisältää asiattomaksi merkittyjä kuvia" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Näytä silti" #: src/util/Utils.vala:146 msgid "Now" msgstr "Nyt" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%d min" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%d t" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "Ei vielä Twitter-tiliä?" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Luo uusi tili" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "Ei valtuutusta. Yleensä tämä tarkoittaa, että Twitter-palvelimilla on ongelmia, joten yritä pian uudelleen" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Kohteen %s avaaminen ei onnistunut" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "Väärä PIN" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Tili on jo käytössä" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "Käyttäjiä ei löytynyt" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Valitse kuva" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Avaa" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Peru" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Seuraa" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Lopeta seuraaminen" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "Kopioi osoite" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "Tallenna nimellä…" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "Tallenna video" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "Tallenna kuva" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Tallenna" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "Ladataan…" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Kohteita ei löytynyt" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Yritä uudelleen" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "Valitse bannerikuva" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "Kuva ei täytä vähimmäisvaatimuksia koon suhteen:" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "Vähimmäisleveys: %d pikseli" msgstr[1] "Vähimmäisleveys: %d pikseliä" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "Vähimmäiskorkeus: %d pikseli" msgstr[1] "Vähimmäiskorkeus: %d pikseliä" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "Valitse" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Lainaa twiittiä" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "Valittu tiedosto ei ole kuva." #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Takaisin" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "Valittu kuva on liian suuri. Kuva voi olla kooltaan korkeintaan %'d Mt." #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "Vain yksi GIF-tiedosto per twiitti on sallittu." #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "Lisää emoji" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Muokkaa suodatinta" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Vastaa" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "Ei vastaa" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Muokkaa tekstileikettä" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "Tekstileike ei voi olla tyhjä" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "Korvike ei voi olla tyhjä" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "Tekstileike ei voi sisältää tyhjätilaa" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "Tekstileike on jo olemassa" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Hei, #Corebird'in uusi versio on nyt saatavilla! \\ (•◡•) / #cool #newisalwaysbetter" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Lisää tai poista käyttäjä listalta" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Sinulla ei ole listoja." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Tietoja - Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Uusi tili" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "Asettaaksesi Corebirdin tunnistautumisen tulee sinun noutaa twitter.comista PIN sille tilille, jonka haluat lisätä" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Pyydä PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "Kirjoita PIN twitter.comista alapuolelle:" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "PIN" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Vahvista" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Tilin asetukset" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Nimi" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Verkkosivusto" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Käynnistä automaattisesti" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Haluatko varmasti poistaa tämän tilin?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "Hymiöt ja ihmiset" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "Ruumis ja vaatetus" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "Eläimet ja luonto" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "Ruoka ja juoma" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "Matkustaminen ja paikat" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "Toimet" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "Objektit" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "Symbolit" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "Liput" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "Tuloksia ei löytynyt" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "Yritä eri hakuehtoja" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Lähetä" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Lisää kuva" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "Näytä suosikkikuvat" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Käyttäjät" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Tilaa" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Lopeta tilaus" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Tilaajat:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Jäsenet:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Tehnyt:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Tehty:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Muokkaa" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Tila:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Yksityinen" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Julkinen" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Kuvaus" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Asetukset" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Pikanäppäimet" #: ui/menus.ui:15 msgid "About" msgstr "Tietoja" #: ui/menus.ui:19 msgid "Quit" msgstr "Lopeta" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Lisää uusi suodatin" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Lisää uusi tekstileike" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Avainsana" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Korvike" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Luo uusi lista" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Nimi:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Luo" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Kirjoita yksityisviesti" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Lisää listalle tai poista listalta" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Estetty" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Mykistetty" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Uudelleentwiittaukset poistettu käytöstä" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Seuraa sinua" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Twiitit" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Seuraajia" #: ui/profile-page.ui:326 msgid "Following" msgstr "Seuraa" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "Näytä sisennetty media" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Näytä aina" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Piilota aina" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Piilota aikajanalla" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Vieritä automaattisesti uusien twiittien myötä" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Kaksoisnapsautuksen aktivointi" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Käyttöliittymä" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Uusissa twiiteissä" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Toiminnot" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Uusissa maininnoissa" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Uusissa viesteissä" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Ei koskaan" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Joka" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "5 pino" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "10 pino" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "25 pino" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "50 pino" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Ilmoitukset" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Pyöreät avatarit" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Poista lopussa olevat hashtagit" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Poista medialinkit" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Piilota asiaton sisältö" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Tekstileikkeitä ei ole määritetty." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Voit aktivoida tekstileikkeet kirjoittamalla avainsanan ja painamalla TAB." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Tekstileikkeet" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Yleiset" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Twiittaa" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Näytä tilin asetukset" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Näytä tili-ikkuna" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Näytä sovelluksen asetukset" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Yläpalkki päälle/pois" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Edellinen" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Seuraava" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Siirry sivulle ..." #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Twiitit" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Twiittaa uudelleen" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Lisää suosikkeihin" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Vastaa" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Lainaa" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Näytä tiedot" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Poista" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "Kirjoita" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "Näytä emoji-valitsin" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Aloita uusi keskustelu" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Osallistujat:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Aloita" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Lainaa" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Twiittaa uudelleen" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Lisää twiitti suosikkeihin" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Vastaa twiittiin" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Lisää" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Suosikki" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Vastaa" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Lopeta estäminen" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Näytä tämän tilin asetukset" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Avaa uudessa ikkunassa" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Siirry profiiliin" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Luotu" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Tilannut" corebird-1.7.4/po/fr.po000066400000000000000000000555551324604713000147200ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # akern , 2016 # Baptiste Mille-Mathias , 2017 # Guillaume Hayot, 2015-2016 # newincpp , 2014 # rc spam , 2016 # RoPP , 2015 # Seboss666 , 2015 # Skyward , 2016 # Étienne Deparis , 2017 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: French (http://www.transifex.com/corebird/corebird/language/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Client Twitter" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird est un client Twitter natif en GTK+ proposant les fonctions vitales telles que les messages directs (dm), les notifications, les vues en conversations." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Les fonctions additionnelles incluent le visionnage en local des vidéos, les insertions multiples d'images, les listes, les filtres, les comptes multiples, etc." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Vue générique de la timeline avec Corebird" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Profil Twitter typique" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Les réglages de comptes peuvent être configurés" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "Timm Bäder" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Utiliser Twitter depuis une application de bureau classique" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "En réponse à" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "et" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "et %d autres" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Conversation directe" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d nouveau message de %s" msgstr[1] "%d nouveaux messages de %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Nouveau message privé de %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Messages privés" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "Impossible de charger les tweets" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Favoris " #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Ajouter un nouveau filtre" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Filtres" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s a retweeté %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s a tweeté" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d nouveau tweet !" msgstr[1] "%d nouveaux tweets !" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Accueil" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Liste" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Listes" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Afficher les comptes configurés" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Écrire un nouveau Tweet" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Ajouter un nouveau compte" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s a mentionné %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Mentions" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Profil protégé" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Tweet à @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Rechercher" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Impossibe d'afficher le tweet" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Retweets" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Ouvrir dans un navigateur" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Source" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Détails du tweet" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d non lu)" msgstr[1] "(%d non lus)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Supprimer" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Bloquer %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Ce tweet contient des images identifiées comme inappropriées" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Montrer quand même" #: src/util/Utils.vala:146 msgid "Now" msgstr "Maintenant" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "Pas encore de compte Twitter ?" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Créer un compte " #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "Non autorisé. La plupart du temps, cela veut dire qu'il y a un problème avec les serveurs Twitter et que vous devriez réessayer plus tard." #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Impossible d'ouvrir %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "PIN erroné" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Compte déjà en utilisation" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "Aucun utilisateur trouvé" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Sélectionner une image" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Ouvrir" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Annuler" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Suivre" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Se désabonner" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "Copier l'URL" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "Enregistrer sous…" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "Enregistrer la vidéo" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "Enregistrer l'image" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Enregistrer" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "Chargement…" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Aucune entrée trouvée" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Réessayer" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "Sélectionner une image de bannière" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "L'image ne répond pas aux caractéristiques minimales de taille :" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "Largeur minimale : %d pixel" msgstr[1] "Largeur minimale : %d pixels" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "Hauteur minimale : %d pixel" msgstr[1] "Hauteur minimale : %d pixels" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "Sélectionner" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Citer le tweet" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Retour" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "L'image sélectionnée est trop grosse. La taille maximale autorisée par image est %'d MB" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "Seulement un GIF par tweet est autorisé." #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Modifier les filtres" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Correspond" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "Ne correspond pas" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Modifier le Snippet" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "Le fragment de code ne peut pas être vide" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "Le texte de remplacement ne peut pas être vide" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "Un snippet en peut contenir d'espace vide" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "Le snippet existe déjà" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Hey, check out this new #Corebird version! \\ (•◡•) / #cool #newisalwaysbetter" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Ajouter ou supprimer des utilisateurs de la liste" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Vous n'avez aucune liste." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "À propos de Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Nouveau compte" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "Pour autoriser Corebird, vous devez récupérer un code PIN de twitter.com, pour le compte que vous souhaitez ajouter" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Demander le PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "Entrez le code PIN de twitter.com ci-dessous :" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "PIN" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Confirmer" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Paramètres du compte" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Nom" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Site web" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Démarrage automatique" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Voulez-vous réellement supprimer ce compte ?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Envoyer" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Ajouter une image" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "Montrer les images favorites" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Utilisateurs" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "S'abonner" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Se désabonner" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Abonnés :" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Membres :" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Créateur :" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Créé à :" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Éditer" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Mode :" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Privé" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Public" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Description" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Préférences" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Raccourcis" #: ui/menus.ui:15 msgid "About" msgstr "À propos" #: ui/menus.ui:19 msgid "Quit" msgstr "Quitter" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Ajouter un nouveau filtre" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Ajouter un nouveau snippet" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Mot-clé" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Substitution" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Créer une nouvelle liste" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Nom :" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Créer" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Écrire un message direct" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Ajouter/Retirer de la Liste" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Bloqué" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Muet" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Retweets désactivés" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Vous suit" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tweets" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Abonnés" #: ui/profile-page.ui:326 msgid "Following" msgstr "Abonnements" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "Afficher les médias sur une ligne" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Toujours afficher" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Toujours cacher" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Cacher dans la timeline" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Défilement automatique des nouveaux tweets" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Activation par double clic" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Interface" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Lors de nouveaux tweets" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Actions" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Lors de nouvelles mentions" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Lors de nouveaux messages" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Jamais" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "à chaque fois" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "tous les 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "tous les 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "tous les 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "tous les 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Notifications" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Avatars ronds" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Supprimer les hashtags superflus" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Supprimer les liens vers les média" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Cacher le contenu inapproprié" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Pas de snippets configurés." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Vous pouvez activer des snippets en écrivant le mot-clé puis tabulation." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Snippets" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Général" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Écrire un nouveau tweet" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Paramètres du compte" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Afficher le compte Popover" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Afficher les paramètres de l'application" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "(Dés)Activer la barre supérieure" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Retour" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Avancer" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Aller à la énième page" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Tweets" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Retweeter" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Ajouter aux favoris" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Répondre" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Citer" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Montrer les détails" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Supprimer" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Démarrer une nouvelle conversation" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Avec :" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Exécuter" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Citer" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Retweeter" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Ajouter aux favoris" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Répondre au tweet" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Plus" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Favori" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Répondre" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Débloquer" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Afficher les paramètres de ce compte" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Ouvrir dans une nouvelle fenêtre" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Se rendre sur le profil" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Créé" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "S'inscrire" corebird-1.7.4/po/ga.po000066400000000000000000000456131324604713000146720ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Aoibhe Agnew (Aoibhe Ní Ghnímh), 2017 # Aoibhe Ní Ghnímh , 2017 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-11-30 16:57+0000\n" "Last-Translator: Aoibhe Ní Ghnímh \n" "Language-Team: Irish (http://www.transifex.com/corebird/corebird/language/ga/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ga\n" "Plural-Forms: nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "" #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "" #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "agus" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" msgstr[4] "" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Teachtaireachtaí Díreacha" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Croíthe" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" msgstr[4] "" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Baile" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Liosta" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Liostaí" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Cuardaigh" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" msgstr[4] "" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Scrois" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Coisc %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "" #: src/util/Utils.vala:146 msgid "Now" msgstr "Anois" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Oscail" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Lean" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Á Leanúint" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Sábháil" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" msgstr[4] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" msgstr[4] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "" #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "" #: ui/account-dialog.ui:88 msgid "Name" msgstr "" #: ui/account-dialog.ui:117 msgid "Website" msgstr "" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "Bratacha" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Seol" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "" #: ui/menus.ui:15 msgid "About" msgstr "" #: ui/menus.ui:19 msgid "Quit" msgstr "" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "" #: ui/profile-page.ui:24 msgid "Muted" msgstr "" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tweetanna" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Leantóirí" #: ui/profile-page.ui:326 msgid "Following" msgstr "Á Leanúint" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "" #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Tweetanna" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Atweetáil" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Freagra" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Freagra" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Díchoisc" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "" corebird-1.7.4/po/gl.po000066400000000000000000000543401324604713000147020ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # antiparvos, 2016-2017 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-12-22 02:49+0000\n" "Last-Translator: antiparvos\n" "Language-Team: Galician (http://www.transifex.com/corebird/corebird/language/gl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: gl\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Cliente para o Twitter" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird é un cliente GTK+ nativo para o Twitter que fornece funcionalidades fundamentais como mensaxes directas (MD), notificacións de chíos ou vistas de conversas." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Inclúe funcionalidades adicionais como visualización local de vídeos, varias imaxes inseridas, listaxes, filtros, múltiples contas, etc." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Cronoloxía xenérica ao usar Corebird" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Perfil típico co Twitter" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "A conta pode configurarse" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "Timm Bäder" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Use Twitter desde un aplicativo de escritorio" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "En resposta a" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "e" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "e %d máis" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Conversa directa" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d nova mensaxe de %s" msgstr[1] "%d novas mensaxes de %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Nova mensaxe directa de %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Mensaxes directas" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "Non foi posíbel cargar os chíos" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Favoritos" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Engadir un filtro" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Filtros" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s rechouchiou a %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s chiou" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d chío novo" msgstr[1] "%d chíos novos" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Inicio" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Listaxe" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Listaxes" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Mostrar contas configuradas" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Escribir un chío" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Engadir conta" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s mencionou a %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Mencións" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "Conta suspendida" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Perfil protexido" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Chiar a @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "Perfil protexido" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Buscar" #: src/SearchPage.vala:401 msgid "Load More" msgstr "Cargar máis" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Non foi posíbel mostrar o chío" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Rechouchíos" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Abrir no navegador" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Orixe" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Detalles do chío" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d sen ler)" msgstr[1] "(%d sen ler)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Eliminar" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Bloquear a %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Este chío contén imaxes marcadas como inapropiadas" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Mostrar" #: src/util/Utils.vala:146 msgid "Now" msgstr "Agora" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "Aínda non ten unha conta no Twitter?" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Crear unha" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "Non autorizado. Normalmente significa que algo funciona mal nos servidores do Twitter e debería tentalo máis tarde" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Non foi posíbel abrir %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "PIN equivocado" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Xa se está usando esta conta" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "Non se atoparon usuarios" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Seleccionar imaxe" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Abrir" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Cancelar" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Seguir" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Non seguir" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "Copiar URL" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "Gardar como..." #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "Gardar vídeo" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "Gardar imaxe" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Gardar" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "Cargando..." #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Non se atoparon entradas" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Reintentar" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "Seleccionar imaxe da cabeceira" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "A imaxe non reúne os requisitos do tamaño mínimo:" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "Largura mínima: %d píxel" msgstr[1] "Largura mínima: %d píxeles" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "Altura mínima: %d píxel" msgstr[1] "Altura mínima: %d píxeles" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "Escolla" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Citar chío" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "O ficheiro seleccionado non é unha imaxe." #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Volver" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "A imaxe é grande de máis. O tamaño máximo permitido son %'d MB" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "Só se permite un ficheiro GIF por chío." #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "Inserir emoji" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Modificar filtro" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Resultados" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "Sen resultados" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Modificar as cadeas" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "A cadea non pode estar baleira" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "A cadea substituta non pode estar baleira" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "A cadea non pode conter espazos" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "A cadea xa existe" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Ei, probe esta nova versión do #Corebird ! \\ (•◡•) / #cool #newisalwaysbetter" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Engadir ou eliminar dunha listaxe" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Non ten listaxes." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Sobre Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Nova conta" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "Para autenticar o Corebird, precisa un PIN de twitter.com coa conta que desexa engadir" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Solicitar PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "Introduza debaixo o PIN de twitter.com:" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "PIN" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Confirmar" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Axustes da conta" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Nome" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Sitio web" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Inicio automático" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Confirma a eliminación desta conta?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "Emoticonos e persoas" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "Corpo e roupa" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "Animais e natureza" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "Comida e bebida" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "Viaxar e lugares" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "Actividades" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "Obxectos" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "Símbolos" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "Bandeiras" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "Sen resultados" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "Tente unha busca diferente" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Enviar" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Engadir imaxe" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "Mostrar as imaxes favoritas" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Usuarios" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Subscribir" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Non subscribir" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Subscritores:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Membros:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Creador:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Creada o:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Editar" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Modo:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Privada" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Pública" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Descrición" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Axustes" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Atallos" #: ui/menus.ui:15 msgid "About" msgstr "Sobre" #: ui/menus.ui:19 msgid "Quit" msgstr "Saír" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Engadir un filtro" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Engadir cadea" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Palabra clave" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Cadea substituta" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Crear unha listaxe" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Nome:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Crear" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Escribir mensaxe directa" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Engadir/Eliminar dunha listaxe" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Bloqueado" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Silenciado" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Rechouchíos desactivados" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Séguete" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Chíos" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Seguidores" #: ui/profile-page.ui:326 msgid "Following" msgstr "Seguindo" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "Mostrar medios inseridos" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Mostrar sempre" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Ocultar sempre" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Ocultar da cronoloxía" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Desprazamento auto. nos chíos novos" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Activación por dobre clic" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Interface" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Dos novos chíos" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Accións" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Das novas mencións" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Das novas mensaxes" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Nunca" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Todos" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Cada 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Cada 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Cada 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Cada 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Notificacións" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Avatares redondos" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Retirar cancelos no final dos chíos" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Retirar ligazóns multimedia" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Ocultar contido inapropiado" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Non hai cadeas configuradas" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Pode activar unha cadea escribindo a palabra clave e premendo TAB." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Cadeas para inserir" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Xeral" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Escribir un chío" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Mostrar os axustes das contas" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Mostrar información emerxente das contas" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Mostrar os axustes dos aplicativos" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Cambiar a barra superior" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Volver" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Seguir" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Ir á páxina" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Chíos" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Rechouchiar" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Marcar favorito" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Responder" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Citar" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Mostrar detalles" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Eliminar" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "Escribir" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "Mostrar o selector de emojis" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Iniciar unha conversa nova" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Con:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Ir" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Citar" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Rechouchiar" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Marcar favorito" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Responder" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Máis" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Marcar favorito" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Responder" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Desbloquear" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Mostrar axustes da conta" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Abrir nunha xanela" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Ir ao perfil" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Creada" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Subscrito a" corebird-1.7.4/po/hi.po000066400000000000000000000466411324604713000147050ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # vm, 2015 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: Hindi (India) (http://www.transifex.com/corebird/corebird/language/hi_IN/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: hi_IN\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "" #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "" #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "" msgstr[1] "" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "%s द्वारा प्रत्यक्ष संदेश" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "प्रत्यक्ष संदेश" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s ने यह रीट्वीट किया %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s ने ट्वीट किया" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d नया ट्वीट" msgstr[1] "%d नए ट्वीट!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "होम" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "सूचि" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "सूचियाँ" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "" msgstr[1] "" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "" #: src/util/Utils.vala:146 msgid "Now" msgstr "" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "अभि बनाये" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "%s नहीं खोल पाये" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "गलत पिन" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "यह खता पहले से उपयोग में है" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "रद्द करें" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "फॉलो करे" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "अनफॉलो करे" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "सहेजें" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "कोई अभिलेख नही मिला" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "पुन: प्रयास करें" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "फ़िल्टर करे संशोधित करे" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "उपयोगकर्ता को सूची से जोड़ें या निकालें" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "आपके पास कोई सूची नही है" #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "खाते की सेटिंग्स" #: ui/account-dialog.ui:88 msgid "Name" msgstr "" #: ui/account-dialog.ui:117 msgid "Website" msgstr "" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "सेटिंग्स" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "" #: ui/menus.ui:15 msgid "About" msgstr "" #: ui/menus.ui:19 msgid "Quit" msgstr "" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "नयी सूचि बनाये " #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "" #: ui/profile-page.ui:24 msgid "Muted" msgstr "" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "" #: ui/profile-page.ui:284 msgid "Followers" msgstr "फ़ॉलोअर्स" #: ui/profile-page.ui:326 msgid "Following" msgstr "" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "" #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "इस खाते की सेटिंग्स दिखाए" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "" corebird-1.7.4/po/hu.po000066400000000000000000000461141324604713000147140ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Peter Borsa , 2015 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: Hungarian (http://www.transifex.com/corebird/corebird/language/hu/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: hu\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Twitter kliens" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "" #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "" #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d új üzenet %s felhasználótól" msgstr[1] "%d új üzenet %s felhasználótól" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Új személyes üzenet %s felhasználótól" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Személyes üzenetek" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Kedvencek" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "" msgstr[1] "" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Home" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Listák" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Védett profil" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Keresés" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Megnyitás a böngészőben" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Forrás" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Tweet részletek" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "" msgstr[1] "" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Törlés" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "" #: src/util/Utils.vala:146 msgid "Now" msgstr "Most" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "Hibás PIN" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "A felhasználói fiók már használatban" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Mégse" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Követés" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Mentés" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Újra" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Vissza" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Szűrő módosítása" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "" #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Corebird-ről" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Új felhasználói fiók" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "PIN kérése" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Megerősítés" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Név" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Weboldal" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Küldés" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Felhasználók" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Feliratkozás" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Leiratkozás" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Feliratkozók:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Tagok:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Szerkesztés" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Privát" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Publikus" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Beállítások" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "" #: ui/menus.ui:15 msgid "About" msgstr "Névjegy" #: ui/menus.ui:19 msgid "Quit" msgstr "Kilépés" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Új lista létrehozása" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Név:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Létrehozás" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Blokkolva" #: ui/profile-page.ui:24 msgid "Muted" msgstr "" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tweetek" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Követők" #: ui/profile-page.ui:326 msgid "Following" msgstr "Követve" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Soha" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Minden" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Értesítések" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "" #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Válasz" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Megnyitás új ablakban" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Létrehozva" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "" corebird-1.7.4/po/id.po000066400000000000000000000535051324604713000146760ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Krisan Alfa Timur , 2015 # Mahyuddin , 2015-2016-2016 # Reza Faiz A. Rahman , 2015 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: Indonesian (Indonesia) (http://www.transifex.com/corebird/corebird/language/id_ID/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: id_ID\n" "Plural-Forms: nplurals=1; plural=0;\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Twitter Klien" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird adalah GTK+ twitter klien native yang menyediakan fitur penting seperti Pesan Langsung (DMS), tweet pemberitahuan, pemandangan percakapan." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Fitur tambahan termasuk melihat video lokal, beberapa gambar di baris, Daftar, Penyaring, beberapa akun, dll" #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Tampilan timeline generik ketika menggunakan Corebird" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Khas Profil Twitter" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Setelan akun dapat dikonfigurasi" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "Timm Bäder" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Gunakan Twitter dari aplikasi desktop normal" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "Membalas ke" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "dan" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "dan 1%d lainnya" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Percakapan Langsung" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d Pesan baru dari %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Pesan baru langsung dari %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Pesan Langsung" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "Tidak bisa menampilkan tweet" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Kesukaan" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Tambah Penyaringan baru" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Penyaringan" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s retweeted %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s tweeted" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d Tweets! baru" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Halaman depan" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Daftar" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Daftar" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Tampilakn pengaturan akun" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Menulis tweet" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Tambah Akun baru" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s mentioned %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Menyebutkan" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Profil dilindungi" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Tweet untuk @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Pencarian" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Tidak bisa menampilkan tweet" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Retweets" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Buka di Peladen" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Sumber" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Tweet Detil" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d belum dibaca)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Hapus" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Blok %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Tweet ini berisi gambar yang ditandai sebagai tidak sesuai" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Tampilkan apa saja" #: src/util/Utils.vala:146 msgid "Now" msgstr "Sekarang" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dj" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "Belum mempunyai akun Twitter?" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Buat satu" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "Tidak sah, ini berarti ada yang tidak beres dengan server Twitter dan Anda harus mencoba lagi nanti" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Tidak bisa membuka %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "Salah PIN" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Akun sudah digunakan" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "User tidak ditemukan" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Pilih Gambar" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Buka" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Batal" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Mengikuti" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Batal mengikuti" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "Salin URL" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "Simpan sebagai..." #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "SImpan Vidio" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "SImpan Gambar" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Simpan" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "Sedang memuatkan..." #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Entri tidak ditemukan" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Coba lagi" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "Pilih Gambar Spanduk" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "Gambar tidak memenuhi persyaratan ukuran minimum:" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "Lebar minimum: %d piksel" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "Tinggi minimum: %d piksel" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "Memilih" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Kutip tweet" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Kembali" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "Gambar yang dipilih terlalu besar. Ukuran berkas maksimum per gambar adalah%'d'd MB" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "Hanya satu file GIF per tweet yang diizinkan." #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Ubah Penyaringan" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Cocok" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "Tidak cocok" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Ubah Cuplikan" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "Cuplikan tidak boleh kosong" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "Penggantian tidak boleh kosong" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "Cuplikan mungkin tidak mengandung spasi" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "Cuplikan sudah ada" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Hai, coba lihat versi baru dari #Corebird! \\ (•◡•) / #keren #yangbaruselalulebihbaik" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Tambah ke atau Hapus Pengguna Dari Daftar" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Anda tidak memiliki daftar." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Tentang Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Akun Baru" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "Untuk mengotentikasi Corebird, Anda memerlukan PIN dari twitter.com dengan akun yang ingin Anda tambahkan" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Permintaan PIN:" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "Masukkan PIN dari twitter.com dibawah ini:" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "PIN" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Konfirmasi" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Pengaturan Akun" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Nama" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Situs" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Mulai Otomatis" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Anda yakin akan menghapus akun ini?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Kirim" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Tambah Gambar" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "Tampilkan gambar favorit" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Pengguna" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Langganan" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Berhenti langganan" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Pelanggan:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Anggota:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Pembuat:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Dibuat di:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Ubah" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Mode:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Rahasia" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Umum" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Deskripsi" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Pengaturan" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Pintasan" #: ui/menus.ui:15 msgid "About" msgstr "Tentang" #: ui/menus.ui:19 msgid "Quit" msgstr "Berhenti" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Tambah Penyaringan Baru" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Tambah Potongan Baru" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Kata Kunci" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Penggantian" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Buat Daftar Baru" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Nama:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Membuat" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Tulis Pesan Langsung" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Tambah ke/Hapus dari daftar" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Diblokir" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Meredam" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Retweets dinonaktifkan" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Mengikuti anda" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tweets" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Pengikut" #: ui/profile-page.ui:326 msgid "Following" msgstr "Mengikuti" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "Tampilkan media dalam baris" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Selalu muncul" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Selalu sembunyi" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Sembunyi di beranda" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Auto scroll pada tweet baru" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Aktivasi klik-ganda" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Antarmuka" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Pada Tweets Baru" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Aksi" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Pada Menyebutkan Baru" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Pada Pesan Baru" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Tidak pernah" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Setiap" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Stack 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Stack 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Stack 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Stack 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Notifikasi" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Avatar bulat" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Hapus hastag sisa" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Hapus tautan media" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Sembunyikan konten yang tidak sesuai" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Tidak ada potongan yang terkonfigurasi." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Kamu bisa mengaktifkan potongan dengan menuliskan kata kuncinya dan tekan tombol TAB." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Potongan" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Umum" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Menulis Tweet" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Tampilkan Pengaturan Akun" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Tampilkan Popover Akun" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Tampilkan Pengaturan Aplikasi" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Alih Topbar" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Kembali" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Lanjut" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Ke halaman nth" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Tweets" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Retweet" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Kesukaan" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Balas" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Kutipan" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Tampilkan Detil" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Hapus" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Mulai percakapan baru" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Dengan:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Pergi" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Kutipan" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Retweet tweet" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Tweet kesukaan" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Balas ke tweet" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Lebih banyak" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Kesukaan" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Balas" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Lepaskan blokir" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Tampilkan pengaturan akun ini" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Buka di window baru" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Pergi ke profil" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Dibuat" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Berlangganan ke" corebird-1.7.4/po/it.po000066400000000000000000000546461324604713000147250ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Daniele Napolitano , 2014 # Emanuele Rocco Petrone , 2018 # Matteo Castelli , 2016 # Nicola Stanislao Vitale , 2015 # Tassoman, 2017 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2018-02-20 19:25+0000\n" "Last-Translator: Emanuele Rocco Petrone \n" "Language-Team: Italian (http://www.transifex.com/corebird/corebird/language/it/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: it\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Client Twitter" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird è un client twitter nativo GTK+ che fornisce funzioni vitali come messaggi diretti (DM), notifiche dei tweet e vista delle conversazioni." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Funzionalità aggiuntive includono riproduzione locale dei video, più immagini in una riga, liste, filtri, account multipli ecc." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Vista generica della timeline quando usi Corebird" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Tipico profilo twitter" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Le impostazioni dell'account possono essere configurate" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "Timm Bäder" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Usa Twitter come una normale applicazione desktop" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "Twitter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "Rispondendo a" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "e" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "e %d altri" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Conversazione diretta" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d nuovo messaggio da %s" msgstr[1] "%d nuovi messaggi da %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Nuovo messaggio diretto da %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Messaggi diretti" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "Impossibile caricare i tweet" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Preferiti" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Aggiungi un nuovo filtro" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Filtri" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s ha ritwittato %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s ritwittato" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d nuovo Twett!" msgstr[1] "%d nuovi Tweet!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Home" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Lista" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Liste" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Mostra gli account configurati" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Componi tweet" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Aggiungi un nuovo account" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s ha menzionato %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Menzioni" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "Account sospeso" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Profilo protetto" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Twitta a @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "Profilo protetto" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Cerca" #: src/SearchPage.vala:401 msgid "Load More" msgstr "Mostra altri" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Impossibile mostrare il tweet" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Retweet" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Apri nel browser" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Sorgente" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Dettagli tweet" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d non letto)" msgstr[1] "(%d non letti)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Elimina" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Blocca %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Questo tweet contiene immagini segnalate come inappropriate" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Mostrare comunque" #: src/util/Utils.vala:146 msgid "Now" msgstr "Adesso" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "Non hai ancora un account Twitter?" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Creane uno" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "Non autorizzato. La maggior parte delle volte questo significa che c'è qualche problema con i server di Twitter e che dovresti provare di nuovo più tardi" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Non posso aprire %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "PIN sbagliato" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Account già in uso" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "Nessun utente" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Sfoglia" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Aprire" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Annulla" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Segui" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Non seguire" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "Copia URL" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "Salva come..." #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "Salva Video" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "Salva immagine" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Salva" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "Caricamento..." #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Nessun risultato trovato" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Riprova" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "Seleziona l'immagine banner" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "L'immagine non rispetta le dimensioni minime richieste" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "Seleziona" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Cita il tweet" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "Il file selezionato non è un'immagine" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Indietro" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "L'immagine selezionata è troppo grossa. La dimensione massima consentita è %'d MB" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "È consentita solo una gif per tweet." #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "Inserisci emoji" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Modifica filtro" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Corrispondenze" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "Non corrisponde" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Modifica Snippet" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "Lo snippet non può essere vuoto" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "Il sostituto non può essere vuoto" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "Lo snippat non può contenere spazi" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "Lo snippet esiste già" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Ehi, scarica la nuova versione di #Corebird \\ (•◡•) / #cool #newisalwaysbetter" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Aggiungi o rimuovi un utente dalla lista" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Non hai nessuna lista" #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Informazioni su Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Nuovo account" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "Per autenticarti su Corebird hai bisogno di un PIN da twitter.com dell'account che desideri aggiungere" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Richiedi PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "Inserisci il pin da Twitter.com qui sotto" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "PIN" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Conferma" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Impostazioni account" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Nome" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Sito web" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Avvio automatico" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Vuoi davvero rimuovere questo account?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "Smiles e Persone" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "Corpo e vestiti" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "Animali e natura" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "Cibo e bevande" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "Viaggi e posti" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "Attività" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "Oggetti" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "Simboli" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "Bandiere" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "Nessun risultato trovato" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "Prova una ricerca differente" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Invia" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Aggiungi immagine" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "Mostra le immagini preferite" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Utenti" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Sottoscrivi" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Rimuovi sottoscrizione" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Iscritti:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Membri:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Creatore:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Creata il:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Modifica" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Modalità:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Privata" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Pubblica" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Descrizione" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Impostazioni" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Scorciatoie" #: ui/menus.ui:15 msgid "About" msgstr "Informazioni su..." #: ui/menus.ui:19 msgid "Quit" msgstr "Esci" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Aggiungi nuovo filtro" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Aggiungi un nuovo snippet" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Parola chiave" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Sostituto" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Crea una nuova lista" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Nome:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Crea" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Scrivi un messaggio diretto" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Aggiunti/rimuovi dalla lista" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Bloccato" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Silenziato" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Retweet disabilitati" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Ti segue" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tweet" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Followers" #: ui/profile-page.ui:326 msgid "Following" msgstr "Following" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "Mostrare i media contenuti" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Mostrare sempre" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Nascondere sempre" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Nascondere nella timeline" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Scorrimento automatico con nuovi tweet" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Doppio-click per attivare" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Interfaccia" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Per Nuovi Tweet" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Azioni" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Per Nuove Menzioni" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Per Nuovi Messaggi" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Mai" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Sempre" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Ogni 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Ogni 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Ogni 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Ogni 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Notifiche" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Avatar arrotondati" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Rimuovere gli hastag finali" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Rimuovi link multimediali" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Contenuto inappropriato" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Nessuno snippet configurato." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Gli snippet si attivano digitando la parola chiave con la tastiera e premendo TAB." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Snippet" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Generale" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Componi tweet" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Mostra Preferenze Account" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Mostra Account sovrimpresso" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Mostra Preferenze Applicazione" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Indietro" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Avanti" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Tweet" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Retweet" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Preferito" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Rispondi" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Cita" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Mostra Dettagli" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Elimina" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "Componi" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "Mostra emoji picker" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Inizia una nuova conversazione" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Con:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Vai" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Cita" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Ritwitta il tweet" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Tweet preferito" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Rispondi al tweet" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Altro" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Mi piace" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Rispondi" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Sblocca" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Mostra le impostazioni di questo account" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Apri in una nuova finestra" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Vai al profilo" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Creato" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Sottoscritto a" corebird-1.7.4/po/ja.po000066400000000000000000000526101324604713000146700ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # kumar8600 , 2014 # MTD TR , 2017 # Yuki Katsura , 2017 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-11-12 23:35+0000\n" "Last-Translator: MTD TR \n" "Language-Team: Japanese (Japan) (http://www.transifex.com/corebird/corebird/language/ja_JP/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ja_JP\n" "Plural-Forms: nplurals=1; plural=0;\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Twitter Client" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird はGTK+ネイティブなTwitterクライアントで、ダイレクトメッセージ (DM) ・ツイート通知・会話ビューといった重要機能を提供します。" #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "加えて、ビデオの表示・インラインでの画像の表示・リスト・フィルター・複数のアカウントなどをサポートします" #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "通常のデスクトップアプリケーションからTwitterを使用する" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%2$s から %1$d 件の新着メッセージ" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "%s からダイレクトメッセージ" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "ダイレクトメッセージ" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "ツイートをロードできませんでした" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "お気に入り" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "フィルターを追加する" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "フィルター" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s が %s をリツイート" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s がツイート" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d 件の新着ツイート!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "ホーム" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "リスト" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "リスト" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "アカウントの設定を表示" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "ツイートの編集" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "アカウントを追加する" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "メンション" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "凍結されたアカウント" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "非公開プロフィール" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "@%s にツイート" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "非公開アカウント" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "検索" #: src/SearchPage.vala:401 msgid "Load More" msgstr "更に読み込む" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "ツイートを表示できませんでした" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "リツイート" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "ブラウザーで開く" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "ソース" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "ツイートの詳細" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d 件の未読)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "削除" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "%sをブロック" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "このツイートは不適切な画像を含んでいます" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "常に表示する" #: src/util/Utils.vala:146 msgid "Now" msgstr "現在" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "まだTwitterアカウントをお持ちではありませんか?" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "%sを開けませんでした" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "誤ったPIN" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "アカウントはすでに使われています" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "ユーザが見つかりません" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "画像を選択" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "キャンセル" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "フォロー" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "フォロー解除" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "URLをコピー" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "名前を付けて保存" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "保存" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "読み込み中..." #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "エントリーがありません" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "再試行" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "戻る" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "選択された画像は大きすぎます。最大ファイルサイズは%'dMBです" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "1つのツイートには1つのGIFファイルしか添付できません。" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "フィルターを編集" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "一致" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "さあ、新しい#Corebird のバージョンをチェックしてください!\\ (•◡•) / #cool #newisalwaysbetter" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "リストからユーザを追加、削除する" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "リストがありません" #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Corebird について" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "新しいアカウント" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "PINを要求" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "確認" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "アカウントの設定" #: ui/account-dialog.ui:88 msgid "Name" msgstr "名前" #: ui/account-dialog.ui:117 msgid "Website" msgstr "" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "自動起動" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "このアカウントを削除しますか?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "送信" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "画像を追加" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "ユーザー" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "購読" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "購読解除" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "購読者:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "メンバー:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "作成者:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "作成時刻:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "編集" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "モード:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "非公開" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "公開" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "設定" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "ショートカット" #: ui/menus.ui:15 msgid "About" msgstr "このアプリケーションについて" #: ui/menus.ui:19 msgid "Quit" msgstr "終了" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "リストの新規作成" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "名前:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "作成" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "ダイレクトメッセージを書く" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "リストへ追加、削除する" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "ブロック済み" #: ui/profile-page.ui:24 msgid "Muted" msgstr "ミュート済み" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "リツイートはできません" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "フォローされている" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "ツイート" #: ui/profile-page.ui:284 msgid "Followers" msgstr "" #: ui/profile-page.ui:326 msgid "Following" msgstr "" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "インターフェイス" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "アクション" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "しない" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "全て" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "5 件溜まったら" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "10 件溜まったら" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "25 件溜まったら" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "50 件溜まったら" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "通知" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "" #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "ツイート" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "リツイート" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "お気に入り" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "返信" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "引用" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "詳細を表示" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "削除" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "新たな会話を始める" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "相手:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "始める" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "引用する" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "リツイートする" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "お気に入りに登録する" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "返信する" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "詳細" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "お気に入り" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "リプライ" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "このアカウントの設定を表示" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "新しいウィンドウで開く" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "プロファイルを開く" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "作成" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "購読中" corebird-1.7.4/po/ko.po000066400000000000000000000525621324604713000147150ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # irus6, 2016 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: Korean (Korea) (http://www.transifex.com/corebird/corebird/language/ko_KR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ko_KR\n" "Plural-Forms: nplurals=1; plural=0;\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "코어버드" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "트위터 클라이언트" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird 는 다이렉트 메시지 (DMs), 트윗 알림, 대화 보기 등의 중요 특징을 제공하는 순수 GTK+ 트위터 클라이언트 입니다." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "추가적인 특징으로는 비디오 보기, 복수의 이미지, 리스트, 필터, 멀티 계정, 등등이 포함됩니다." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "코어버드 사용시 일반 타임라인 보기" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "전형적인 트위터 프로필" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "계정 환경이 설정 될 수 있음" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "일반 데스크탑 앱에서 트위터 사용하기" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "트위터;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "다이렉트 대화" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%2$s 로부터 %1$d 개의 새로운 메시지" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "%s 로부터 새로운 다이렉트 메시지" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "다이렉트 메시지" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "트윗을 불러올 수 없습니다." #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "즐겨찾기" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "새로운 필터 추가" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "필터" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s 가 %s 를 리트윗 함" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s 가 트윗함" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d 개의 새로운 트윗!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "홈" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "리스트" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "리스트" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "구성된 계정 보여주기" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "트윗 작성" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "새로운 계정 추가" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s 가(이) %s 을(를) 멘션함" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "멘션" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "보호되는 프로필" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "@%s 에게 트윗" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "찾기" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "트윗을 볼 수 없음" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "리트윗" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "브라우저에서 열기" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "원본" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "상세 트윗" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d 개의 읽지 않음)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "지우기" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "%s 차단" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "이 트윗에 포함된 이미지는 적절하지 않다고 표시 되었습니다" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "아무튼 보기" #: src/util/Utils.vala:146 msgid "Now" msgstr "현재" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%d분" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%d시" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "하나 만들기" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "%s 을 열 수 없음" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "올바르지 않은 PIN" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "계정이 이미 사용 중" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "사용자 없음" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "이미지 선택" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "열기" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "취소" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "팔로우" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "언팔로우" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "URL 복사" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "저장" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "찾을수 없음" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "재시도" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "트윗 인용" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "뒤로" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "필터 수정" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "일치함" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "조각 수정" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "조각이 공백을 포함 하지 않음" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "조각이 이미 존재함" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "헤이, 새로운 #Corebird 버젼을 확인하세요! \\ (•◡•) / #cool #newisalwaysbetter" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "리스트에서 사용자 추가 또는 삭제" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "리스트 없음." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Corebird 에 관해" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "새 계정" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "PIN 요청" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "확인" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "계정 설정" #: ui/account-dialog.ui:88 msgid "Name" msgstr "이름" #: ui/account-dialog.ui:117 msgid "Website" msgstr "웹사이트" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "자동 시작" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "정말 이 계정을 지울건가요?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "보내기" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "이미지 추가" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "사용자" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "구독하기" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "구독 취소" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "구독자:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "멤버:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "작성자:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "작성 장소:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "수정" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "모드:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "전용" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "공용" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "설명" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "설정" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "바로가기" #: ui/menus.ui:15 msgid "About" msgstr "About" #: ui/menus.ui:19 msgid "Quit" msgstr "종료" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "새로운 필터 추가" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "새로운 스니펫 추가" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "키워드" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "대체" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "새로운 리스트 생성" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "이름:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "생성" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "다이렉트 메시지 작성" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "리스트에 추가/삭제" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "차단됨" #: ui/profile-page.ui:24 msgid "Muted" msgstr "음소거됨" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "리트윗 불가" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "당신을 팔로우함" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "트윗" #: ui/profile-page.ui:284 msgid "Followers" msgstr "팔로워" #: ui/profile-page.ui:326 msgid "Following" msgstr "팔로우 중" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "인라인 미디어 보기" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "항상 보이기" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "항상 가리기" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "타임라인에서 가리기" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "새로운 트윗으로 자동 스크롤" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "더블 클릭 활성화" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "인터페이스" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "새로운 트윗" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "액션" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "새로운 멘션" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "새로운 메시지" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "절대 안함" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "매번" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "스택 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "스택 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "스택 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "스택 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "알림" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "둥근 아바타" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "추적하는 해쉬태그 삭제" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "미디어 링크 삭제" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "부적절한 컨텐츠 숨기기" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "스니펫이 구성되지 않음." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "키워드를 쓰고 TAB 을 눌러서 스니펫을 활성화할 수 있음." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "스니펫" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "일반" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "트윗 작성" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "계정 설정 보기" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "계정 팝오버 보기" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "앱 설정 보기" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "상단 바 토글" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "뒤로 가기" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "앞으로 가기" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "특정 페이지 가기" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "트윗" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "리트윗" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "즐겨찾기" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "답장" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "상세 보기" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "지우기" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "새로운 대화 시작" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "함께:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "가기" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "인용" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "트윗을 리트윗" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "트윗을 즐겨찾기" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "트윗에 답장" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "더 보기" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "즐겨찾기" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "답장" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "블락 해제" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "이 계정의 설정을 보여주기" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "새 창에서 열기" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "프로필로 가기" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "만들어짐" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "을 구독함" corebird-1.7.4/po/lt.po000066400000000000000000000561701324604713000147220ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Moo, 2014-2017 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 18:18+0000\n" "Last-Translator: Moo\n" "Language-Team: Lithuanian (http://www.transifex.com/corebird/corebird/language/lt/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: lt\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Twitter klientas" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird yra savas GTK+ twitter klientas, kuris suteikia svarbiausias ypatybes, tokias kaip Tiesioginės Žinutės (TŽ), tauškalų pranešimai, pokalbių rodiniai." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Papildomas ypatybes sudaro vietinė vaizdo įrašų peržiūra, kelių įterptų paveikslėlių galimybė, Sąrašai, filtrai, kelių paskyrų galimybė ir t. t." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Bendrinis laiko juostos rodinys, naudojant Corebird" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Tipinis Twitter profilis" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Paskyros nustatymai gali būti konfigūruojami" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "Timm Bäder" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Naudokitės Twitter normalioje darbalaukio programoje" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "Atsakoma" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "ir" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "ir %d kitiems" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Tiesioginis pokalbis" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d nauja žinutė nuo %s" msgstr[1] "%d naujos žinutės nuo %s" msgstr[2] "%d naujų žinučių nuo %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Nauja tiesioginė žinutė nuo %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Tiesioginės žinutės" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "Nepavyko įkelti tauškalų" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Mėgstami" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Pridėti naują filtrą" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Filtrai" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s persiuntė naudotojo %s tauškalą" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s išsiuntė tauškalą" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d naujas tauškalas!" msgstr[1] "%d nauji tauškalai!" msgstr[2] "%d naujų tauškalų!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Pradžia" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Sąrašas" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Sąrašai" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Rodyti konfigūruotas paskyras" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Rašyti tauškalą" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Pridėti naują paskyrą" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s paminėjo %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Paminėjimai" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "Pristabdyta paskyra" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Apsaugotas profilis" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Siųsti tauškalą naudotojui @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "Apsaugotas profilis" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Paieška" #: src/SearchPage.vala:401 msgid "Load More" msgstr "Įkelti daugiau" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Nepavyko parodyti tauškalo" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Persiųsti tauškalai" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Atverti naršyklėje" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Šaltinis" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Išsamiau apie tauškalą" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d neperskaitytas)" msgstr[1] "(%d neperskaityti)" msgstr[2] "(%d neperskaitytų)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Ištrinti" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Blokuoti %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Šiame tauškale yra paveikslų, kurie yra pažymėti kaip netinkami" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Vis tiek rodyti" #: src/util/Utils.vala:146 msgid "Now" msgstr "Dabar" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dmin." #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dval." #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "Dar neturite Twitter paskyros?" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Susikurkite paskyrą" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "Neleistina prieiga. Daugelyje atvejų, tai reiškia Twitter serveriuose įvykusius nesklandumus, tad vėliau, turėtumėte pabandyti dar kartą" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Nepavyko atverti %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "Neteisingas PIN" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Paskyra jau yra naudojama" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "Naudotojų nerasta" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Pasirinkite paveikslą" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Atverti" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Atsisakyti" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Sekti" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Nustoti sekti" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "Kopijuoti URL" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "Įrašyti kaip…" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "Įrašyti vaizdo įrašą" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "Įrašyti paveikslą" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Įrašyti" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "Įkeliama…" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Nerasta įrašų" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Pakartoti" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "Pasirinkite reklamjuostės paveikslą" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "Paveikslas neatitinka minimalių dydžio reikalavimų:" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "Minimalus plotis: %d taškas" msgstr[1] "Minimalus plotis: %d taškai" msgstr[2] "Minimalus plotis: %d taškų" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "Minimalus aukštis: %d taškas" msgstr[1] "Minimalus aukštis: %d taškai" msgstr[2] "Minimalus aukštis: %d taškų" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "Pasirinkti" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Cituoti tauškalą" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "Pasirinktas failas nėra paveikslas." #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Grįžti" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "Pasirinktas paveikslas yra per didelis. Didžiausias vieno paveikslo failo dydis yra %'d MB" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "Viename tauškale leidžiamas tik vienas GIF failas." #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "Įterpti jaustuką" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Keisti filtrą" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Sutampa" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "Nesutampa" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Keisti iškarpą" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "Iškarpa negali būti tuščia" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "Pakeitimas negali būti tuščias" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "Iškarpoje negali būti tarpų" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "Iškarpa jau yra" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Labas, pažvelk į šią naują #Corebird versiją! \\ (•◡•) / #jega #naujayravisadageriau" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Pridėti į arba šalinti naudotoją iš sąrašo" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Jūs neturite sąrašų." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Apie Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Nauja paskyra" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "Norint programoje Corebird nustatyti tapatybę, yra reikalingas PIN iš twitter.com su paskyra, kurią norite pridėti" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Užklausti PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "Žemiau įveskite PIN iš twitter.com:" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "PIN" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Patvirtinti" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Paskyros nustatymai" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Vardas" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Svetainė" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Paleisti įjungus" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Ar tikrai norite ištrinti šią paskyrą?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "Šypsenėlės ir žmonės" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "Kūnas ir apranga" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "Gyvūnai ir gamta" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "Maistas ir gėrimai" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "Kelionės ir vietos" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "Veiklos" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "Objektai" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "Simboliai" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "Vėliavos" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "Rezultatų nerasta" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "Bandykite kitą paiešką" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Siųsti" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Pridėti paveikslą" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "Rodyti mėgstamus paveikslus" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Naudotojai" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Prenumeruoti" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Atsisakyti prenumeratos" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Prenumeratoriai:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Nariai:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Kūrėjas:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Sukurta:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Redaguoti" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Veiksena:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Privatus" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Viešas" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Aprašas" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Nustatymai" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Trumpiniai" #: ui/menus.ui:15 msgid "About" msgstr "Apie" #: ui/menus.ui:19 msgid "Quit" msgstr "Išeiti" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Pridėti naują filtrą" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Pridėti naują iškarpą" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Raktažodis" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Pakeitimas" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Sukurti naują sąrašą" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Pavadinimas:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Sukurti" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Rašyti tiesioginę žinutę" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Pridėti į/Pašalinti iš sąrašo" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Užblokuotas" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Nutildytas" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Tauškalų persiuntimas išjungtas" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Seka paskui jus" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tauškalai" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Sekėjų" #: ui/profile-page.ui:326 msgid "Following" msgstr "Seka" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "Rodyti įterptąją mediją" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Visada rodyti" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Visada slėpti" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Slėpti laiko juostoje" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Automatiškai slinkti, esant naujiems tauškalams" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Aktyvavimas dvikarčiu spustelėjimu" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Sąsaja" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Esant naujiems tauškalams" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Veiksmai" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Esant naujiems paminėjimams" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Esant naujoms žinutėms" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Niekada" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Kiekvienam" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Dėklui iš 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Dėklui iš 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Dėklui iš 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Dėklui iš 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Pranešimai" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Apvalinti avatarus" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Šalinti besivelkančius saitažodžius" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Šalinti medijos nuorodas" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Slėpti netinkamą turinį" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Nėra sukonfigūruotų iškarpų." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Aktyvinkite iškarpas, įrašę raktažodį ir nuspausdami TAB klavišą." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Iškarpos" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Bendra" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Rašyti tauškalą" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Rodyti paskyros nustatymus" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Rodyti iškylančiuosius paskyrų langus" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Rodyti programos nustatymus" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Perjungti viršutinę juostą" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Grįžti atgal" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Eiti pirmyn" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Eiti į n-tą puslapį" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Tauškalai" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Persiųsti" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Mėgstamas" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Atsakyti" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Cituoti" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Rodyti išsamesnę informaciją" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Ištrinti" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "Rašyti" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "Rodyti jaustukų parinkiklį" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Pradėti naują pokalbį" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Su:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Pirmyn" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Cituoti" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Persiųsti tauškalą" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Mėgstamas tauškalas" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Atsakyti į tauškalą" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Daugiau" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Mėgstamas" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Atsakyti" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Atblokuoti" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Rodyti šios paskyros nustatymus" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Atidaryti naujame lange" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Pereiti į profilį" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Sukurta" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Jūsų prenumeratos" corebird-1.7.4/po/meson.build000066400000000000000000000001001324604713000160630ustar00rootroot00000000000000i18n = import('i18n') i18n.gettext('corebird', preset: 'glib') corebird-1.7.4/po/nb.po000066400000000000000000000533451324604713000147030ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Kim Malmo , 2018 # Åka Sikrom, 2016-2017 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2018-02-09 06:34+0000\n" "Last-Translator: Kim Malmo \n" "Language-Team: Norwegian Bokmål (http://www.transifex.com/corebird/corebird/language/nb/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: nb\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Twitter-klient" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird er en GTK+-basert Twitter-klient som tilbyr viktige funksjoner som direktemeldinger, tweet-varslinger og samtalevisning." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Du kan også se videoer, flerbilde-tweets, lister, filtre og bruke ulike kontoer samtidig i programmet." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Generisk tidslinje-visning ved bruk av Corebird" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Typisk Twitter-profil" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Kontoinnstillinger kan endres" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "Timm Bäder" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Bruk Twitter som et vanlig skrivebordsprogram" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "Svarer" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "og" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "og %d andre" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Direktesamtale" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d ny melding fra %s" msgstr[1] "%d nye meldinger fra %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Ny direktemelding fra %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Direktemeldinger" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "Klarte ikke å laste inn tweets" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Favoritter" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Legg til nytt filter" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Filtre" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s retweetet %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s tweetet" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d ny tweet!" msgstr[1] "%d nye tweets!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Hjem" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Liste" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Lister" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Vis oppsatte kontoer" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Skriv tweet" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Legg til ny konto" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s nevnte %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Nevnelser" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "Suspendert konto" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Beskyttet profil" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Tweet til @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "Beskyttet profil" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Søk" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Klarte ikke å vise tweet" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Retweets" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Åpne i nettleser" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Kilde" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Tweet-detaljer" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d ulest)" msgstr[1] "(%d ulest)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Slett" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Blokker %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Denne tweet-en inneholder bilder som er merket som upassende" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Vis likevel" #: src/util/Utils.vala:146 msgid "Now" msgstr "Nå" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "Har du ikke en Twitter-konto enda?" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Lag konto" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "Ikke autentisert. Dette betyr som regel at det er noe galt med Twitter-tjenerne. Prøv igjen senere" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Klarte ikke å åpne %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "Feil PIN" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Konto allerede i bruk" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "Fant ingen brukere" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Velg bild" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Åpne" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Avbryt" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Følg" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Avfølg" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "Kopier nettadresse" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "Lagre som …" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "Lagre video" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "Lagre bilde" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Lagre" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "Laster inn …" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Fant ingen elementer" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Prøv på nyt" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "Velg fanebilde" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "Bildet tilfredsstiller ikke størrelseskrav:" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "Minimumsbredde: %d piksel" msgstr[1] "Minimumsbredde: %d piksler" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "Minimumshøyde: %d piksel" msgstr[1] "Minimumshøyde: %d piksler" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "Velg" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Siter tweet" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "Valgte fil er ikke et bilde." #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Tilbake" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "Valgt bilde er for stort. Maksimal filstørrelse per bilde er %'d MB" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "Du kan bare legge til én GIF-fil per tweet." #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Endre filter" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Treff" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "Samsvarer ikke" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Endre kodesnut" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "Kodesnutt kan ikke være tom" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "Erstatning kan ikke være tom" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "Kodesnutt kan ikke inneholde mellomrom" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "Kodesnutt finnes allerede" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Sjekk ut den nye versjonen av #Corebird ! \\ (•◡•) / #cool" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Legg til eller fjern bruker fra liste" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Du har ingen lister." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Om Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Ny konto" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "For å autentisere Corebird må du logge inn på twitter.com med kontoen du vil bruke og hente en PIN-kode" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Be om PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "Skriv inn PIN fra twitter.com nedenfor:" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "PIN" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Bekreft" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Kontoinnstillinger" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Navn" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Nettside" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Autostart" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Er du sikker på at du vil slette denne kontoen?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Send" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Legg til bild" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "Vis favorittbilder" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Brukere" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Abonner" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Avslutt abonnement" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Abonnenter:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Medlemmer:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Laget av:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Laget:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Rediger" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Modus:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Privat" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Offentlig" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Beskrivelse" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Innstillinger" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Snarveier" #: ui/menus.ui:15 msgid "About" msgstr "Om" #: ui/menus.ui:19 msgid "Quit" msgstr "Avslutt" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Legg til nytt filter" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Legg til ny kodesnut" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Nøkkelord" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Erstatning" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Lag ny liste" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Navn:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Lag" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Skriv direktemelding" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Legg til/fjern fra liste" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Blokkert" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Skjult" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Retweets slått av" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Følger deg" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tweets" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Følgere" #: ui/profile-page.ui:326 msgid "Following" msgstr "Følger" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "Vis multimedier" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Vis alltid" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Skjul allti" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Skjul fra tidslinje" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Rull automatisk ved nye tweets" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Dobbeltklikk-trigging" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Grensesnitt" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Ved nye tweets" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Handlinger" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Ved nye nevnelser" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Ved nye meldinger" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Aldri" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Hver" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Stabel 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Stabel 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Stabel 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Stabel 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Varslinger" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Runde profilbilder" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Fjern avsluttende hasjtagger" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Fjern medielenker" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Skjul upassende innhold" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Ingen kodesnutter satt opp." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Slå på kodesnutter ved å skrive med tastaturet og trykk TAB." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Kodesnutter" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Generelt" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Skriv tweet" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Vis kontoinnstillinger" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Vis pop-over med kontoer" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Vis programinnstillinger" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Slå på/av topplinje" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Gå tilbake" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Gå framover" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Gå til n-te sid" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Tweets" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Retweet" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Favoritt" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Svar" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Sitat" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Vis detaljer" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Slett" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Start ny samtale" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Med:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Kjør" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Siter" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Retweet tweet" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Merk tweet som favoritt" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Svar på tweet" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Mer" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Favoritt" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Svar" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Fjern blokkering" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Vis innstillinger for denne kontoen" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Åpne i nytt vindu" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Gå til profil" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Laget" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Abonnert på" corebird-1.7.4/po/nl.po000066400000000000000000000554541324604713000147200ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Geert Wirken , 2014 # Heimen Stoffels , 2014-2015-2015 # Nathan Follens, 2017 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 14:06+0000\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: Dutch (http://www.transifex.com/corebird/corebird/language/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: nl\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Twittertoepassing" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird is een geïntegreerde GTK+ Twittertoepassing met ondersteuning voor de belangrijkste functionaliteiten, zoals privéberichten (DM's), tweetmeldingen en gespreksweergaven." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Extra mogelijkheden zijn o.a. het weergeven van video's in de toepassing zelf, meerdere bijgevoegde afbeeldingen, lijsten, filters, meerdere accounts en nog veel meer." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Algemene tijdlijnweergave gebruiken" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Doorsnee Twitter-profiel" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Accountinstellingen kunnen worden ingesteld" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "Timm Bäder" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Gebruik Twitter vanuit een normale bureaubladtoepassing" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "In antwoord op" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "en" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "en %d anderen" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Privégesprek" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d nieuw bericht van %s" msgstr[1] "%d nieuwe berichten van %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Nieuw privébericht van %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Privéberichten" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "De tweets kunnen niet worden geladen" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Vind-ik-leuks" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Nieuw filter toevoegen" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Filters" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s heeft %s geretweet" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s tweette" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d nieuwe tweet!" msgstr[1] "%d nieuwe tweets!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Thuis" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Lijst" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Lijsten" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Ingestelde accounts weergeven" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Tweet opstellen" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Nieuw account toevoegen" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s heeft %s vermeld" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Vermeldingen" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "Opgeschort account" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Afgeschermd profiel" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Verstuur een tweet naar @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "Afgeschermd profiel" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Zoeken" #: src/SearchPage.vala:401 msgid "Load More" msgstr "Meer laden" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "De tweet kan niet worden weergegeven" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Retweets" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Openen in webbrowser" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Bron" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Details van tweet" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d ongelezen)" msgstr[1] "(%d ongelezen)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Verwijderen" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "%s blokkeren" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Deze tweet bevat afbeeldingen die gemarkeerd zijn als gevoelig" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Tóch weergeven" #: src/util/Utils.vala:146 msgid "Now" msgstr "Nu" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%du" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "Heeft u nog geen Twitter-account?" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Creëer een account" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "Niet geautoriseerd. Meestal betekent dit dat er iets mis is met de Twitter-servers en dat u het later opnieuw moet proberen." #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Het openen van %s is mislukt" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "Onjuiste pincode" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Account is al in gebruik" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "Er zijn geen gebruikers gevonden" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Afbeelding selecteren" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Openen" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Annuleren" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Volgen" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Ontvolgen" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "URL kopiëren" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "Opslaan als…" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "Video opslaan" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "Afbeelding opslaan" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Opslaan" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "Bezig met laden..." #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Geen items gevonden" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Opnieuw proberen" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "Omslagfoto selecteren" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "De afbeelding voldoet niet aan de minimale grootte-vereisten:" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "Minimale breedte: %d pixel" msgstr[1] "Minimale breedte: %d pixels" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "Minimale hoogte: %d pixel" msgstr[1] "Minimale hoogte: %d pixels" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "Kiezen" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Tweet citeren" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "Het geselecteerde bestand is geen afbeelding." #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Terug" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "De geselecteerde afbeelding is te groot. De maximale bestandsgrootte is %'d MB per afbeelding." #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "Per tweet wordt slechts één GIF-bestand toegestaan." #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "Emoji invoegen" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Filter aanpassen" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Overeenkomsten" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "Komt niet overeen" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Fragment bewerken" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "Het fragment mag niet leeg worden gelaten" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "De vervanging mag niet leeg worden gelaten" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "Het fragment mag geen witruimte bevatten" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "Het fragment bestaat al" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Hé! Kijk eens naar deze nieuwe #Corebird-versie! \\ (•◡•) / #cool #nieuwisaltijdbeter" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Gebruiker toevoegen aan of verwijderen uit lijst" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "U heeft geen lijsten." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Over Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Nieuw account" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "Om Corebird toestemming te geven heeft u een pincode nodig van twitter.com van het toe te voegen account" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Pincode aanvragen" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "Voer de pincode van twitter.com hieronder in:" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "Pincode" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Bevestigen" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Accountinstellingen" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Naam" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Website" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Automatisch laden" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Weet u zeker dat u dit account wilt verwijderen?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "Smileys en personen" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "Lichaam en kledij" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "Dieren en natuur" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "Eten en drinken" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "Reizen en plaatsen" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "Activiteiten" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "Objecten" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "Symbolen" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "Vlaggen" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "Geen resultaten gevonden" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "Probeer een andere zoekopdracht" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Verzenden" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Afbeelding toevoegen" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "Favoriete afbeeldingen weergeven" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Gebruikers" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Abonneren" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Opzeggen" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Abonnees:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Leden:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Auteur:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Gecreëerd op:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Bewerken" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Modus:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Privé" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Openbaar" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Omschrijving" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Instellingen" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Sneltoetsen" #: ui/menus.ui:15 msgid "About" msgstr "Over" #: ui/menus.ui:19 msgid "Quit" msgstr "Afsluiten" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Nieuw filter toevoegen" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Nieuw fragment toevoegen" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Sleutelwoord" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Vervangen door" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Nieuwe lijst creëren" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Naam:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Creëren" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Privébericht opstellen" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Toevoegen aan of verwijderen uit lijst" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Geblokkeerd" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Genegeerd" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Retweets uitschakelen" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Volgt u" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tweets" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Volgers" #: ui/profile-page.ui:326 msgid "Following" msgstr "Volgend" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "Media weergeven" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Altijd weergeven" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Altijd verbergen" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Verbergen op tijdlijn" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Automatisch scrollen bij nieuwe tweets" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Tweet openen door te dubbelklikken" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Uiterlijk" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Bij nieuwe tweets" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Acties" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Bij nieuwe vermeldingen" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Bij nieuwe berichten" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Nooit" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Elke" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "5 opstapelen" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "10 opstapelen" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "25 opstapelen" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "50 opstapelen" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Meldingen" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Ronde profielfoto's" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Bijgevoegde hashtags verwijderen" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Medialinks verwijderen" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Gevoelige inhoud verbergen" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Er zijn geen fragmenten ingesteld." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "U kunt fragmenten activeren door het sleutelwoord te typen en daarna op de tabtoets te drukken." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Fragmenten" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Algemeen" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Tweet opstellen" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Accountinstellingen weergeven" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Accounts-snelmenu weergeven" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Toepassingsinstellingen weergeven" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Bovenste balk weergeven/verbergen" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Terug" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Vooruit" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Ga naar pagina n" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Tweets" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Retweeten" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Vind ik leuk" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Beantwoorden" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Citeren" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Details weergeven" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Verwijderen" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "Opstellen" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "Emojikiezer weergeven" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Nieuw gesprek starten" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Met:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Gaan" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Tweet citeren" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Tweet retweeten" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Vind ik leuk" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Tweet beantwoorden" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Meer" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Vind ik leuk" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Beantwoorden" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Deblokkeren" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Accountinstellingen weergeven" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Openen in een nieuw venster" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Ga naar profiel" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Gecreëerd" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Geabonneerd op" corebird-1.7.4/po/pl.po000066400000000000000000000564511324604713000147200ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Piotr Drąg , 2014-2017 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 15:14+0000\n" "Last-Translator: Piotr Drąg \n" "Language-Team: Polish (http://www.transifex.com/corebird/corebird/language/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pl\n" "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Klient Twittera" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird jest klientem Twittera natywnym dla biblioteki GTK+ dostarczającym takie ważne funkcje, jak prywatne wiadomości, powiadomienia o tweetach oraz widok rozmowy." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Dodatkowe funkcje obejmują lokalne wyświetlanie filmów i wielu obrazów osadzonych w tweetach oraz obsługę list, filtrów i wielu kont na raz." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Oś czasu programu Corebird" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Typowy profil Twittera" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Można konfigurować ustawienia konta" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "Timm Bäder" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Korzystanie z Twittera w zwykłym programie" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "W odpowiedzi do" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "i" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "i %d innych" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Prywatna rozmowa" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d nowa wiadomość od %s" msgstr[1] "%d nowe wiadomości od %s" msgstr[2] "%d nowych wiadomości od %s" msgstr[3] "%d nowych wiadomości od %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Nowa wiadomość prywatna od %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Wiadomości prywatne" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "Nie można wczytać tweetów" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Ulubione" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Dodanie nowego filtru" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Filtry" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "Użytkownik %s podał dalej tweet użytkownika %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "Użytkownik %s napisał tweet" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d nowy tweet" msgstr[1] "%d nowe tweety" msgstr[2] "%d nowych tweetów" msgstr[3] "%d nowych tweetów" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Główna" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Lista" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Listy" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Wyświetla skonfigurowane konta" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Napisz tweet" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Dodanie nowego konta" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "Użytkownik %s wspomniał użytkownika %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Wzmianki" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "Zawieszone konto" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Profil chroniony" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Tweet do @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "Profil chroniony" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Wyszukaj" #: src/SearchPage.vala:401 msgid "Load More" msgstr "Wczytaj więcej" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Nie można wyświetlić tweeta" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Podane dalej" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Otwórz w przeglądarce" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Źródło" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Szczegóły tweeta" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d nieprzeczytana)" msgstr[1] "(%d nieprzeczytane)" msgstr[2] "(%d nieprzeczytanych)" msgstr[3] "(%d nieprzeczytanych)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Usuń" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Zablokuj użytkownika %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Ten tweet zawiera obrazy oznaczone jako nieodpowiednie" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Wyświetl mimo to" #: src/util/Utils.vala:146 msgid "Now" msgstr "Teraz" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%d min" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%d godz." #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "Nie masz jeszcze konta serwisu Twitter?" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Utwórz konto" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "Brak upoważnienia. Zwykle oznacza to, że coś jest nie tak z serwerami Twittera i powinno się spróbować później" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Nie można otworzyć %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "Błędny numer PIN" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Konto jest już używane" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "Nie odnaleziono żadnych użytkowników" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Wybór obrazu" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Otwórz" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Anuluj" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Obserwuj" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Przestań obserwować" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "Skopiuj adres URL" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "Zapisz jako…" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "Zapis filmu" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "Zapis obrazu" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Zapisz" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "Wczytywanie…" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Nie odnaleziono żadnych wpisów" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Ponów" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "Wybór obrazu banera" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "Obraz nie spełnia minimalnych wymaganych wymiarów:" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "Minimalna szerokość: %d piksel" msgstr[1] "Minimalna szerokość: %d piksele" msgstr[2] "Minimalna szerokość: %d pikseli" msgstr[3] "Minimalna szerokość: %d pikseli" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "Minimalna wysokość: %d piksel" msgstr[1] "Minimalna wysokość: %d piksele" msgstr[2] "Minimalna wysokość: %d pikseli" msgstr[3] "Minimalna wysokość: %d pikseli" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "Wybierz" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Cytowanie tweeta" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "Wybrany plik nie jest obrazem." #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Wstecz" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "Wybrany obraz jest za duży. Maksymalny rozmiar pliku na obraz to %'d MB." #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "Dozwolony jest tylko jeden plik GIF na tweet." #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "Wstaw emoji" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Modyfikacja filtru" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Pasujące" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "Niepasujące" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Modyfikacja wstawki" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "Wstawka nie może być pusta" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "Zamiennik nie może być pusty" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "Wstawka nie może zawierać spacji" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "Wstawka już istnieje" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Hej, #Corebird ma nową wersję! \\ (•◡•) / #super #nowejestzawszelepsze" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Dodawanie lub usuwanie użytkowników z listy" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Nie ma żadnych list." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "O programie Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Nowe konto" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "Aby uwierzytelnić program Corebird, należy podać PIN z serwisu twitter.com dla dodawanego konta" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Zażądaj kod PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "Proszę podać kod PIN z serwisu twitter.com poniżej:" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "Kod PIN" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Potwierdź" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Ustawienia konta" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Nazwa" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Strona WWW" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Automatyczne uruchamianie" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Na pewno usunąć to konto?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "Uśmieszki i osoby" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "Ciało i ubrania" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "Zwierzęta i przyroda" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "Jedzenie i napoje" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "Podróże i miejsca" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "Sport" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "Rzeczy" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "Symbole" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "Flagi" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "Brak wyników" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "Proszę spróbować innych słów" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Wyślij" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Dodaj obraz" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "Wyświetla ulubione obrazy" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Użytkownicy" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Subskrybuj" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Usuń subskrypcję" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Subskrybenci:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Członkowie:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Twórca:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Utworzono:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Modyfikuj" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Tryb:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Prywatny" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Publiczny" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Opis" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Ustawienia" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Skróty" #: ui/menus.ui:15 msgid "About" msgstr "O programie" #: ui/menus.ui:19 msgid "Quit" msgstr "Zakończ" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Dodanie nowego filtru" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Dodanie nowej wstawki" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Słowo kluczowe" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Zamiennik" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Utwórz nową listę" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Nazwa:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Utwórz" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Napisz prywatną wiadomość" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Dodaj/usuń z listy" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Zablokowany" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Wyciszony" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Wyłączono podawanie dalej" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Obserwuje cię" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tweety" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Obserwujący" #: ui/profile-page.ui:326 msgid "Following" msgstr "Obserwowanie" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "Multimedia w wiadomościach" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Wyświetlanie" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Ukrywanie" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Ukrywanie na osi czasu" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Automatyczne przewijanie do nowych tweetów" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Aktywacja podwójnym kliknięciem" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Interfejs" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Po otrzymaniu nowych tweetów" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Działania" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Po otrzymaniu nowych wzmianek" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Po otrzymaniu nowych wiadomości" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Nigdy" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Każdy" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Co pięć" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Co dziesięć" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Co dwadzieścia pięć" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Co pięćdziesiąt" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Powiadomienia" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Okrągłe awatary" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Usuwanie końcowych znaczników #" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Usuwanie odnośników do multimediów" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Ukrywanie nieodpowiednich treści" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Nie skonfigurowano żadnych wstawek." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Można aktywować wstawkę pisząc słowo kluczowe i naciskając klawisz Tab." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Wstawki" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Ogólne" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Napisanie tweeta" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Wyświetlenie ustawień konta" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Wyświetlenie okienka kont" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Wyświetlenie ustawień programu" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Przełączenie górnego paska" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Wstecz" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Dalej" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Przejście do n-tej strony" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Tweety" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Podanie tweeta dalej" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Dodanie do ulubionych" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Odpowiadanie" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Cytowanie" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Wyświetlenie szczegółów" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Usunięcie" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "Pisanie" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "Wyświetlenie okna wyboru emoji" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Rozpocznij nową rozmowę" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Z:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Przejdź" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Cytuj" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Podaj tweet dalej" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Dodaj tweet do ulubionych" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Odpowiedz na tweet" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Więcej" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Dodaje do ulubionych" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Odpowiada" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Odblokuj" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Wyświetla ustawienia tego konta" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Otwiera w nowym oknie" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Przejdź do profilu" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Utworzono" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Subskrybowano do" corebird-1.7.4/po/pt.po000066400000000000000000000526621324604713000147300ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Bruno Guerreiro , 2015 # Steeven Lopes , 2017 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: Portuguese (Portugal) (http://www.transifex.com/corebird/corebird/language/pt_PT/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pt_PT\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Cliente de Twitter" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird é um cliente twitter nativo GTK+ que fornece recursos vitais como Mensagens Diretas (DMs), notificações de tweet, exibição de conversas." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Funcionalidades adicionais incluem visualização de vídeos locais, múltiplas imagens em linha, Listas, Filtros, múltiplas contas, entre outros." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Mostrar timeline genérica ao usar o Corebird" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Perfil típico do Twitter" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Definições da conta podem ser configuradas" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Usar o Twitter de uma aplicação de desktop normal" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Conversa Direta" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d nova Mensagem de %s" msgstr[1] "%d novas Mensagens de %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Nova mensagem direta de %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Mensagens diretas" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "Não foi possível obter os tweets" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Favoritos" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Adicionar um novo filtro" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Filtros" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s retweetou %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s twittou" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d novo Tweet!" msgstr[1] "%d novos Tweets!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Inicio" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Lista" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Listas" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Mostrar contas configuradas" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Compor Tweet" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Adicionar uma nova conta" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s mencionado %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Citações" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Perfil protegido" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Tweet para @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Procurar" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Não possível mostrar o tweet" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Retweets" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Abrir no navegador" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Fonte" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Detalhes do Tweet" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d não lido)" msgstr[1] "(%d não lidos)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Apagar" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Bloco %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Este tweet contém imagens marcadas como impróprio" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Mostrar de qualquer forma" #: src/util/Utils.vala:146 msgid "Now" msgstr "Agora" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Criar uma" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Não foi possível abrir %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "PIN errado" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Conta já me uso" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "Não foram encontrados usuários " #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Selecionar imagens" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Abrir" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Cancelar" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Seguir" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Deixar de seguir" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "Copiar URL" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Gravar" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Nenhuma entrada encontrada" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Tentar novamente" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Citar tweet" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Voltar" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "A imagem selecionada é muito grande. O tamanho máximo por imagem é de %'d MB" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Modificar Filtro" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Corresponde" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Modificar o snippet" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "Snippet não pode conter espaços em branco" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "Snippet já existe" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Ei, confira essa nova versão do #Corebird! \\ (•◡•) / #cool #novoesempremelhor" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Adicionar ou Remover Utilizador Da Lista" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Você não tem listas." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Sobre Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Nova Conta" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Pedir PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Confirmar" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Definições de conta" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Nome" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Website" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Iniciar automaticamente" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Quer realmente pagar a conta?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Enviar" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Adicionar Imagem" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Utilizadores" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Subscrever" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Cancelar subscrição" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Inscritos:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Membros:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Criador:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Criado em:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Editar" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Modo:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Privado" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Publico" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Descrição" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Definições" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Atalhos" #: ui/menus.ui:15 msgid "About" msgstr "Sobre" #: ui/menus.ui:19 msgid "Quit" msgstr "Sair" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Adicionar Novo Filtro" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Adicionar novo fragmento" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Palavra-Chave" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Substituição" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Criar nova lista" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Nome:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Criado" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Escrever Mensagem Direta" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Adicionar/remover da lista" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Bloquado" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Mudo" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Retweets desativados" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Segue-te" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tweets" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Seguidores" #: ui/profile-page.ui:326 msgid "Following" msgstr "Seguindo" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Mostrar sempre" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Esconder sempre" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Esconder no timeline" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Deslocação automática de novos tweets" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Ativação do clique-duplo" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Interface" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Em novos tweets" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Ações" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Em novas citações" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Em novas mensagens" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Nunca" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Todo" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Pilha 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Pilha 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Pilha 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Pilha 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Notificações" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Avatares redondos" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Remover hashtags à direita" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Remover ligações de mídia" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Ocultar conteúdo impróprio" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Nenhum fragmento configurado." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Pode ativar fragmentos escrevendo a palavra-chave e depois pressionar TAB." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Fragmentos" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Geral" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Compor Tweet" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Mostrar definições da conta" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Mostrar Contas Popover" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Mostrar Definições da Aplicação" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Alternar para a barra superior" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Voltar atrás" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Ir para frente" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Ir para a página nth" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Tweets" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Retweet" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Favorito" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Responder" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Citar" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Mostrar Detalhes" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Eliminar" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Iniciar nova conversa" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Com:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Ir" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Citar" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Retweetar tweet" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Tweet favorito" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Responder a tweet" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Mais" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Favorito" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Responder" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Desbloquear" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Mostrar definições desta conta" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Abrir em numa nova janela" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Ir para o perfil" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Criado" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Subscrito" corebird-1.7.4/po/pt_BR.po000066400000000000000000000541341324604713000153070ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Anders Bateva , 2016 # Evertton de Lima , 2015 # Luderson Costa , 2016 # Mauro Pereira Junior , 2015 # Ricardo Borges Jr. , 2015-2017 # Salomão Carneiro de Brito , 2015 # Vanderlei Ventura , 2018 # x_root , 2015 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2018-01-02 18:35+0000\n" "Last-Translator: Vanderlei Ventura \n" "Language-Team: Portuguese (Brazil) (http://www.transifex.com/corebird/corebird/language/pt_BR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pt_BR\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Cliente Twitter" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird é um cliente twitter nativo GTK+ que fornece recursos vitais como mensagens diretas (DMs), notificações de tweet, exibição de conversas." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Funcionalidades adicionais incluem visualização de vídeos locais, múltiplas imagens em linha, Listas, Filtros, múltiplas contas, entre outros." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Timeline genérica quando visualizada usando Corebird" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "perfil do Twitter típico" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Configuração de conta pode ser configurada" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "Timm Bäder" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Use o Twitter de um aplicativo desktop normal" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter:" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "Respondendo a" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "e" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "e %d outros" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Conversa direta" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d nova mensagem de %s" msgstr[1] "%d novas mensagens de %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Nova mensagem direta de %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Mensagens diretas" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "Não foi possível carrega os tweets" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Favoritos" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Adicionar novo Filtro" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Filtros" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s retweetou %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s twittou" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d novo tweet!" msgstr[1] "%d novos tweets!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Início" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Lista" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Listas" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Mostrar contas configuradas" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Compor tweet" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Adicionar nova conta" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s mencionado %s " #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Menções" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Perfil protegido" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Tweet para @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Pesquisa" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Não foi possível mostrar o tweet" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Retweets" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Abrir no navegador" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Fonte" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Detalhes do tweet" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d não lido)" msgstr[1] "(%d não lidos)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Excluir" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Bloquear%s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Este tweet contém imagens marcadas como inapropriadas" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Mostrar de qualquer maneira" #: src/util/Utils.vala:146 msgid "Now" msgstr "Agora" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "Ainda não tem uma conta do Twitter?" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Criar uma" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "Não autorizado. Na maioria das vezes, isso significa que há algo errado com os servidores do Twitter e você deve tentar novamente mais tarde" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Não foi possível abrir %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "PIN errado" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Conta já em uso" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "Usuário não encontrado" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Selecione a image" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Abrir" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Cancelar" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Seguir" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Deixar de seguir" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "Copiar URL" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "Salvar como..." #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "Salvar Vídeo" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "Salvar imagem" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Salvar" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "Carregando..." #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Nenhuma entrada encontrada" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Tentar novamente" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Citar tweet" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Voltar" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "A imagem selecionada é muito grande. O tamanho máximo do arquivo por imagem é %'d MB" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "Somente um arquivo GIF por tweet é permitido." #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Modificar o filtro" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Corresponde" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "Não Corresponde" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Modificar snippet" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "O snippet não pode contém espaços" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "Snippet já existente" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Ei, confira essa nova versão do #Corebird! \\ (•◡•) / #legal #novoesempremelhor" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Adicionar ou remover o usuário da lista" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Você não tem listas." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Sobre o Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Nova conta" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Pedido de PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "Digite o PIN do twitter.com abaixo:" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "PIN" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Confirmar" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Configurações da conta" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Nome" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Website" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Início automático" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Você realmente quer excluir esta conta?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Enviar" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Adicionar imagem" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Usuários" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Inscreva-se" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Cancelar inscrição" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Inscritos:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Membros:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Criador:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Criado em:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Editar" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Modo:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Privado" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Público" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Descrição" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Configurações" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Atalhos" #: ui/menus.ui:15 msgid "About" msgstr "Sobre" #: ui/menus.ui:19 msgid "Quit" msgstr "Sair" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Adicionar novo filtro" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Adicionar novo snippet" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Palavra-chave" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Substituição" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Criar nova lista" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Nome:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Criar" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Escrever mensagem direta" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Adicionar/remover da lista" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Bloqueado" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Silenciado" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Retweets desativados" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Você segue" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tweets" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Seguidores" #: ui/profile-page.ui:326 msgid "Following" msgstr "Seguindo" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Sempre mostrar" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Sempre esconder" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Esconder na timeline" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Rolagem automática de novos tweets" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Ativar dois cliques" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Interface" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Em novos tweets" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Ações" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Em novas menções" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Em novas mensagens" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Nunca" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Todo" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Pilha 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Pilha 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Pilha 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Pilha 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Notificações" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Avatares redondos" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Remover hashtags à direita" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Remover links de mídia" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Ocultar conteúdo inapropriado" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Nenhum snippet configurado." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Você pode ativar snippets digitando a palavra-chave e depois pressionando TAB." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Snippets" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Geral" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Compor tweet" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Mostrar configurações da conta" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Mostrar popover das contas" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Mostrar configurações da aplicação" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Alterar barra superior" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Voltar" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Avançar" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Ir para página n°" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Tweets" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Retweet" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Favorito" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Responder" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Citar" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Mostrar detalhes" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Excluir" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Iniciar nova conversa" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Com:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Ir" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Citação" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Retweetar" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Curtir" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Responder" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Mais" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Favorito" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Responder" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Desbloquear" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Mostrar configurações dessa conta" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Abrir em uma nova janela" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Ir para o perfil" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Criado" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Inscrever-se" corebird-1.7.4/po/ro.po000066400000000000000000000505761324604713000147270ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Iulian Vărzaru , 2015 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: Romanian (Romania) (http://www.transifex.com/corebird/corebird/language/ro_RO/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ro_RO\n" "Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Client Twitter" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird este un client de Twitter nativ GTK+ care furnizează caracteristici vitale ca Mesaje Directe (DM), notificări, pagina de conversaţii." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Caracteristici adiţionale includ vizualizarea videoclipurilor local, imagini in linie multiple, Liste, Filtre, conturi multiple, etc." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Conversaţie directă" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Un nou mesaj direct de la %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Mesaje directe" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Apreciate" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Pagina de pornire" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Listă" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Liste" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Arată conturile configurate" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Compune Tweet" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Menţionări" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Profil protejat" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Caută" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Nu am putut afişa tweet" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Deschide în Browser" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Sursă" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Detalii tweet" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d necitit)" msgstr[1] "(%d necitite)" msgstr[2] "(%d necitite)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Şterge" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "" #: src/util/Utils.vala:146 msgid "Now" msgstr "Acum" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Creează unul" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Nu s-a putut deschide %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "PIN Greşit" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Cont deja in folosinţă" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Anulează" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Urmăreşte" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Nu mai urmări" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Salvează" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Nu a fost găsit nici un rezultat" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Reîncearcă" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Încorporează tweet" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Înapoi" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Modifică Filtru" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Se potriveşte" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Adaugă sau Şterge un utilizator din listă" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Nu ai nici o listă." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Despre Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Cont nou" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Cere PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Confirmă" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Setări cont" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Nume" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Website" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Pornire automată" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Eşti sigur că doreşti să ştergi contul?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Trimite" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Utilizatori" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Abonează-te" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Dezabonează-te" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Abonaţi:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Membrii:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Creator:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Creat la:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Editează" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Mod:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Privat" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Public" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Setări" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "" #: ui/menus.ui:15 msgid "About" msgstr "Despre" #: ui/menus.ui:19 msgid "Quit" msgstr "Închide" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Adaugă un filtru nou" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Adaugă un nou fragment de text" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Cuvânt cheie" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Înlocuitor" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Creează o listă nouă" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Nume:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Creează" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Scrie mesaj direct" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Adaugă sau Şterge din listă" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Blocat" #: ui/profile-page.ui:24 msgid "Muted" msgstr "" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Te urmăreşte" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tweeturi" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Urmăritori" #: ui/profile-page.ui:326 msgid "Following" msgstr "Urmăriţi" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Derulare automată la tweeturi noi" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Activare prin dublu click" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Interfaţă" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "La tweeturi noi" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Acţiuni" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "La menţionări noi" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "La mesaje noi" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Niciodată" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Fiecare" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Câte 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Câte 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Câte 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Câte 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Notificări" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Avatare rotunde" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Şterge hashtag-urile de la sfârşit" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Şterge linkurile media" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Nici un fragment de text configurat." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Poţi activa fragmentele de text scriinde cuvântul cheie si apasând tasta TAB." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Fragmente" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Începe o conversaţie nouă" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Cu:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Începe" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Încorporează" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Apreciază tweet" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Răspunde la tweet" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Mai multe" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Apreciază" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Răspunde" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Deblochează" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Arată setările acestui cont" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Deschide intr-o pagină nouă" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Creat" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Abonat la" corebird-1.7.4/po/ru.po000066400000000000000000000607051324604713000147300ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Alex Baranov , 2016 # Alexander , 2016 # Andrew Makarov, 2015 # Dmitry Kireev , 2015 # Egor Erastov , 2018 # George - , 2018 # Ivlev Denis , 2015 # Max Koloskov , 2015 # Mikhail Ivanov , 2016 # netforhack, 2015 # Yevgeny Kvitchenko , 2017 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2018-03-01 17:12+0000\n" "Last-Translator: Egor Erastov \n" "Language-Team: Russian (http://www.transifex.com/corebird/corebird/language/ru/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" "Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Клиент Твиттер" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird является родным GTK + клиентом Twitter, который поддерживает такие функции, как прямые сообщения (DMS), уведомления и беседы." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Дополнительные функции включают в себя: просмотр видео и изображений в клиенте, списки, фильтры, несколько учетных записей и т.д." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Общий вид ленты при использовании Corebird" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Типичный профиль Twitter" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Настройки аккаунта можно изменить" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "Timm Bäder" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Пользуйтесь Твиттером как обычным приложением" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "и" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Личная беседа" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d новое сообщение от %s" msgstr[1] "%d новых сообщения от %s" msgstr[2] "%d новых сообщений от %s" msgstr[3] "%d новых сообщений от %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Новое сообщение от %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Сообщения" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "Не получается загрузить твиты" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Избранные" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Добавить новый фильтр" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Фильтры" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s ретвитнут %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s твитнут" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d новый твит!" msgstr[1] "%d новых твита!" msgstr[2] "%d новых твитов!" msgstr[3] "%d новых твитов!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Домой" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Список" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Списки" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Настройки аккаунтов" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Написать твит" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Добавить новый аккаунт" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s упомянул %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Упоминания" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Защищённый профиль" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Твитнуть @%s " #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "Защищённый профиль" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Поиск" #: src/SearchPage.vala:401 msgid "Load More" msgstr "Больше..." #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Не удалось показать Твит" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Ретвиты" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Открыть в браузере" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Источник" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Подробности твита" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d не прочитано)" msgstr[1] "(%d не прочитано)" msgstr[2] "(%d не прочитано)" msgstr[3] "(%d не прочитано)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Удалить" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Блокировать %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Медиафайлы могут носить деликатный характер" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Все равно показать" #: src/util/Utils.vala:146 msgid "Now" msgstr "Сейчас" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dм" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dч" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Создать" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Не могу открыть %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "Неверный PIN" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Аккаунт уже используется" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "Пользователь не найден" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Выбрать изображение" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Открыть" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Отмена" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Читать" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Не читать" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "Копировать URL" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Сохранить" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Нет записей" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Повтор" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgstr[3] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Цитировать tweet" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Назад" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "Выбранное изображение слишком велико. Максимальный размер файла изображения %'d MB" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Изменить фильтр" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Совпадения" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Изменить сниппет" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "Сниппет не может содержать пробелы" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "Сниппеты уже существуют" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Привет, появилась новая версия #Corebird! \\ (•◡•) / #cool #newisalwaysbetter" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Добавить или удалить пользователя из списка" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "У вас нет списков." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "О Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Новый аккаунт" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Запросить PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Подтвердить" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Настройки аккаунта" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Имя" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Вебсайт" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Автозапуск" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Вы действительно хотите удалить данный аккаунт?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Отправить" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Добавить изображение" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Пользователи" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Подписаться" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Отписаться" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Подписчики:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Участники:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Создатель:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Создан:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Редактировать" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Режим:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Приватный" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Открытый" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Описание" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Настройки" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Шоткаты" #: ui/menus.ui:15 msgid "About" msgstr "О программе" #: ui/menus.ui:19 msgid "Quit" msgstr "Выход" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Добавить новый фильтр" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Добавить новый сниппет" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Ключевое слово" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Замена" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Создать новый список" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Имя:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Создать" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Написать личное сообщение" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Добавить/Удалить из списка" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Заблокирован" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Беззвучно" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Ретвиты отключены" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Читают тебя" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Твиты" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Избранные" #: ui/profile-page.ui:326 msgid "Following" msgstr "Читаешь" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "Показать встроенные средства" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Всегда показывать" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Всегда скрывать" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Скрыть в ленте" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Автопрокрутка к новым твитам" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Активация по двойному клику" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Интерфейс" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "К новым твитам" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Действия" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "К новым упоминаниям" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "К новым сообщениям" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Никогда" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Каждый" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Стек 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Стек 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Стек 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Стек 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Уведомления" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Округлять аватары" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Удалить завершающие хэштеги" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Удалить ссылки на медиа" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Спрятать неуместные картинки" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Настроенных сниппетов нет." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Вы можете отметить и переместить слово в тексте нажав клавишу TAB" #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Сниппеты" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Общий" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Написать твит" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Показать настройки аккаунта" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Показать всплывающее окно аккаунтов" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Показать настройки приложения" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Переключить тулбар" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Назад" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Вперед" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Перейти на страницу nth" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Твиты" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Ретвит" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Избранные" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Ответить" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Цитировать" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Показать подробности" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Удалить" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Начать новую беседу" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "С:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Вперед" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Цитата" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Ретвитнуть" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "В избранное" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Ответить" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Ещё..." #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "В избранные" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Ответить" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Разблокировать" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Показать настройки аккаунта" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Открыть в новом окне" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Перейти к профилю" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Создан" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Подписаться" corebird-1.7.4/po/sr.po000066400000000000000000000635171324604713000147320ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Марко М. Костић (Marko M. Kostić) , 2016-2017 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-11-19 08:34+0000\n" "Last-Translator: Марко М. Костић (Marko M. Kostić) \n" "Language-Team: Serbian (http://www.transifex.com/corebird/corebird/language/sr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sr\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Корберд" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Твитер клијент" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Корберд је домаћи ГТК+ твитер клијент који пружа основне могућности као што су директне поруке, обавештења о твитовима и прегледе разговора." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Додатне могућности садрже локално прегледање видео записа, вишеструке угнежђене слике, спискове, филтере, вишеструке налоге и тако даље." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Општи преглед временске линије када се користи Корберд" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Уобичајени Твитер профил" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Подешавања налога се могу мењати" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "Timm Bäder" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Користите Твитер помоћу обичног рачунарског програма" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;tviter;твитер;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "Одговор за" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "и" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "и %d других" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Директни разговори" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d нова порука од %s" msgstr[1] "%d нове поруке од %s" msgstr[2] "%d нових порука од %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Нова директна порука од %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Директне поруке" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "Не могу да учитам твитове" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Омиљено" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Додај нови филтер" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Филтери" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s је поново твитовао/ла %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s је твитовао/ла" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d нови твит!" msgstr[1] "%d нова твита!" msgstr[2] "%d нових твитова!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Почетна" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Списак" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Спискови" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Прикажи подешене налоге" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Састави твит" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Додај нови налог" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s спомену %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Спомињања" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "Обустављени налог" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Заштићен профил" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Твитуј ка @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "Заштићени профил" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Претрага" #: src/SearchPage.vala:401 msgid "Load More" msgstr "Учитај још" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Не могу да прикажем твит" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "поновних твитова" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Отвори у прегледачу" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "извор" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "појединости твита" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d непрочитана)" msgstr[1] "(%d непрочитане)" msgstr[2] "(%d непрочитаних)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Обриши" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Блокирај %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Означено је да овај твит садржи недоличне слике" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Ипак прикажи" #: src/util/Utils.vala:146 msgid "Now" msgstr "сада" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dм" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dч" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "Још немате Твитер налог?" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Направи налог" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "Неовлашћено. У већини случајева ово значи да нешто није у реду са Твитеровим серверима и да требате покушати касније" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Не могу да отворим %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "Погрешан ПИН" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Налог се већ користи" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "Нема пронађених корисника" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Изабери слику" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Отвори" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Откажи" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Прати" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Престани са праћењем" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "Копирај УРЛ" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "Сачувај као…" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "Сачувај видео" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "Сачувај слику" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Сачувај" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "Учитавам…" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Нема пронађених уноса" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Поново покушај" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "Изаберите слику барјака" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "Слика не испуњава минималне захтеве о величини:" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "Минимална ширина: %d пиксел" msgstr[1] "Минимална ширина: %d пиксела" msgstr[2] "Минимална ширина: %d пиксела" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "Минимална висина: %d пиксел" msgstr[1] "Минимална висина: %d пиксела" msgstr[2] "Минимална висина: %d пиксела" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "Изабери" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Цитирај твит" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "Изабрана датотека није слика." #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Назад" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "Изабрана слика је сувише велика. Највећа дозвољена величина датотеке по слици је %'d мегабајта" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "Дозвољен је само један ГИФ по твиту." #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "Убаци емоџи" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Измени филтер" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "подудара се" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "Не поклапа се" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Измени исечак" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "Исечак не може бити празан" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "Замена не може бити празна" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "Исечак не може садржати размак" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "Исечак већ постоји" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Хеј, баци поглед на ново #Corebird издање! \\ (•◡•) / #cool #новојеувекбоље" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Додај или уклони корисника са списка" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Немате ниједан списак." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "О Корберду" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Нови налог" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "Да бисте овластили Корберд, морате унети ПИН са twitter.com преко налога којег желите додати" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Затражи ПИН" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "Унесите ПИН са twitter.com испод:" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "ПИН" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Потврди" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Подешавања налога" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Име" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Веб сајт" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Самостално покрени" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Да ли стварно желите обрисати овај налог?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "Смешци и људи" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "Тело и одећа" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "Животиње и природа" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "Храна и пиће" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "Путовање и места" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "Активности" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "Ствари" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "Знакови" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "Заставе" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "Нема резултата" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "Пробајте са другачијом претрагом" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Пошаљи" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Додај слику" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "Прикажи омиљене слике" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Корисници" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Претплати се" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Укини претплату" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Претплатници:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Чланови:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Творац:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Направљено у:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Уреди" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Режим:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Приватно" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Јавно" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Опис" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Подешавања" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Пречице" #: ui/menus.ui:15 msgid "About" msgstr "О програму" #: ui/menus.ui:19 msgid "Quit" msgstr "Изађи" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Додај нови филтер" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Додај нови исечак" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Кључна реч" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Замена" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Направи нови списак" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Име:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Направи" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Напиши директну поруку" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Додај или уклони са списка" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Блокиран(а)" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Пригушен(а)" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Поновни твитови онемогућени" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Прати вас" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Твитови" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Пратиоци" #: ui/profile-page.ui:326 msgid "Following" msgstr "Прати" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "Прикажи угнежђени садржај" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Увек прикажи" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Увек скривај" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Сакриј са временске линије" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Аутоматски клизај при новим твитовима" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Активирај на дупли клик" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Интерфејс" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "При новим твитовима" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Радње" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "При новим спомињањима" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "При новим порукама" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Никада" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Сваки пут" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Сакупи 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Сакупи 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Сакуп 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Сакупи 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Обавештења" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Округли аватари" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Уклони пратеће тарабице" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Уклони мултимедијалне везе" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Сакриј неприкладни садржај" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Нема подешених исечака." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Увек можете активирати исечке писањем кључне речи и притиском тастера TAB." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Исечци" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Опште" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Састави твит" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Прикажи подешавања налога" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Прикажи искачући прозор налога" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Прикажи подешавања програма" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Укључи или искључи горњу траку" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Иди назад" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Иди напред" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Иди на одређену страну" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Твитови" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Поново твитуј" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Означи као омиљено" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Одговори" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Цитирај" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Прикажи појединости" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Обриши" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "Састави" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "Прикажи бирача емоџија" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Започни нови разговор" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Са:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Иди" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Цитирај" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Поново твитуј твит" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Означи твит омиљеним" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Одговори на твит" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Више" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Стави као омиљено" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Одговори" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Деблокирај" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Прикажи подешавања овог налога" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Отвори у новом прозору" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Иди на профил" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Направи" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Претплаћен(а) на" corebird-1.7.4/po/sr@latin.po000066400000000000000000000551561324604713000160620ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-11-20 17:05+0000\n" "Last-Translator: Slobodan Terzić \n" "Language-Team: Serbian (Latin) (http://www.transifex.com/corebird/corebird/language/sr%40latin/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sr@latin\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Twitter klijent" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird je domaći GTK+ tviter klijent koji pruža osnovne mogućnosti kao što su direktne poruke, obaveštenja o tvitovima i preglede razgovora." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Dodatne mogućnosti sadrže lokalno pregledanje video zapisa, višestruke ugnežđene slike, spiskove, filtere, višestruke naloge i tako dalje." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "Opšti pregled vremenske linije kada se koristi Corebird" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "Uobičajeni Twitter profil" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "Podešavanja naloga se mogu menjati" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "Timm Bäder" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "Koristite Twitter pomoću običnog računarskog programa" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;tviter;tviter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "Odgovor za" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "i" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "i %d drugih" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Direktni razgovori" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d nova poruka od %s" msgstr[1] "%d nove poruke od %s" msgstr[2] "%d novih poruka od %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Nova direktna poruka od %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Direktne poruke" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "Ne mogu da učitam tvitove" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Omiljeno" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "Dodaj novi filter" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "Filteri" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s je ponovo tvitovao/la %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s je tvitovao/la" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d novi tvit!" msgstr[1] "%d nova tvita!" msgstr[2] "%d novih tvitova!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Početna" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Spisak" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Spiskovi" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Prikaži podešene naloge" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Sastavi tvit" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "Dodaj novi nalog" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s spomenu %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Spominjanja" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "Obustavljeni nalog" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Zaštićen profil" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Tvituj ka @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "Zaštićeni profil" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Pretraga" #: src/SearchPage.vala:401 msgid "Load More" msgstr "Učitaj još" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Ne mogu da prikažem tvit" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "ponovnih tvitova" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Otvori u pregledaču" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "izvor" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "pojedinosti tvita" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d nepročitana)" msgstr[1] "(%d nepročitane)" msgstr[2] "(%d nepročitanih)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Obriši" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "Blokiraj %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "Označeno je da ovaj tvit sadrži nedolične slike" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "Ipak prikaži" #: src/util/Utils.vala:146 msgid "Now" msgstr "sada" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dč" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "Još nemate Twitter nalog?" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Napravi nalog" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "Neovlašćeno. U većini slučajeva ovo znači da nešto nije u redu sa Twitterovim serverima i da trebate pokušati kasnije" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Ne mogu da otvorim %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "Pogrešan PIN" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Nalog se već koristi" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "Nema pronađenih korisnika" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "Izaberi sliku" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "Otvori" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Otkaži" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Prati" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Prestani sa praćenjem" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "Kopiraj URL" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "Sačuvaj kao…" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "Sačuvaj video" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "Sačuvaj sliku" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Sačuvaj" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "Učitavam…" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Nema pronađenih unosa" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Ponovo pokušaj" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "Izaberite sliku barjaka" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "Slika ne ispunjava minimalne zahteve o veličini:" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "Minimalna širina: %d piksel" msgstr[1] "Minimalna širina: %d piksela" msgstr[2] "Minimalna širina: %d piksela" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "Minimalna visina: %d piksel" msgstr[1] "Minimalna visina: %d piksela" msgstr[2] "Minimalna visina: %d piksela" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "Izaberi" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Citiraj tvit" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "Izabrana datoteka nije slika." #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Nazad" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "Izabrana slika je suviše velika. Najveća dozvoljena veličina datoteke po slici je %'d megabajta" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "Dozvoljen je samo jedan GIF po tvitu." #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "Ubaci emodži" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Izmeni filter" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "podudara se" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "Ne poklapa se" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "Izmeni isečak" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "Isečak ne može biti prazan" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "Zamena ne može biti prazna" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "Isečak ne može sadržati razmak" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "Isečak već postoji" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Hej, baci pogled na novo #Corebird izdanje! \\ (•◡•) / #cool #novojeuvekbolje" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Dodaj ili ukloni korisnika sa spiska" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Nemate nijedan spisak." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "O Corebirdu" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Novi nalog" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "Da biste ovlastili Corebird, morate uneti PIN sa twitter.com preko naloga kojeg želite dodati" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Zatraži PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "Unesite PIN sa twitter.com ispod:" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "PIN" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Potvrdi" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Podešavanja naloga" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Ime" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Veb sajt" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Samostalno pokreni" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Da li stvarno želite obrisati ovaj nalog?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "Smešci i ljudi" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "Telo i odeća" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "Životinje i priroda" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "Hrana i piće" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "Putovanje i mesta" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "Aktivnosti" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "Stvari" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "Znakovi" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "Zastave" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "Nema rezultata" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "Probajte sa drugačijom pretragom" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Pošalji" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Dodaj sliku" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "Prikaži omiljene slike" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Korisnici" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Pretplati se" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Ukini pretplatu" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Pretplatnici:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Članovi:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Tvorac:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Napravljeno u:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Uredi" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Režim:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Privatno" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Javno" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "Opis" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Podešavanja" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Prečice" #: ui/menus.ui:15 msgid "About" msgstr "O programu" #: ui/menus.ui:19 msgid "Quit" msgstr "Izađi" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Dodaj novi filter" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Dodaj novi isečak" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Ključna reč" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Zamena" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Napravi novi spisak" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Ime:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Napravi" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Napiši direktnu poruku" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Dodaj ili ukloni sa spiska" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Blokiran(a)" #: ui/profile-page.ui:24 msgid "Muted" msgstr "Prigušen(a)" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Ponovni tvitovi onemogućeni" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Prati vas" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tvitovi" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Pratioci" #: ui/profile-page.ui:326 msgid "Following" msgstr "Prati" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "Prikaži ugnežđeni sadržaj" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "Uvek prikaži" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "Uvek skrivaj" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "Sakrij sa vremenske linije" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Automatski klizaj pri novim tvitovima" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Aktiviraj na dupli klik" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Interfejs" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Pri novim tvitovima" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Radnje" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Pri novim spominjanjima" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Pri novim porukama" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Nikada" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Svaki put" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "Sakupi 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "Sakupi 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "Sakup 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "Sakupi 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Obaveštenja" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Okrugli avatari" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Ukloni prateće tarabice" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Ukloni multimedijalne veze" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Sakrij neprikladni sadržaj" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Nema podešenih isečaka." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Uvek možete aktivirati isečke pisanjem ključne reči i pritiskom tastera TAB." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Isečci" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Opšte" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Sastavi tvit" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Prikaži podešavanja naloga" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Prikaži iskačući prozor naloga" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Prikaži podešavanja programa" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Uključi ili isključi gornju traku" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Idi nazad" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Idi napred" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Idi na određenu stranu" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Tvitovi" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Ponovo tvituj" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Označi kao omiljeno" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Odgovori" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "Citiraj" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Prikaži pojedinosti" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Obriši" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "Sastavi" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "Prikaži birača emodžija" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Započni novi razgovor" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "Sa:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Idi" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Citiraj" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Ponovo tvituj tvit" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Označi tvit omiljenim" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Odgovori na tvit" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Više" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Stavi kao omiljeno" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Odgovori" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Deblokiraj" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Prikaži podešavanja ovog naloga" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Otvori u novom prozoru" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "Idi na profil" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Napravi" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Pretplaćen(a) na" corebird-1.7.4/po/tr.po000066400000000000000000000507401324604713000147250ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # adem demir , 2015 # Ali GOREN , 2015 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: Turkish (Turkey) (http://www.transifex.com/corebird/corebird/language/tr_TR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: tr_TR\n" "Plural-Forms: nplurals=1; plural=0;\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Twitter İstemcisi" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird, Direkt Mesajlar (DMS), Bildirimler, Konuşma Görünümleri Gibi Hayati Özellikler Taşıyan GTK+ Kullanan Yerel Bir Twitter İstemcisidir." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Yerel Görüntüleme İçin Ek Özellikler Şunlardır, Videolar, Çoklu Resimler, Listeler, Çoklu Hesaplar ve Dahası." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Doğrudan Görüşme" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%2$s Kullanıcısından %1$d Yeni Mesaj" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "%s Kullanıcısından Yeni Mesaj Var" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Direkt Mesajlar" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Favoriler" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s Retweetlendi %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s Tweetlendi" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d Yeni Tweet!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Anasayfa" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Liste" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Listeler" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Hesap Yapılandırmalarını Göster" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Tweet Oluştur" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Bahsedilenler" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Korumalı Hesap" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "@%s Kullanıcısına Tweetle" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Ara" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Tweet Gösterilemiyor" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Yeniden Tweetlenenler" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Tarayıcıda Aç" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Kaynak" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Tweet Detayları" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d Okunmamış)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Sil" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "" #: src/util/Utils.vala:146 msgid "Now" msgstr "Şimdi" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%dm" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%dh" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Yeni Bir Tane Oluştur." #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "%s Açılamadı" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "Yanlış PİN." #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Bu Hesap Bir Başkası Tarafından Kullanılıyor." #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "İptal" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Takip Et" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Takibi Bırak" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Kaydet" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Herhangi Bir Tweet Bulunamadı." #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Tekrar" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Alıntı Tweet" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Geri" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Filtreleri Düzenle." #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Eşleşenler" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Merhaba, #Corebird Uygulamasının Son Sürümüne Bakmanı Öneririm! \\ (•◡•) / #harika" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Kullanıcı Listesinden Ekle ya da Kaldır" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "Herhangi Bir Listeniz Yok." #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Corebird Hakkında" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Yeni Hesap" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Yeni PIN İste" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Doğrula" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Hesap Ayarları" #: ui/account-dialog.ui:88 msgid "Name" msgstr "İsim" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Website" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Otomotik Başlat" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Gerçekten Bu Hesabı Silmek İstiyor Musunuz?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Gönder" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Kullanıcılar" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Abone Ol" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Abonelikten Çık" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Aboneler:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Üyeler:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Kurucular:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Oluşturulan:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Düzenleme" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Mod:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Özel" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Genel" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Ayarlar" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "" #: ui/menus.ui:15 msgid "About" msgstr "Hakkında" #: ui/menus.ui:19 msgid "Quit" msgstr "Çıkış" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Yeni Filtre Ekle" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Yeni Parçacık Ekle" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Anahtar Kelime" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Değiştirme" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Yeni Liste Oluştur" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "İsim:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Oluştur" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Direkt Mesaj Gönder" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Listeden Kaldır/Ekle" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Engellenmiş" #: ui/profile-page.ui:24 msgid "Muted" msgstr "" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Retweetler Kapalı" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Takip edenler" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Tweetler" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Takipçiler" #: ui/profile-page.ui:326 msgid "Following" msgstr "Takiptekiler" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Yeni Tweetlerde Otomatik Kaydır" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Etkinleştirmek İçin Çift Tıkla" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Arayüz" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "Yeni Tweetlerde" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Etkileşimler" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "Yeni Cevaplarda" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "Yeni Mesajlarda" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Asla" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Her Zaman" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "5 destek" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "10 destek" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "25 destek" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "50 destek" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Bildirimler" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Yuvarlak Avatarlar" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Tekrarlayan Hashtagleri Kaldır" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Medya Linklerini Kaldır" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Yapılandırılmış Parçalar Yok." #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Anahtar Kelimeleri Yazıp TAB Tuşuna Basabilir ve Parçacıkları Aktif Edebilirsiniz." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Parçacıklar" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Yeni Bir Sohbet Başlat" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "İle:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Git" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Alıntı" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Tweet'i Retweetle" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Tweet'i Favorile" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Tweeti Cevapla" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Daha Fazla" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Favori" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Cevap" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Engeli Kaldır" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Hesap Ayarlarını Göster" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Yeni Pencerede Aç" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Oluşturuldu" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Abone Olundu" corebird-1.7.4/po/uk_UA.po000066400000000000000000000554471324604713000153150ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # Illia Ratkevych , 2016 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: Ukrainian (Ukraine) (http://www.transifex.com/corebird/corebird/language/uk_UA/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: uk_UA\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Twitter-клієнт" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird - Twitter-клієнт заснований на GTK+, який реалізує такі необхідні функції як приватні повідомлення (ПП), сповіщення, перегляд розмов." #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "Додаткові функції включають у себе локальний перегляд відео, вбудовані зображення, списки, фільтри, робота з кількома акаунтами, тощо." #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "Прямі розмови" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%d нове повідомлення від %s" msgstr[1] "%d нових повідомлень від %s" msgstr[2] "%d нових повідомлень від %s" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "Нове приватне повідомлення від %s" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "Приватне повідомлення" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "Вподобання" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s ретвітнув %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s твітнув" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d новий твіт" msgstr[1] "%d нових твітів" msgstr[2] "%d нових твітів" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "Головна" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "Список" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "Списки" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "Показати акаунти" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "Написати новий твіт" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "Згадування" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "Захищенний профайл" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "Твіт до @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "Пошук" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "Не можу показати твіт" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "Ретвіти" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "Відкрити у браузері" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "Джерело" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "Деталі твіта" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d непрочитано)" msgstr[1] "(%d непрочитано)" msgstr[2] "(%d непрочитано)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "Видалити" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "" #: src/util/Utils.vala:146 msgid "Now" msgstr "Зараз" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%d хв" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%d г" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "Створити акаунт" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "Не можливо відкрити %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "Невірний PIN" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "Акаунт вже використовується" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "Скасувати" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "Читати" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "Не читати" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "Зберегти" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "Записів не знайдено" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "Спробувати ще" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "Цитувати твіт" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "Назад" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "Змінити фільтр" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "Співпадає" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "Вау, заціни нову версію #Corebird ! \\ (•◡•) / #cool #newisalwaysbetter" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "Додати/видалити користувача зі списку" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "У вас немає списків" #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "Про Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "Новий акаунт" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "Запит PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "Підтвердити" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "Налаштування акаунта" #: ui/account-dialog.ui:88 msgid "Name" msgstr "Ім'я" #: ui/account-dialog.ui:117 msgid "Website" msgstr "Вебсайт" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "Авто-старт" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "Ви дійсно бажаєте видалити цей акаунт?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "Відправити" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "Додати зображення" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "Користувачі" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "Підписатись" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "Відписатись" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "Підписники:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "Члени:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "Автор:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "Створено в:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "Редагувати" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "Режим:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "Приватний" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "Публічний" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "Налаштування" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "Скорочення" #: ui/menus.ui:15 msgid "About" msgstr "Про" #: ui/menus.ui:19 msgid "Quit" msgstr "Вийти" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "Додати новий фільтр" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "Додати новий сніпет" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "Ключові слова" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "Заміна" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "Створити новий список" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "Ім'я:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "Створити" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "Написати приватне повідомлення" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "Додати/видалити зі списку" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "Заблоковано" #: ui/profile-page.ui:24 msgid "Muted" msgstr "" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "Ретвіти вимкнуто" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "Читає вас" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "Твіти" #: ui/profile-page.ui:284 msgid "Followers" msgstr "Читачі" #: ui/profile-page.ui:326 msgid "Following" msgstr "Читає" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "Авто-скрол при появі нових твітів" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "Активація при подвійному кліку" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "Інтерфейс" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "При отриманні нових твітів" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "Дії" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "При нових отриманні нових згадок" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "При отриманні нових повідомлень" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "Ніколи" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "Кожний" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "По 5" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "По 10" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "По 25" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "По 50" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "Сповіщення" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "Круглі аватари" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "Прибрати хештеги наприкінці" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "Прибрати посилання на медіа" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "Сховати недопустимий контент" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "Не сконфігуровано жодного сніпета" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "Ви можете активувати сніпет написавши ключове слово і натиснувши TAB." #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "Сніпети" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "Загальне" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "Створити новий твіт" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "Показати налаштування акаунта" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "Показати налаштування поповера" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "Показати налаштування програми" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "Перемкнути топбар" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "Назад" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "Вперед" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "Перейти на сторінку" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "Твіти" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "Цитувати" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "Вподобане" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "Відповісти" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "Показати деталі" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "Видалити" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "Почати нову розмову" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "З:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "Go" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "Цитувати" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "Цитувати твіт" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "Вподобати твіт" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "Відповісти на твіт" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "Більше" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "Вподобане" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "Відповісти" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "Розблокувати" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "Показати налаштування акаунта" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "Відкрити у новому вікні" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "Створено" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "Підписано на" corebird-1.7.4/po/zh_CN.po000066400000000000000000000515421324604713000153020ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # colindemian , 2015-2016-2016 # Dianjin Wang <1132321739qq@gmail.com>, 2014 # hmingming1222 , 2015 # S Shen, 2016 # Tong Hui , 2014 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2017-10-22 10:08+0000\n" "Last-Translator: baedert \n" "Language-Team: Chinese (China) (http://www.transifex.com/corebird/corebird/language/zh_CN/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: zh_CN\n" "Plural-Forms: nplurals=1; plural=0;\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Twitter 客户端" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird 是一款原生的 GTK+ Twitter 客户端,能提供多种基础功能如直接消息(私信)、提醒、会话视图等。" #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "高级功能包括本地观看视频、插入多个图片、列表、过滤、多帐号等。" #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "使用 Corebird 时启用常规时间线视图" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "常规 Twitter 资料" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "账户设置可自定义" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "桌面应用般的 Twitter 使用体验" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "直接对话" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "有来自 %2$s 的 %1$d 条新消息" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "有来自 %s 的新私信" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "私信" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "无法加载推文" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "收藏" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "添加新过滤器" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "过滤器" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s 转推了 %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s 已发推" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d 条新推!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "主页" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "列表" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "列表" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "显示已配置的账户" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "撰写推文" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "添加新账户" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s 提及了 %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "提及我的" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "资料已受保护" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "给 @%s 发推" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "搜索" #: src/SearchPage.vala:401 msgid "Load More" msgstr "" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "无法显示推文" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "转推" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "浏览器中打开" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "来源" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "详情" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d 条未读)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "删除" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "屏蔽 %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "此推文包含被标记为“不适宜”的图片" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "仍然显示" #: src/util/Utils.vala:146 msgid "Now" msgstr "现在" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%d分钟" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%d小时" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "注册一个" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "无法打开 %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "PIN 码错误" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "账户已在使用中" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "未找到用户" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "选择图片" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "打开" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "取消" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "关注" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "取消关注" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "复制 URL" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "" #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "保存" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "" #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "未找到条目" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "重试" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "引用推文" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "返回" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "所选图像过大,每张图象大小上限为 %'d MB" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "" #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "修改过滤器" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "匹配" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "修改插件" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "插件不可包含空字符" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "插件已存在" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "新版的 #Corebird 发布啦!\\ (•◡•) / #cool #newisalwaysbetter" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "添加或删除列表中的用户" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "您还没有任何列表。" #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "关于 Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "新账户" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "需要输入 PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "确认" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "账户设置" #: ui/account-dialog.ui:88 msgid "Name" msgstr "姓名" #: ui/account-dialog.ui:117 msgid "Website" msgstr "网站" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "自动登入" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "您确定要删除此账户吗?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "发送" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "添加图像" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "用户" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "订阅" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "取消订阅" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "订阅者:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "成员:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "创建人:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "创建时间:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "编辑" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "模式:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "私人" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "公开" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "描述" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "设置" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "快捷键" #: ui/menus.ui:15 msgid "About" msgstr "关于" #: ui/menus.ui:19 msgid "Quit" msgstr "退出" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "添加新过滤器" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "添加新插件" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "关键字" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "替换内容" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "创建新列表" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "名称:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "创建" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "发消息" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "添加至列表/从列表删除" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "已屏蔽" #: ui/profile-page.ui:24 msgid "Muted" msgstr "已静音" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "转推已禁用" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "关注您" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "推文" #: ui/profile-page.ui:284 msgid "Followers" msgstr "关注者" #: ui/profile-page.ui:326 msgid "Following" msgstr "已关注" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "显示内联媒体" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "总是显示" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "总是隐藏" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "从时间线上隐藏" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "自动载入新推文" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "双击激活" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "外观" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "新推文" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "行为" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "新提及" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "新消息" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "从不" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "间隔" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "5 条" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "10 条" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "25 条" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "50 条" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "通知" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "圆形头像" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "移除尾部标签" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "移除媒体链接" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "隐藏不适宜内容" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "未配置任何插件" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "您可以通过键入关键词和按下 TAB 键来激活插件。" #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "插件" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "一般选项" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "撰写推文" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "显示账户设置" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "显示账户弹出框" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "显示应用设置" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "顶栏开关" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "返回" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "前进" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "跳转页面" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "推文" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "转推" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "收藏" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "回复" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "引用" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "显示详情" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "删除" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "开始新对话" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "和:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "前往" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "引用" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "转推" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "收藏推文" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "回复推文" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "更多" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "收藏" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "回复" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "取消屏蔽" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "显示此账户设置" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "新窗口打开" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "查看资料" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "已创建" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "订阅到" corebird-1.7.4/po/zh_TW.po000066400000000000000000000531361324604713000153350ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR Timm Bäder # This file is distributed under the same license as the corebird package. # # Translators: # HybridGlucose , 2016-2017-2017 msgid "" msgstr "" "Project-Id-Version: Corebird\n" "Report-Msgid-Bugs-To: https://github.com/baedert/corebird/issues/new\n" "POT-Creation-Date: 2017-10-22 12:02+0200\n" "PO-Revision-Date: 2018-01-17 19:31+0000\n" "Last-Translator: HybridGlucose \n" "Language-Team: Chinese (Taiwan) (http://www.transifex.com/corebird/corebird/language/zh_TW/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: zh_TW\n" "Plural-Forms: nplurals=1; plural=0;\n" #: data/org.baedert.corebird.appdata.xml.in:5 #: data/org.baedert.corebird.desktop.in:3 msgid "Corebird" msgstr "Corebird" #: data/org.baedert.corebird.appdata.xml.in:6 #: data/org.baedert.corebird.desktop.in:4 msgid "Twitter Client" msgstr "Twitter用戶端" #: data/org.baedert.corebird.appdata.xml.in:10 msgid "" "Corebird is a native GTK+ twitter client that provides vital features such " "as Direct Messages (DMs), tweet notifications, conversation views." msgstr "Corebird 是原生的 GTK+ Twitter 用戶端,具有訊息、推文通知、以對話形式檢視回覆等等功能。" #: data/org.baedert.corebird.appdata.xml.in:13 msgid "" "Additional features include local viewing of videos, multiple inline images," " Lists, Filters, multiple accounts, etc." msgstr "其他功能包括直接觀賞影片、顯示多張影像、列表、過濾器及多重帳戶登入等等。" #: data/org.baedert.corebird.appdata.xml.in:21 msgid "Generic timeline view when using Corebird" msgstr "使用 Corebird 以時間軸方式瀏覽" #: data/org.baedert.corebird.appdata.xml.in:25 msgid "Typical Twitter profile" msgstr "典型的 Twitter 個人檔案" #: data/org.baedert.corebird.appdata.xml.in:29 msgid "Account settings can be configured" msgstr "可以設定帳戶資料" #: data/org.baedert.corebird.appdata.xml.in:41 msgid "Timm Bäder" msgstr "" #: data/org.baedert.corebird.desktop.in:5 msgid "Use Twitter from within a normal desktop application" msgstr "在普通的桌面應用程式內使用 Twitter" #. TRANSLATORS: Search terms to find this application. Do NOT translate or #. localize the semicolons! The list MUST also end with a semicolon! #: data/org.baedert.corebird.desktop.in:7 msgid "twitter;" msgstr "twitter;" #. TRANSLATORS: Do NOT translate or transliterate this text (this is an icon #. file name)! #: data/org.baedert.corebird.desktop.in:11 msgid "corebird" msgstr "corebird" #. TRANSLATORS: This is the start of a "Replying to" line in a tweet #: src/CbUtils.c:177 src/TweetInfoPage.vala:525 msgid "Replying to" msgstr "回覆給" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet. Example: "Replying to Foo and Bar" where #. * "and Bar" comes from this string. #: src/CbUtils.c:188 src/TweetInfoPage.vala:537 msgid "and" msgstr "和" #. TRANSLATORS: This gets appended to the "replying to" line #. * in a tweet #: src/CbUtils.c:197 #, c-format msgid "and %d others" msgstr "和其他 %d 人" #: src/DMPage.vala:344 msgid "Direct Conversation" msgstr "對話" #: src/DMThreadsPage.vala:228 #, c-format msgid "%d new Message from %s" msgid_plural "%d new Messages from %s" msgstr[0] "%2$s 傳送了 %1$d 則新訊息" #: src/DMThreadsPage.vala:234 src/UserEventReceiver.vala:98 #, c-format msgid "New direct message from %s" msgstr "%s 傳送了新訊息" #: src/DMThreadsPage.vala:244 src/DMThreadsPage.vala:258 msgid "Direct Messages" msgstr "訊息" #: src/DefaultTimeline.vala:366 msgid "Could not load tweets" msgstr "無法載入推文" #: src/FavoritesTimeline.vala:72 src/FavoritesTimeline.vala:76 #: src/TweetInfoPage.vala:567 msgid "Favorites" msgstr "喜歡" #: src/FilterPage.vala:47 msgid "Add new Filter" msgstr "新增過濾器" #: src/FilterPage.vala:317 src/FilterPage.vala:323 msgid "Filters" msgstr "過濾器" #: src/HomeTimeline.vala:143 #, c-format msgid "%s retweeted %s" msgstr "%s 轉推了 %s" #: src/HomeTimeline.vala:146 #, c-format msgid "%s tweeted" msgstr "%s 推文了" #: src/HomeTimeline.vala:155 #, c-format msgid "%d new Tweet!" msgid_plural "%d new Tweets!" msgstr[0] "%d 則新推文!" #: src/HomeTimeline.vala:190 msgid "Home" msgstr "主頁" #: src/ListStatusesPage.vala:378 msgid "List" msgstr "列表" #: src/ListsPage.vala:126 src/ListsPage.vala:131 ui/profile-page.ui:368 msgid "Lists" msgstr "列表" #: src/MainWindow.vala:88 msgid "Show configured accounts" msgstr "顯示帳戶" #: src/MainWindow.vala:100 ui/compose-window.ui:11 ui/compose-window.ui:25 msgid "Compose Tweet" msgstr "撰寫新推文" #: src/MainWindow.vala:132 msgid "Add new Account" msgstr "加入新帳戶" #: src/MentionsTimeline.vala:93 src/UserEventReceiver.vala:115 #, c-format msgid "%s mentioned %s" msgstr "%s 提及 %s" #: src/MentionsTimeline.vala:111 src/MentionsTimeline.vala:115 msgid "Mentions" msgstr "提及" #: src/ProfilePage.vala:218 msgid "Suspended Account" msgstr "被暫時停用的帳戶" #: src/ProfilePage.vala:272 msgid "Protected profile" msgstr "受保護的個人檔案" #: src/ProfilePage.vala:369 #, c-format msgid "Tweet to @%s" msgstr "推文給 @%s" #: src/ProfilePage.vala:507 src/ProfilePage.vala:551 msgid "Protected Profile" msgstr "受保護的個人檔案" #: src/SearchPage.vala:371 src/SearchPage.vala:380 ui/search-page.ui:24 msgid "Search" msgstr "搜尋" #: src/SearchPage.vala:401 msgid "Load More" msgstr "載入更多" #: src/TweetInfoPage.vala:345 msgid "Could not show tweet" msgstr "無法顯示推文" #: src/TweetInfoPage.vala:566 msgid "Retweets" msgstr "個轉推" #: src/TweetInfoPage.vala:575 src/widgets/MediaButton.vala:103 msgid "Open in Browser" msgstr "在瀏覽器中開啟" #: src/TweetInfoPage.vala:575 msgid "Source" msgstr "來源" #: src/TweetInfoPage.vala:621 msgid "Tweet Details" msgstr "推文詳細" #: src/list/DMThreadEntry.vala:71 #, c-format msgid "(%d unread)" msgid_plural "(%d unread)" msgstr[0] "(%d 未讀)" #: src/list/FavImageRow.vala:81 ui/account-dialog.ui:212 #: ui/account-dialog.ui:269 ui/filter-list-entry.ui:93 #: ui/list-list-entry.ui:132 ui/list-statuses-page.ui:166 #: ui/modify-snippet-dialog.ui:108 ui/tweet-info-page.ui:10 #: ui/tweet-list-entry.ui:10 msgid "Delete" msgstr "刪除" #: src/list/TweetListEntry.vala:455 #, c-format msgid "Block %s" msgstr "封鎖 %s" #: src/list/TweetListEntry.vala:611 msgid "This tweet contains images marked as inappropriate" msgstr "這則推文被標記為敏感內容" #: src/list/TweetListEntry.vala:618 msgid "Show anyway" msgstr "顯示它" #: src/util/Utils.vala:146 msgid "Now" msgstr "現在" #: src/util/Utils.vala:148 #, c-format msgid "%dm" msgstr "%d分鐘" #: src/util/Utils.vala:152 #, c-format msgid "%dh" msgstr "%d小時" #: src/widgets/AccountCreateWidget.vala:42 msgid "Don’t have a Twitter account yet?" msgstr "還沒有 Twitter 帳號嗎?" #: src/widgets/AccountCreateWidget.vala:42 msgid "Create one" msgstr "註冊一個" #: src/widgets/AccountCreateWidget.vala:55 msgid "" "Unauthorized. Most of the time, this means that there’s something wrong with" " the Twitter servers and you should try again later" msgstr "授權失敗。通常這是因為Twitter伺服器出錯,請稍後再試。" #: src/widgets/AccountCreateWidget.vala:69 #, c-format msgid "Could not open %s" msgstr "無法開啟 %s" #: src/widgets/AccountCreateWidget.vala:98 msgid "Wrong PIN" msgstr "PIN 錯誤" #: src/widgets/AccountCreateWidget.vala:124 msgid "Account already in use" msgstr "此帳戶已被加入過" #: src/widgets/CompletionTextView.vala:59 msgid "No users found" msgstr "沒有找到用戶" #: src/widgets/FavImageView.vala:153 src/window/ComposeTweetWindow.vala:299 msgid "Select Image" msgstr "選擇媒體" #: src/widgets/FavImageView.vala:156 src/window/AccountDialog.vala:347 #: src/window/ComposeTweetWindow.vala:302 msgid "Open" msgstr "開啟" #: src/widgets/FavImageView.vala:157 src/widgets/MediaButton.vala:276 #: src/window/AccountDialog.vala:348 src/window/ComposeTweetWindow.vala:275 #: src/window/ComposeTweetWindow.vala:303 src/window/UserListDialog.vala:47 #: ui/account-dialog.ui:25 ui/account-dialog.ui:256 ui/compose-window.ui:41 #: ui/filter-list-entry.ui:80 ui/list-list-entry.ui:98 #: ui/list-statuses-page.ui:173 ui/modify-filter-dialog.ui:13 #: ui/modify-snippet-dialog.ui:14 ui/shortcuts-window.ui:134 #: ui/user-filter-entry.ui:114 msgid "Cancel" msgstr "取消" #: src/widgets/FollowButton.vala:43 msgid "Follow" msgstr "跟隨" #: src/widgets/FollowButton.vala:44 msgid "Unfollow" msgstr "取消跟隨" #: src/widgets/MediaButton.vala:48 msgid "Copy URL" msgstr "複製網址" #: src/widgets/MediaButton.vala:105 msgid "Save as…" msgstr "儲存為..." #: src/widgets/MediaButton.vala:268 msgid "Save Video" msgstr "儲存影片" #: src/widgets/MediaButton.vala:270 msgid "Save Image" msgstr "儲存圖片" #: src/widgets/MediaButton.vala:275 src/window/AccountDialog.vala:429 #: src/window/AccountDialog.vala:450 src/window/UserListDialog.vala:48 #: ui/account-dialog.ui:33 ui/list-statuses-page.ui:129 #: ui/modify-filter-dialog.ui:20 ui/modify-snippet-dialog.ui:21 msgid "Save" msgstr "儲存" #: src/widgets/TweetListBox.vala:123 msgid "Loading…" msgstr "載入中..." #: src/widgets/TweetListBox.vala:126 src/widgets/TweetListBox.vala:177 msgid "No entries found" msgstr "沒有找到條目" #: src/widgets/TweetListBox.vala:137 ui/account-create-widget.ui:119 msgid "Retry" msgstr "重試" #: src/window/AccountDialog.vala:344 msgid "Select Banner Image" msgstr "選擇首頁相片" #: src/window/AccountDialog.vala:380 msgid "Image does not meet minimum size requirements:" msgstr "影像沒有達到最低大小:" #: src/window/AccountDialog.vala:381 #, c-format msgid "Minimum width: %d pixel" msgid_plural "Minimum width: %d pixels" msgstr[0] "最小寬度: %d 像素" #: src/window/AccountDialog.vala:383 #, c-format msgid "Minimum height: %d pixel" msgid_plural "Minimum height: %d pixels" msgstr[0] "最小高度: %d 像素" #: src/window/AccountDialog.vala:404 src/window/AccountDialog.vala:417 msgid "Pick" msgstr "" #: src/window/ComposeTweetWindow.vala:138 msgid "Quote tweet" msgstr "引用推文" #: src/window/ComposeTweetWindow.vala:331 msgid "Selected file is not an image." msgstr "所選擇的檔案並非影像或圖片。" #: src/window/ComposeTweetWindow.vala:332 #: src/window/ComposeTweetWindow.vala:338 #: src/window/ComposeTweetWindow.vala:344 #: src/window/ComposeTweetWindow.vala:365 #: src/window/ComposeTweetWindow.vala:428 msgid "Back" msgstr "返回" #: src/window/ComposeTweetWindow.vala:336 #, c-format msgid "" "The selected image is too big. The maximum file size per image is %'d MB" msgstr "所選擇的媒體檔案太大。大小上限為 %'d MB。" #: src/window/ComposeTweetWindow.vala:343 msgid "Only one GIF file per tweet is allowed." msgstr "每一則推文只允許一個GIF檔案." #: src/window/ComposeTweetWindow.vala:392 msgid "Insert Emoji" msgstr "插入顏文字" #: src/window/ModifyFilterDialog.vala:45 msgid "Modify Filter" msgstr "修改過濾器" #: src/window/ModifyFilterDialog.vala:76 msgid "Matches" msgstr "相符" #: src/window/ModifyFilterDialog.vala:78 msgid "Doesn’t match" msgstr "不符合" #: src/window/ModifySnippetDialog.vala:44 msgid "Modify Snippet" msgstr "編輯片語" #: src/window/ModifySnippetDialog.vala:63 msgid "Snippet can’t be empty" msgstr "片語關鍵字不可空白" #: src/window/ModifySnippetDialog.vala:70 msgid "Replacement can’t be empty" msgstr "替換內容不可空白" #: src/window/ModifySnippetDialog.vala:78 msgid "Snippet may not contain whitespace" msgstr "片語關鍵字不可包含空白" #: src/window/ModifySnippetDialog.vala:86 msgid "Snippet already exists" msgstr "片語關鍵字已存在" #: src/window/SettingsDialog.vala:85 msgid "" "Hey, check out this new #Corebird version! \\ (•◡•) / #cool " "#newisalwaysbetter" msgstr "嘿,#Corebird 推出了新版本! \\ (•◡•) / #cool #newisalwaysbetter" #: src/window/UserListDialog.vala:40 msgid "Add to or Remove User From List" msgstr "從列表中加入或移除成員" #: src/window/UserListDialog.vala:69 msgid "You have no lists." msgstr "你沒有任何列表" #: ui/about-dialog.ui:5 msgid "About Corebird" msgstr "關於Corebird" #: ui/account-create-widget.ui:28 msgid "New Account" msgstr "新帳戶" #: ui/account-create-widget.ui:47 msgid "" "To authenticate Corebird, you need a PIN from twitter.com with the account " "you wish to add" msgstr "為了授權 Corebird 的使用,您必須向 twitter.com 取的欲新增帳戶之 PIN 。" #: ui/account-create-widget.ui:58 msgid "Request PIN" msgstr "取得 PIN" #: ui/account-create-widget.ui:88 msgid "Enter PIN from twitter.com below:" msgstr "在下方輸入 twitter.com 所提供的 PIN :" #: ui/account-create-widget.ui:106 msgid "PIN" msgstr "PIN" #: ui/account-create-widget.ui:132 ui/list-statuses-page.ui:358 msgid "Confirm" msgstr "確認" #: ui/account-dialog.ui:19 msgid "Account Settings" msgstr "帳戶設定" #: ui/account-dialog.ui:88 msgid "Name" msgstr "名稱" #: ui/account-dialog.ui:117 msgid "Website" msgstr "網站" #: ui/account-dialog.ui:181 msgid "Autostart" msgstr "自動啟動" #: ui/account-dialog.ui:244 msgid "Do you really want to delete this account?" msgstr "你確定要移除這個帳戶?" #: ui/cb-emoji-chooser.ui:41 msgctxt "emoji category" msgid "Smileys & People" msgstr "表情&人物" #: ui/cb-emoji-chooser.ui:54 msgctxt "emoji category" msgid "Body & Clothing" msgstr "身體&衣服" #: ui/cb-emoji-chooser.ui:67 msgctxt "emoji category" msgid "Animals & Nature" msgstr "動物&自然" #: ui/cb-emoji-chooser.ui:80 msgctxt "emoji category" msgid "Food & Drink" msgstr "食物&飲品" #: ui/cb-emoji-chooser.ui:93 msgctxt "emoji category" msgid "Travel & Places" msgstr "旅行&地點" #: ui/cb-emoji-chooser.ui:106 msgctxt "emoji category" msgid "Activities" msgstr "活動" #: ui/cb-emoji-chooser.ui:119 msgctxt "emoji category" msgid "Objects" msgstr "物件" #: ui/cb-emoji-chooser.ui:132 msgctxt "emoji category" msgid "Symbols" msgstr "象徵物" #: ui/cb-emoji-chooser.ui:145 msgctxt "emoji category" msgid "Flags" msgstr "旗幟" #: ui/cb-emoji-chooser.ui:272 msgid "No Results Found" msgstr "找不到相符的" #: ui/cb-emoji-chooser.ui:285 msgid "Try a different search" msgstr "試試其他的吧" #: ui/compose-window.ui:48 ui/dm-page.ui:51 ui/shortcuts-window.ui:127 msgid "Send" msgstr "推文" #: ui/compose-window.ui:177 msgid "Add Image" msgstr "新增媒體" #: ui/compose-window.ui:185 msgid "Show favorite images" msgstr "顯示加入常用的媒體" #: ui/filter-page.ui:71 ui/search-page.ui:86 msgid "Users" msgstr "帳戶" #: ui/list-list-entry.ui:109 msgid "Subscribe" msgstr "訂閱" #: ui/list-list-entry.ui:122 msgid "Unsubscribe" msgstr "取消訂閱" #: ui/list-statuses-page.ui:26 msgid "Subscribers:" msgstr "訂閱者:" #: ui/list-statuses-page.ui:49 msgid "Members:" msgstr "成員:" #: ui/list-statuses-page.ui:72 msgid "Creator:" msgstr "建立者:" #: ui/list-statuses-page.ui:84 msgid "Created at:" msgstr "建立時間:" #: ui/list-statuses-page.ui:119 msgid "Edit" msgstr "編輯" #: ui/list-statuses-page.ui:194 msgid "Mode:" msgstr "隱私:" #: ui/list-statuses-page.ui:217 msgid "Private" msgstr "私人" #: ui/list-statuses-page.ui:218 msgid "Public" msgstr "公開" #: ui/list-statuses-page.ui:296 msgid "Description" msgstr "描述" #: ui/menus.ui:6 ui/settings-dialog.ui:16 ui/settings-dialog.ui:22 msgid "Settings" msgstr "設定" #: ui/menus.ui:10 msgid "Shortcuts" msgstr "快捷鍵" #: ui/menus.ui:15 msgid "About" msgstr "關於" #: ui/menus.ui:19 msgid "Quit" msgstr "離開" #: ui/modify-filter-dialog.ui:6 msgid "Add New Filter" msgstr "新增過濾器" #: ui/modify-snippet-dialog.ui:6 msgid "Add New Snippet" msgstr "新增片語" #: ui/modify-snippet-dialog.ui:46 msgid "Keyword" msgstr "關鍵字" #: ui/modify-snippet-dialog.ui:72 msgid "Replacement" msgstr "替換成" #: ui/new-list-entry.ui:30 msgid "Create New List" msgstr "建立新列表" #: ui/new-list-entry.ui:51 msgid "Name:" msgstr "名稱:" #: ui/new-list-entry.ui:73 msgid "Create" msgstr "建立" #: ui/profile-page.ui:6 msgid "Write Direct Message" msgstr "寫新訊息" #: ui/profile-page.ui:14 msgid "Add to/Remove from List" msgstr "新增到列表中或從列表中移除" #: ui/profile-page.ui:20 msgid "Blocked" msgstr "封鎖" #: ui/profile-page.ui:24 msgid "Muted" msgstr "靜音" #: ui/profile-page.ui:28 msgid "Retweets disabled" msgstr "禁止轉推" #: ui/profile-page.ui:166 msgid "Follows you" msgstr "跟隨你" #: ui/profile-page.ui:242 ui/search-page.ui:74 ui/settings-dialog.ui:420 msgid "Tweets" msgstr "推文" #: ui/profile-page.ui:284 msgid "Followers" msgstr "跟隨者" #: ui/profile-page.ui:326 msgid "Following" msgstr "正在關注" #: ui/settings-dialog.ui:59 msgid "Show inline media" msgstr "顯示推文中媒體" #: ui/settings-dialog.ui:78 msgid "Always show" msgstr "永遠顯示" #: ui/settings-dialog.ui:79 msgid "Always hide" msgstr "永遠隱藏" #: ui/settings-dialog.ui:80 msgid "Hide in timeline" msgstr "在時間軸上隱藏" #: ui/settings-dialog.ui:101 msgid "Auto scroll on new tweets" msgstr "有新推文時自動滾動" #: ui/settings-dialog.ui:131 msgid "Double-click activation" msgstr "雙擊操作" #: ui/settings-dialog.ui:161 msgid "Interface" msgstr "界面" #: ui/settings-dialog.ui:179 msgid "On New Tweets" msgstr "有新推文" #: ui/settings-dialog.ui:193 msgid "Actions" msgstr "動作" #: ui/settings-dialog.ui:208 msgid "On New Mentions" msgstr "有新提及" #: ui/settings-dialog.ui:222 msgid "On New Messages" msgstr "有新訊息" #: ui/settings-dialog.ui:266 msgid "Never" msgstr "從不" #: ui/settings-dialog.ui:267 msgid "Every" msgstr "每個" #: ui/settings-dialog.ui:268 msgid "Stack 5" msgstr "每5個" #: ui/settings-dialog.ui:269 msgid "Stack 10" msgstr "每10個" #: ui/settings-dialog.ui:270 msgid "Stack 25" msgstr "每25個" #: ui/settings-dialog.ui:271 msgid "Stack 50" msgstr "每50個" #: ui/settings-dialog.ui:284 msgid "Notifications" msgstr "通知" #: ui/settings-dialog.ui:311 msgid "Round avatars" msgstr "圓形的大頭貼" #: ui/settings-dialog.ui:338 msgid "Remove trailing hashtags" msgstr "移除貼文標籤" #: ui/settings-dialog.ui:366 msgid "Remove media links" msgstr "移除媒體連結" #: ui/settings-dialog.ui:394 msgid "Hide inappropriate content" msgstr "隱藏標示為敏感內容之推文" #: ui/settings-dialog.ui:441 msgid "No snippets configured." msgstr "沒有設定片語" #: ui/settings-dialog.ui:480 msgid "You can activate snippets by writing the keyword and pressing TAB." msgstr "你可以在撰寫時輸入關鍵字後按TAB鍵轉換成片語。" #: ui/settings-dialog.ui:494 msgid "Snippets" msgstr "片語" #: ui/shortcuts-window.ui:12 msgctxt "shortcuts window" msgid "General" msgstr "一般" #: ui/shortcuts-window.ui:17 msgctxt "shortcuts window" msgid "Compose Tweet" msgstr "撰寫新推文" #: ui/shortcuts-window.ui:24 msgctxt "shortcuts window" msgid "Show Account settings" msgstr "顯示帳戶設定" #: ui/shortcuts-window.ui:31 msgctxt "shortcuts window" msgid "Show Accounts Popover" msgstr "顯示帳戶列表" #: ui/shortcuts-window.ui:38 msgctxt "shortcuts window" msgid "Show Application Settings" msgstr "顯示程式設定" #: ui/shortcuts-window.ui:45 msgctxt "shortcuts window" msgid "Toggle Topbar" msgstr "顯示/隱藏頂端分頁" #: ui/shortcuts-window.ui:52 msgctxt "shortcuts window" msgid "Go Back" msgstr "上一頁" #: ui/shortcuts-window.ui:59 msgctxt "shortcuts window" msgid "Go Forward" msgstr "下一頁" #: ui/shortcuts-window.ui:66 msgctxt "shortcuts window" msgid "Go to nth page" msgstr "切換第...分頁" #: ui/shortcuts-window.ui:74 msgctxt "shortcuts window" msgid "Tweets" msgstr "推文" #: ui/shortcuts-window.ui:79 msgctxt "shortcuts window" msgid "Retweet" msgstr "轉推" #: ui/shortcuts-window.ui:86 msgctxt "shortcuts window" msgid "Favorite" msgstr "喜歡" #: ui/shortcuts-window.ui:93 msgctxt "shortcuts window" msgid "Reply" msgstr "回覆" #: ui/shortcuts-window.ui:100 msgctxt "shortcuts window" msgid "Quote" msgstr "引用" #: ui/shortcuts-window.ui:107 msgctxt "shortcuts window" msgid "Show Details" msgstr "顯示詳細資料" #: ui/shortcuts-window.ui:114 msgctxt "shortcuts window" msgid "Delete" msgstr "刪除" #: ui/shortcuts-window.ui:122 msgctxt "shortcuts window" msgid "Compose" msgstr "撰寫新推文" #: ui/shortcuts-window.ui:141 msgid "Show Emoji Chooser" msgstr "顯示顏文字清單" #: ui/start-conversation-entry.ui:29 msgid "Start new conversation" msgstr "開始新對話" #: ui/start-conversation-entry.ui:49 msgid "With:" msgstr "跟:" #: ui/start-conversation-entry.ui:75 msgid "Go" msgstr "開始" #: ui/tweet-info-page.ui:6 ui/tweet-list-entry.ui:6 msgid "Quote" msgstr "引用" #: ui/tweet-info-page.ui:252 msgid "Retweet tweet" msgstr "轉推" #: ui/tweet-info-page.ui:277 msgid "Favorite tweet" msgstr "個喜歡" #: ui/tweet-info-page.ui:302 msgid "Reply to tweet" msgstr "回覆" #: ui/tweet-info-page.ui:338 msgid "More" msgstr "更多" #: ui/tweet-list-entry.ui:60 msgid "Favorite" msgstr "喜歡" #: ui/tweet-list-entry.ui:81 msgid "Reply" msgstr "回覆" #: ui/user-filter-entry.ui:125 msgid "Unblock" msgstr "解除封鎖" #: ui/user-list-entry.ui:67 msgid "Show settings of this account" msgstr "顯示這個帳號的設定" #: ui/user-list-entry.ui:92 msgid "Open in new window" msgstr "在新視窗中開啟" #: ui/user-list-entry.ui:116 msgid "Go to profile" msgstr "前往個人檔案" #: ui/user-lists-widget.ui:14 msgid "Created" msgstr "建立" #: ui/user-lists-widget.ui:86 msgid "Subscribed to" msgstr "訂閱的" corebird-1.7.4/sql/000077500000000000000000000000001324604713000141135ustar00rootroot00000000000000corebird-1.7.4/sql/accounts/000077500000000000000000000000001324604713000157325ustar00rootroot00000000000000corebird-1.7.4/sql/accounts/Create.1.sql000066400000000000000000000020321324604713000200120ustar00rootroot00000000000000PRAGMA user_version = 1; -- SQL schema for an account database CREATE TABLE IF NOT EXISTS `common`( token VARCHAR(100), token_secret VARCHAR(100) ); CREATE TABLE IF NOT EXISTS `info`( id NUMERIC(19,0) PRIMARY KEY, screen_name VARCHAR(30), name VARCHAR(30) ); CREATE TABLE IF NOT EXISTS `dm_threads`( user_id NUMERIC(19,0) PRIMARY KEY, name VARCHAR(40), screen_name VARCHAR(30), last_message VARCHAR(250), last_message_id NUMERIC(19,0), avatar_url VARCHAR (250) ); CREATE TABLE IF NOT EXISTS `dms` ( from_id NUMERIC(19,0), to_id NUMERIC(19,0), from_screen_name VARCHAR(30), to_screen_name VARCHAR(40), from_name VARCHAR(30), to_name VARCHAR(30), timestamp INTEGER(11), avatar_url VARCHAR(250), id NUMERIC (19,0) PRIMARY KEY, text TEXT ); CREATE TABLE IF NOT EXISTS `user_cache`( id NUMERIC(19,0) PRIMARY KEY, screen_name VARCHAR(30), user_name VARCHAR (40), score INTEGER (11) ); INSERT INTO `user_cache` (id, screen_name, name, score) VALUES ('2877682863', 'corebirdclient', 'Corebird', 10); corebird-1.7.4/sql/accounts/Create.2.sql000066400000000000000000000002021324604713000200100ustar00rootroot00000000000000PRAGMA user_version = 2; CREATE TABLE IF NOT EXISTS `filters`( id INTEGER PRIMARY KEY AUTOINCREMENT, content VARCHAR(100) ); corebird-1.7.4/sql/accounts/Create.3.sql000066400000000000000000000001111324604713000200100ustar00rootroot00000000000000PRAGMA user_version = 3; ALTER TABLE `info` ADD last_tweet VARCHAR(255) corebird-1.7.4/sql/init/000077500000000000000000000000001324604713000150565ustar00rootroot00000000000000corebird-1.7.4/sql/init/Create.1.sql000066400000000000000000000002561324604713000171440ustar00rootroot00000000000000 PRAGMA user_version = 1; CREATE TABLE IF NOT EXISTS `accounts`( id NUMERIC(19,0) PRIMARY KEY, screen_name VARCHAR(30), name VARCHAR(30), avatar_url VARCHAR(255) ); corebird-1.7.4/sql/init/Create.2.sql000066400000000000000000000005361324604713000171460ustar00rootroot00000000000000 PRAGMA user_version = 2; CREATE TABLE IF NOT EXISTS `snippets`( id INTEGER PRIMARY KEY AUTOINCREMENT, key VARCHAR(20), value VARCHAR(200) ); INSERT INTO `snippets` (key, value) VALUES ('dealwithit', '(•_•) ( •_•)>⌐■-■ (⌐■_■)'); INSERT INTO `snippets` (key, value) VALUES ('tableflip', '(╯°□°)╯︵ ┻━┻'); corebird-1.7.4/src/000077500000000000000000000000001324604713000141035ustar00rootroot00000000000000corebird-1.7.4/src/Account.vala000066400000000000000000000501251324604713000163470ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public class Account : GLib.Object { public const string DUMMY = "screen_name"; public int64 id; public Sql.Database db; public string screen_name; public string name; public string avatar_url; public string? banner_url; public string? website; public string? description; public Cairo.Surface avatar_small {public get; public set;} public Cairo.Surface avatar {public get; public set;} public Rest.OAuthProxy proxy; public Cb.UserStream user_stream; public Cb.UserCounter user_counter; private UserEventReceiver event_receiver; public NotificationManager notifications; public int64[] friends; public int64[] blocked; public int64[] muted; public int64[] disabled_rts; public GLib.GenericArray filters; public signal void info_changed (string screen_name, string name, Cairo.Surface avatar_small, Cairo.Surface avatar); public Account (int64 id, string screen_name, string name) { this.id = id; this.screen_name = screen_name; this.name = name; this.filters = new GLib.GenericArray (); this.event_receiver = new UserEventReceiver (this); this.notifications = new NotificationManager (this); } /** * Initializes the database. All account databases are VersionedDatabases * and are stored in accounts/ID.db. * */ public void init_database () { if (db != null) return; this.db = new Sql.Database (Dirs.config (@"accounts/$id.db"), Sql.ACCOUNTS_INIT_FILE, Sql.ACCOUNTS_SQL_VERSION); user_counter = new Cb.UserCounter (); this.load_filters (); } /** * Initializes the RestProxy object. * * @param load_secrets If set to true, the token and token_secret will be loaded * from the account's database. * @param force If set to true, we will simply force to create a new * RestProxy object. */ public void init_proxy (bool load_secrets = true, bool force = false) { if (proxy != null && !force) return; this.proxy = new Rest.OAuthProxy (Settings.get_consumer_key (), Settings.get_consumer_secret (), "https://api.twitter.com/", false); this.user_stream = new Cb.UserStream (this.screen_name, STRESSTEST); this.user_stream.register (this.event_receiver); if (load_secrets) { init_database (); int n_rows = db.select ("common").cols ("token", "token_secret") .run ((vals) => { proxy.token = vals[0]; proxy.token_secret = vals[1]; user_stream.set_proxy_data (proxy.token, proxy.token_secret); return false; //stop }); if (n_rows < 1) { critical ("Could not load token{_secret} for user %s", this.screen_name); } } } public void uninit () { this.proxy = null; this.user_counter.save (this.db.get_sqlite_db ()); this.user_stream.unregister (this.event_receiver); this.user_stream.stop (); this.user_stream = null; } /** * Loads the small and normally sized avatars from disk. * Normal: accounts/ID.png * Small: accounts/ID_small.png */ public void load_avatar () { string small_path = Dirs.config (@"accounts/$(id)_small.png"); string path = Dirs.config (@"accounts/$(id).png"); this.avatar_small = load_surface (small_path); this.avatar = load_surface (path); info_changed (screen_name, name, avatar, avatar_small); } public void set_new_avatar (Cairo.Surface new_avatar) { string path = Dirs.config (@"accounts/$(id).png"); string small_path = Dirs.config (@"accounts/$(id)_small.png"); Cairo.Surface avatar = scale_surface ((Cairo.ImageSurface)new_avatar, 48, 48); Cairo.Surface avatar_small = scale_surface ((Cairo.ImageSurface)new_avatar, 24, 24); write_surface (avatar, path); write_surface (avatar_small, small_path); this.avatar = avatar; this.avatar_small = avatar_small; } /** * Download the appropriate user info from the Twitter server, * updating the local information stored in this class' local variables * and the information stored in the account's database file. * * @param screen_name The screen name to use for the API call or null in * which case the ID will be used. */ public async void query_user_info_by_screen_name (string? screen_name = null) { if (proxy == null) error ("Proxy not initialized"); var call = proxy.new_call (); call.set_function ("1.1/users/show.json"); call.set_method ("GET"); if (screen_name != null) { call.add_param ("screen_name", screen_name); this.screen_name = screen_name; } else { call.add_param ("user_id", this.id.to_string ()); } call.add_param ("skip_status", "true"); Json.Node? root_node = null; try { root_node = yield Cb.Utils.load_threaded_async (call, null); } catch (GLib.Error e) { warning (e.message); return; } bool values_changed = false; var root = root_node.get_object (); this.id = root.get_int_member ("id"); if (this.name != root.get_string_member ("name")) { this.name = root.get_string_member ("name"); values_changed = true; } if (this.screen_name != root.get_string_member ("screen_name")) { string old_screen_name = this.screen_name; this.screen_name = root.get_string_member ("screen_name"); Utils.update_startup_account (old_screen_name, this.screen_name); values_changed = true; } Json.Array desc_urls = root.get_object_member ("entities").get_object_member ("description") .get_array_member ("urls"); var urls = new Cb.TextEntity[desc_urls.get_length ()]; desc_urls.foreach_element ((arr, index, node) => { Json.Object obj = node.get_object (); Json.Array indices = obj.get_array_member ("indices"); urls[index] = Cb.TextEntity () { from = (uint)indices.get_int_element (0), to = (uint)indices.get_int_element (1), display_text = obj.get_string_member ("expanded_url"), target = null }; }); this.description = Cb.TextTransform.text (root.get_string_member ("description"), urls, Cb.TransformFlags.EXPAND_LINKS, 0, 0); if (root.has_member ("profile_banner_url")) this.banner_url = root.get_string_member ("profile_banner_url"); /* Website URL */ if (root.get_object_member ("entities").has_member ("url")) { this.website = root.get_object_member ("entities").get_object_member ("url") .get_array_member ("urls").get_object_element (0).get_string_member ("expanded_url"); } else this.website = ""; string avatar_url = root.get_string_member ("profile_image_url"); values_changed |= yield update_avatar (avatar_url); if (values_changed) { if (this.db != null) this.save_info (); info_changed (this.screen_name, this.name, this.avatar_small, this.avatar); } } public async void init_information () { var collect_obj = new Collect (4); collect_obj.finished.connect (() => { init_information.callback (); }); query_user_info_by_screen_name.begin (null, () => { collect_obj.emit (); }); load_id_array.begin (collect_obj, "1.1/friendships/no_retweets/ids.json", true, (obj, res) => { Json.Array? arr = load_id_array.end (res); if (arr != null) { this.set_disabled_rts (arr); collect_obj.emit (); } }); load_id_array.begin (collect_obj, "1.1/blocks/ids.json", false, (obj, res) => { Json.Array? arr = load_id_array.end (res); if (arr != null) { this.set_blocked (arr); collect_obj.emit (); } }); load_id_array.begin (collect_obj, "1.1/mutes/users/ids.json", false, (obj, res) => { Json.Array? arr = load_id_array.end (res); if (arr != null) { this.set_muted (arr); collect_obj.emit (); } }); yield; } private async Json.Array? load_id_array (Collect collect_obj, string function, bool direct) { var call = this.proxy.new_call (); call.set_function (function); call.set_method ("GET"); Json.Node? root = null; try { root = yield Cb.Utils.load_threaded_async (call, null); } catch (GLib.Error e) { warning (e.message); collect_obj.emit (); return null; } if (direct) return root.get_array (); else return root.get_object ().get_array_member ("ids"); } /** * Updates the account's avatar picture. * This means that the new avatar will be downloaded if necessary and * scaled appropriately. * * @param url The url of the (possibly) new avatar(optional). */ private async bool update_avatar (string url = "") { string dest_path = Dirs.config (@"accounts/$(id)_small.png"); string big_dest = Dirs.config (@"accounts/$(id).png"); if (url.length > 0 && url == this.avatar_url) { if (GLib.FileUtils.test (dest_path, GLib.FileTest.EXISTS) && GLib.FileUtils.test (big_dest, GLib.FileTest.EXISTS)) return false; } debug ("Using %s to update the avatar(old: %s)", url, this.avatar_url); if (url.length > 0) { var msg = new Soup.Message ("GET", url); SOUP_SESSION.queue_message (msg, (_s, _msg) => { var data_stream = new MemoryInputStream.from_data (msg.response_body.data, GLib.g_free); string type = Cb.Utils.get_file_type (url); Gdk.Pixbuf pixbuf; try { pixbuf = new Gdk.Pixbuf.from_stream(data_stream); pixbuf.save(big_dest, type); data_stream.close (); double scale_x = 24.0 / pixbuf.get_width(); double scale_y = 24.0 / pixbuf.get_height(); var scaled_pixbuf = new Gdk.Pixbuf(Gdk.Colorspace.RGB, pixbuf.has_alpha, 8, 24, 24); pixbuf.scale(scaled_pixbuf, 0, 0, 24, 24, 0, 0, scale_x, scale_y, Gdk.InterpType.HYPER); scaled_pixbuf.save(dest_path, type); debug ("saving to %s", dest_path); this.avatar_small = Gdk.cairo_surface_create_from_pixbuf (scaled_pixbuf, 1, null); this.avatar = Gdk.cairo_surface_create_from_pixbuf (pixbuf, 1, null); } catch (GLib.Error e) { critical (e.message); } this.avatar_url = url; Corebird.db.update ("accounts").val ("avatar_url", url).where_eqi ("id", id).run (); update_avatar.callback (); }); yield; return true; } else { critical ("Not implemented yet"); } return false; } /** * Saves the account info both in the account's database and in the * global one. */ public void save_info () { db.replace ("info").vali64 ("id", id) .val ("screen_name", screen_name) .val ("name", name) .run (); Corebird.db.replace ("accounts").vali64 ("id", id) .val ("screen_name", screen_name) .val ("name", name) .val ("avatar_url", avatar_url) .run (); } /** * Load all the filters from the database. */ private void load_filters () { this.db.select ("filters").cols ("content", "id") .order ("id").run ((cols) => { Cb.Filter f = new Cb.Filter (cols[0]); f.set_id (int.parse (cols[1])); filters.add (f); return true; }); } public void add_filter (owned Cb.Filter f) { this.filters.add (f); } /** * Checks if any of the filters associated to this acount match * the given tweet. * * @param t The tweet to check for * * @return true iff at least one of the filters match, false otherwise. */ public bool filter_matches (Cb.Tweet t) { if (t.source_tweet.author.id == this.id) return false; string text = t.get_filter_text (); for (int i = 0; i < filters.length; i ++) { var f = this.filters.get (i); if (f.matches (text)) { return true; } } return false; } public void set_friends (Json.Array friends_array) { this.friends = new int64[friends_array.get_length ()]; debug ("Adding %d friends...", friends.length); for (int i = 0; i < friends_array.get_length (); i ++) { this.friends[i] = friends_array.get_int_element (i); } } public bool follows_id (int64 user_id) { foreach (int64 id in this.friends) if (id == user_id) return true; return false; } public void follow_id (int64 user_id) { this.friends.resize (this.friends.length + 1); this.friends[this.friends.length - 1] = user_id; } public void unfollow_id (int64 user_id) { if (this.friends == null || this.friends.length == 0) { warning ("friends == null"); return; } int64[] new_friends = new int64[this.friends.length]; int o = 0; bool found = false; for (int i = 0; i < this.friends.length; i++) { if (this.friends[i] == user_id) { found = true; continue; } new_friends[o] = this.friends[i]; o ++; } if (found) new_friends.resize (new_friends.length - 1); this.friends = new_friends; } public void set_muted (Json.Array muted_array) { this.muted = new int64[muted_array.get_length ()]; debug ("Add %d muted ids", this.muted.length); for (int i = 0; i < this.muted.length; i ++) { this.muted[i] = muted_array.get_int_element (i); } } public void mute_id (int64 id) { this.muted.resize (this.muted.length + 1); this.muted[this.muted.length - 1] = id; } public void unmute_id (int64 id) { if (this.muted == null || this.muted.length == 0) { warning ("muted == null"); return; } int64[] new_muted = new int64[this.muted.length - 1]; int o = 0; for (int i = 0; i < this.muted.length; i++) { if (this.muted[i] == id) { continue; } muted[o] = this.muted[i]; o ++; } this.muted = new_muted; } public void set_blocked (Json.Array blocked_array) { this.blocked = new int64[blocked_array.get_length ()]; debug ("Add %d blocked ids", this.blocked.length); for (int i = 0; i < this.blocked.length; i ++) { this.blocked[i] = blocked_array.get_int_element (i); } } public void block_id (int64 id) { this.blocked.resize (this.blocked.length + 1); this.blocked[this.blocked.length - 1] = id; } public void unblock_id (int64 id) { if (this.blocked == null || this.blocked.length == 0) { warning ("blocked == null"); return; } int64[] new_blocked = new int64[this.blocked.length - 1]; int o = 0; for (int i = 0; i < this.blocked.length; i++) { if (this.blocked[i] == id) { continue; } blocked[o] = this.blocked[i]; o ++; } this.blocked = new_blocked; } public void set_disabled_rts (Json.Array disabled_rts_array) { this.disabled_rts = new int64[disabled_rts_array.get_length ()]; debug ("Add %d disabled_rts ids", this.disabled_rts.length); for (int i = 0; i < this.disabled_rts.length; i ++) { this.disabled_rts[i] = disabled_rts_array.get_int_element (i); } } public void add_disabled_rts_id (int64 user_id) { this.disabled_rts.resize (this.disabled_rts.length + 1); this.disabled_rts[this.disabled_rts.length - 1] = id; } public void remove_disabled_rts_id (int64 user_id) { if (this.disabled_rts == null || this.disabled_rts.length == 0) { warning ("disabled_rts == null"); return; } int64[] new_disabled_rts = new int64[this.disabled_rts.length - 1]; int o = 0; for (int i = 0; i < this.disabled_rts.length; i++) { if (this.disabled_rts[i] == id) { continue; } disabled_rts[o] = this.disabled_rts[i]; o ++; } this.disabled_rts = new_disabled_rts; } public bool blocked_or_muted (int64 user_id) { foreach (int64 id in this.muted) if (id == user_id) return true; foreach (int64 id in this.blocked) if (id == user_id) return true; return false; } public bool is_blocked (int64 user_id) { foreach (int64 id in this.blocked) if (id == user_id) return true; return false; } public bool is_muted (int64 user_id) { foreach (int64 id in this.muted) if (id == user_id) return true; return false; } /** Static stuff ********************************************************************/ private static GLib.GenericArray? accounts = null; public static Account get_nth (uint index) { if (GLib.unlikely (accounts == null)) lookup_accounts (); return accounts.get (index); } public static uint get_n () { if (GLib.unlikely (accounts == null)) lookup_accounts (); return accounts.length; } /** * Look up the accounts. Each account has a .db in ~/.corebird/accounts/ * The accounts are initialized with only their screen_name and their ID. */ private static void lookup_accounts () { assert (accounts == null); accounts = new GLib.GenericArray (); Corebird.db.select ("accounts").cols ("id", "screen_name", "name", "avatar_url").run ((vals) => { Account acc = new Account (int64.parse(vals[0]), vals[1], vals[2]); acc.avatar_url = vals[3]; acc.load_avatar (); accounts.add (acc); return true; }); } /** * Adds the given account to the end of the current account list. * * @param acc The account to add. */ public static void add_account (Account acc) { accounts.add (acc); } /** * Removes the acccunt with th given screen name from the account list. * * @param screen_name The screen name of the account to remove. */ public static void remove_account (string screen_name) { if (GLib.unlikely (accounts == null)) lookup_accounts (); for (uint i = 0; i < accounts.length; i ++) { var a = accounts.get (i); if (a.screen_name == screen_name) { accounts.remove (a); return; } } } /** * Returns an unowned reference to the account with the given screen name. * * @param screen_name The screen name of the account to return * @return An unowned reference to the account object with the given screen name or * null of no such instance could be found. */ public static unowned Account? query_account (string screen_name) { if (GLib.unlikely (accounts == null)) lookup_accounts (); for (uint i = 0; i < accounts.length; i ++) { unowned Account a = accounts.get (i); if (screen_name == a.screen_name || screen_name == "@" + a.screen_name) return a; } return null; } public static unowned Account? query_account_by_id (int64 id) { if (GLib.unlikely (accounts == null)) lookup_accounts (); for (uint i = 0; i < accounts.length; i ++) { unowned Account a = accounts.get (i); if (id == a.id) return a; } return null; } } corebird-1.7.4/src/CbAvatarCache.c000066400000000000000000000151771324604713000166710ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2016 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbAvatarCache.h" G_DEFINE_TYPE (CbAvatarCache, cb_avatar_cache, G_TYPE_OBJECT); typedef struct _CacheEntry CacheEntry; struct _CacheEntry { gint64 user_id; int refcount; char *url; cairo_surface_t *surface; }; static void cache_entry_destroy (CacheEntry *entry) { g_free (entry->url); if (entry->surface) cairo_surface_destroy (entry->surface); } static inline CacheEntry * get_entry_for_user_id (CbAvatarCache *self, gint64 user_id) { guint i; for (i = 0; i < self->entries->len; i ++) { CacheEntry *e = &g_array_index (self->entries, CacheEntry, i); if (e->user_id == user_id) return e; } return NULL; } CbAvatarCache * cb_avatar_cache_new (void) { return CB_AVATAR_CACHE (g_object_new (CB_TYPE_AVATAR_CACHE, NULL)); } void cb_avatar_cache_add (CbAvatarCache *self, gint64 user_id, cairo_surface_t *surface, const char *url) { CacheEntry *entry = NULL; g_return_if_fail (CB_IS_AVATAR_CACHE (self)); entry = get_entry_for_user_id (self, user_id); if (entry == NULL) { /* Actually a new entry */ g_array_set_size (self->entries, self->entries->len + 1); entry = &g_array_index (self->entries, CacheEntry, self->entries->len - 1); entry->user_id = user_id; if (surface) entry->surface = cairo_surface_reference (surface); entry->url = g_strdup (url); /* The subsequent incrase_refcount will push this up to 1. Entries with * refcount 0 won't be removed from the cache until a decrease_refcount call */ entry->refcount = 0; } else { if (entry->surface != NULL) cairo_surface_destroy (entry->surface); if (surface) entry->surface = cairo_surface_reference (surface); if (entry->url != NULL) g_free (entry->url); entry->url = g_strdup (url); } } void cb_avatar_cache_set_avatar (CbAvatarCache *self, gint64 user_id, cairo_surface_t *surface, const char *url) { CacheEntry *entry = NULL; g_return_if_fail (CB_IS_AVATAR_CACHE (self)); g_return_if_fail (surface != NULL); entry = get_entry_for_user_id (self, user_id); g_assert (entry != NULL); if (entry->surface != NULL) cairo_surface_destroy (entry->surface); entry->surface = cairo_surface_reference (surface); if (entry->url != NULL) g_free (entry->url); entry->url = g_strdup (url); } cairo_surface_t * cb_avatar_cache_get_surface_for_id (CbAvatarCache *self, gint64 user_id, gboolean *out_found) { const CacheEntry *entry = NULL; g_return_val_if_fail (CB_IS_AVATAR_CACHE (self), NULL); g_return_val_if_fail (user_id > 0, NULL); g_return_val_if_fail (out_found != NULL, NULL); entry = get_entry_for_user_id (self, user_id); if (entry != NULL) { *out_found = TRUE; return entry->surface; /* Can still be NULL... */ } else { *out_found = FALSE; return NULL; } } void cb_avatar_cache_set_url (CbAvatarCache *self, gint64 user_id, const char *url) { CacheEntry *entry = NULL; g_return_if_fail (CB_IS_AVATAR_CACHE (self)); entry = get_entry_for_user_id (self, user_id); g_assert (entry != NULL); if (entry->url != NULL) g_free (entry->url); entry->url = g_strdup (url); } void cb_avatar_cache_decrease_refcount_for_surface (CbAvatarCache *self, cairo_surface_t *surface) { guint i; guint index = (guint) -1; CacheEntry *entry = NULL; g_return_if_fail (CB_IS_AVATAR_CACHE (self)); g_return_if_fail (surface != NULL); for (i = 0; i < self->entries->len; i ++) { CacheEntry *e = &g_array_index (self->entries, CacheEntry, i); if (e->surface == surface) { entry = e; index = i; break; } } if (entry == NULL) { /* Surface not even in cache */ return; } entry->refcount --; if (entry->refcount <= 0) { g_debug ("Removing avatar with id %" G_GINT64_FORMAT " from cache", entry->user_id); g_array_remove_index_fast (self->entries, index); } } void cb_avatar_cache_increase_refcount_for_surface (CbAvatarCache *self, cairo_surface_t *surface) { guint i; g_return_if_fail (CB_IS_AVATAR_CACHE (self)); g_return_if_fail (surface != NULL); for (i = 0; i < self->entries->len; i ++) { CacheEntry *e = &g_array_index (self->entries, CacheEntry, i); if (e->surface == surface) { e->refcount ++; break; } } } const char * cb_avatar_cache_get_url_for_id (CbAvatarCache *self, gint64 user_id) { const CacheEntry *entry; g_return_val_if_fail (CB_IS_AVATAR_CACHE (self), NULL); g_return_val_if_fail (user_id > 0, NULL); entry = get_entry_for_user_id (self, user_id); if (entry == NULL) return NULL; return entry->url; } guint cb_avatar_cache_get_n_entries (CbAvatarCache *self) { g_return_val_if_fail (CB_IS_AVATAR_CACHE (self), 0); return self->entries->len; } static void cb_avatar_cache_finalize (GObject *obj) { CbAvatarCache *self = CB_AVATAR_CACHE (obj); g_array_free (self->entries, TRUE); G_OBJECT_CLASS (cb_avatar_cache_parent_class)->finalize (obj); } static void cb_avatar_cache_init (CbAvatarCache *self) { self->entries = g_array_new (FALSE, TRUE, sizeof (CacheEntry)); g_array_set_clear_func (self->entries, (GDestroyNotify) cache_entry_destroy); } static void cb_avatar_cache_class_init (CbAvatarCacheClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = cb_avatar_cache_finalize; } corebird-1.7.4/src/CbAvatarCache.h000066400000000000000000000057311324604713000166710ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2016 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef AVATAR_CACHE_H #define AVATAR_CACHE_H #include #include G_BEGIN_DECLS typedef struct _CbAvatarCache CbAvatarCache; struct _CbAvatarCache { GObject parent_instance; GArray *entries; }; #define CB_TYPE_AVATAR_CACHE cb_avatar_cache_get_type () G_DECLARE_FINAL_TYPE (CbAvatarCache, cb_avatar_cache, CB, AVATAR_CACHE, GObject); CbAvatarCache *cb_avatar_cache_new (void); void cb_avatar_cache_decrease_refcount_for_surface (CbAvatarCache *self, cairo_surface_t *surface); void cb_avatar_cache_increase_refcount_for_surface (CbAvatarCache *self, cairo_surface_t *surface); void cb_avatar_cache_add (CbAvatarCache *self, gint64 user_id, cairo_surface_t *surface, const char *url); void cb_avatar_cache_set_avatar (CbAvatarCache *self, gint64 user_id, cairo_surface_t *surface, const char *url); cairo_surface_t * cb_avatar_cache_get_surface_for_id (CbAvatarCache *self, gint64 user_id, gboolean *out_found); const char * cb_avatar_cache_get_url_for_id (CbAvatarCache *self, gint64 user_id); guint cb_avatar_cache_get_n_entries (CbAvatarCache *self); void cb_avatar_cache_set_url (CbAvatarCache *self, gint64 user_id, const char *url); G_END_DECLS #endif corebird-1.7.4/src/CbBundle.c000066400000000000000000000161511324604713000157310ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbBundle.h" #include G_DEFINE_TYPE (CbBundle, cb_bundle, G_TYPE_OBJECT); CbBundle * cb_bundle_new (void) { return CB_BUNDLE (g_object_new (CB_TYPE_BUNDLE, NULL)); } static GValue * find_value (CbBundle *self, int key) { guint i; for (i = 0; i < self->keys->len; i ++) { int k = g_array_index (self->keys, int, i); if (k == key) { return &g_array_index (self->values, GValue, i); } } return NULL; } guint cb_bundle_get_size (CbBundle *self) { g_return_val_if_fail (CB_IS_BUNDLE (self), 0); g_assert (self->keys->len == self->values->len); return self->keys->len; } void cb_bundle_put_string (CbBundle *self, int key, const char *val) { GValue *gvalue; g_return_if_fail (CB_IS_BUNDLE (self)); g_return_if_fail (val != NULL); g_assert (find_value (self, key) == NULL); g_array_append_val (self->keys, key); g_array_set_size (self->values, self->values->len + 1); gvalue = &g_array_index (self->values, GValue, self->values->len - 1); g_assert (self->keys->len == self->values->len); g_value_init (gvalue, G_TYPE_STRING); g_value_set_string (gvalue, val); } const char * cb_bundle_get_string (CbBundle *self, int key) { GValue *gvalue; g_return_val_if_fail (CB_IS_BUNDLE (self), NULL); gvalue = find_value (self, key); if (gvalue != NULL) return g_value_get_string (gvalue); return NULL; } void cb_bundle_put_int (CbBundle *self, int key, int val) { GValue *gvalue; g_return_if_fail (CB_IS_BUNDLE (self)); g_assert (find_value (self, key) == NULL); g_array_append_val (self->keys, key); g_array_set_size (self->values, self->values->len + 1); gvalue = &g_array_index (self->values, GValue, self->values->len - 1); g_assert (self->keys->len == self->values->len); g_value_init (gvalue, G_TYPE_INT); g_value_set_int (gvalue, val); } int cb_bundle_get_int (CbBundle *self, int key) { GValue *gvalue; g_return_val_if_fail (CB_IS_BUNDLE (self), 0); gvalue = find_value (self, key); if (gvalue != NULL) return g_value_get_int (gvalue); return -1; } void cb_bundle_put_int64 (CbBundle *self, int key, gint64 val) { GValue *gvalue; g_return_if_fail (CB_IS_BUNDLE (self)); g_assert (find_value (self, key) == NULL); g_array_append_val (self->keys, key); g_array_set_size (self->values, self->values->len + 1); gvalue = &g_array_index (self->values, GValue, self->values->len - 1); g_assert (self->keys->len == self->values->len); g_value_init (gvalue, G_TYPE_INT64); g_value_set_int64 (gvalue, val); } gint64 cb_bundle_get_int64 (CbBundle *self, int key) { GValue *gvalue; g_return_val_if_fail (CB_IS_BUNDLE (self), 0); gvalue = find_value (self, key); if (gvalue != NULL) return g_value_get_int64 (gvalue); return -1; } void cb_bundle_put_bool (CbBundle *self, int key, gboolean val) { GValue *gvalue; g_return_if_fail (CB_IS_BUNDLE (self)); g_assert (find_value (self, key) == NULL); g_array_append_val (self->keys, key); g_array_set_size (self->values, self->values->len + 1); gvalue = &g_array_index (self->values, GValue, self->values->len - 1); g_assert (self->keys->len == self->values->len); g_value_init (gvalue, G_TYPE_BOOLEAN); g_value_set_boolean (gvalue, val); } gboolean cb_bundle_get_bool (CbBundle *self, int key) { GValue *gvalue; g_return_val_if_fail (CB_IS_BUNDLE (self), 0); gvalue = find_value (self, key); if (gvalue != NULL) return g_value_get_boolean (gvalue); return FALSE; } void cb_bundle_put_object (CbBundle *self, int key, GObject *val) { GValue *gvalue; g_return_if_fail (CB_IS_BUNDLE (self)); g_assert (find_value (self, key) == NULL); g_array_append_val (self->keys, key); g_array_set_size (self->values, self->values->len + 1); gvalue = &g_array_index (self->values, GValue, self->values->len - 1); g_assert (self->keys->len == self->values->len); g_value_init (gvalue, G_TYPE_POINTER); g_value_set_pointer (gvalue, g_object_ref (val)); } GObject * cb_bundle_get_object (CbBundle *self, int key) { GValue *gvalue; g_return_val_if_fail (CB_IS_BUNDLE (self), 0); gvalue = find_value (self, key); if (gvalue != NULL) return g_value_get_pointer (gvalue); return NULL; } gboolean cb_bundle_equals (CbBundle *self, CbBundle *other) { guint i; g_return_val_if_fail (CB_IS_BUNDLE (self), FALSE); if (other == NULL) return FALSE; g_return_val_if_fail (CB_IS_BUNDLE (other), FALSE); for (i = 0; i < self->values->len; i ++) { GValue *v1; GValue *v2; char *contents1; char *contents2; int key = g_array_index (self->keys, int, i); v1 = find_value (self, key); v2 = find_value (other, key); /* They must contains the same keys */ if (v1 == NULL) return FALSE; contents1 = g_strdup_value_contents (v1); contents2 = g_strdup_value_contents (v2); if (strcmp (contents1, contents2) != 0) return FALSE; g_free (contents1); g_free (contents2); } return self->keys->len == other->keys->len; } static void cb_bundle_finalize (GObject *object) { guint i; CbBundle *self = CB_BUNDLE (object); /* _put_object ref's the object, so unref it here */ for (i = 0; i < self->values->len; i ++) { GValue *v = &g_array_index (self->values, GValue, i); /* We only insert pointers for _put_object */ if (G_VALUE_HOLDS_POINTER (v)) g_object_unref (G_OBJECT (g_value_get_pointer (v))); } g_array_unref (self->keys); g_array_unref (self->values); G_OBJECT_CLASS (cb_bundle_parent_class)->finalize (object); } static void cb_bundle_class_init (CbBundleClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = cb_bundle_finalize; } static void cb_bundle_init (CbBundle *self) { self->keys = g_array_new (FALSE, FALSE, sizeof (int)); self->values = g_array_new (FALSE, TRUE, sizeof (GValue)); g_array_set_clear_func (self->values, (GDestroyNotify)g_value_reset); } corebird-1.7.4/src/CbBundle.h000066400000000000000000000041231324604713000157320ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef _CB_BUNDLE_H_ #define _CB_BUNDLE_H_ #include /* * TODO: * Make every CbBundle instance take a fixed amount of data instead * of keeping two GArrays around. The keys are already required to be * integers, so just also require them to be subsequent integers starting * from 0 and just them as indeces into an array. */ struct _CbBundle { GObject parent_instance; GArray *values; GArray *keys; }; typedef struct _CbBundle CbBundle; #define CB_TYPE_BUNDLE cb_bundle_get_type () G_DECLARE_FINAL_TYPE (CbBundle, cb_bundle, CB, BUNDLE, GObject); CbBundle * cb_bundle_new (void); gboolean cb_bundle_equals (CbBundle *self, CbBundle *other); void cb_bundle_put_string (CbBundle *self, int key, const char *val); const char * cb_bundle_get_string (CbBundle *self, int key); void cb_bundle_put_int (CbBundle *self, int key, int val); int cb_bundle_get_int (CbBundle *self, int key); void cb_bundle_put_int64 (CbBundle *self, int key, gint64 val); gint64 cb_bundle_get_int64 (CbBundle *self, int key); void cb_bundle_put_bool (CbBundle *self, int key, gboolean val); gboolean cb_bundle_get_bool (CbBundle *self, int key); void cb_bundle_put_object (CbBundle *self, int key, GObject *object); GObject * cb_bundle_get_object (CbBundle *self, int key); #endif corebird-1.7.4/src/CbBundleHistory.c000066400000000000000000000076411324604713000173170ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbBundleHistory.h" #include G_DEFINE_TYPE (CbBundleHistory, cb_bundle_history, G_TYPE_OBJECT); static void cb_bundle_history_finalize (GObject *object) { CbBundleHistory *self = CB_BUNDLE_HISTORY (object); int i; for (i = 0; i < HISTORY_SIZE; i ++) { if (self->bundles[i] != NULL) g_object_unref (self->bundles[i]); } G_OBJECT_CLASS (cb_bundle_history_parent_class)->finalize (object); } static void cb_bundle_history_init (CbBundleHistory *self) { int i; for (i = 0; i < HISTORY_SIZE; i ++) self->elements[i] = -1; self->pos = -1; } static void cb_bundle_history_class_init (CbBundleHistoryClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = cb_bundle_history_finalize; } CbBundleHistory * cb_bundle_history_new () { return CB_BUNDLE_HISTORY (g_object_new (CB_TYPE_BUNDLE_HISTORY, NULL)); } void cb_bundle_history_push (CbBundleHistory *self, int v, CbBundle *bundle) { if (self->pos < HISTORY_SIZE - 1) { /* More space available. Just put it at the end and increase pos */ self->pos ++; } else { /* No space left. Move everything one place to the start and then add @bundle */ memmove (self->elements, self->elements + 1, sizeof (int) * (HISTORY_SIZE - 1)); memmove (self->bundles, self->bundles + 1, sizeof (CbBundle *) * (HISTORY_SIZE - 1)); } self->elements[self->pos] = v; self->bundles[self->pos] = bundle ? g_object_ref (bundle) : NULL; } int cb_bundle_history_back (CbBundleHistory *self) { if (self->pos > 0) { self->pos --; return self->elements[self->pos]; } return -1; } int cb_bundle_history_forward (CbBundleHistory *self) { if (self->pos < HISTORY_SIZE - 1) { self->pos ++; return self->elements[self->pos]; } return -1; } gboolean cb_bundle_history_at_start (CbBundleHistory *self) { return self->pos == 0; } gboolean cb_bundle_history_at_end (CbBundleHistory *self) { if (self->pos == HISTORY_SIZE - 1) return TRUE; if (self->pos == -1 || self->elements[self->pos] == -1 || self->elements[self->pos + 1] == -1) return TRUE; return FALSE; } void cb_bundle_history_remove_current (CbBundleHistory *self) { self->elements[self->pos] = -1; if (self->bundles[self->pos] != NULL) g_object_unref (self->bundles[self->pos]); self->bundles[self->pos] = NULL; /* Fill an eventual gap */ if (self->pos + 1 < HISTORY_SIZE - 1 && self->elements[self->pos + 1] != -1) { memmove (self->elements + self->pos, self->elements + self->pos + 1, (HISTORY_SIZE - self->pos - 1) * sizeof (int)); memmove (self->bundles + self->pos, self->bundles + self->pos + 1, (HISTORY_SIZE - self->pos - 1) * sizeof (CbBundle*)); } } int cb_bundle_history_get_current (CbBundleHistory *self) { if (self->pos == -1) return -1; return self->elements[self->pos]; } CbBundle * cb_bundle_history_get_current_bundle (CbBundleHistory *self) { if (self->pos == -1) return NULL; return self->bundles[self->pos]; } corebird-1.7.4/src/CbBundleHistory.h000066400000000000000000000040441324604713000173160ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef CB_BUNDLE_HISTORY_H #define CB_BUNDLE_HISTORY_H #include #include "CbBundle.h" #define HISTORY_SIZE 10 struct _CbBundleHistory { GObject parent_instance; int pos; int elements[HISTORY_SIZE]; CbBundle *bundles[HISTORY_SIZE]; }; typedef struct _CbBundleHistory CbBundleHistory; #define CB_TYPE_BUNDLE_HISTORY cb_bundle_history_get_type () G_DECLARE_FINAL_TYPE (CbBundleHistory, cb_bundle_history, CB, BUNDLE_HISTORY, GObject); CbBundleHistory * cb_bundle_history_new (void); void cb_bundle_history_push (CbBundleHistory *self, int v, CbBundle *bundle); int cb_bundle_history_back (CbBundleHistory *self); int cb_bundle_history_forward (CbBundleHistory *self); gboolean cb_bundle_history_at_start (CbBundleHistory *self); gboolean cb_bundle_history_at_end (CbBundleHistory *self); void cb_bundle_history_remove_current (CbBundleHistory *self); int cb_bundle_history_get_current (CbBundleHistory *self); CbBundle * cb_bundle_history_get_current_bundle (CbBundleHistory *self); #endif corebird-1.7.4/src/CbComposeJob.c000066400000000000000000000322771324604713000165670ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include #include "CbComposeJob.h" #include "CbUtils.h" G_DEFINE_TYPE (CbComposeJob, cb_compose_job, G_TYPE_OBJECT); #define MAX_UPLOADS 4 static void do_send (CbComposeJob *self); enum { IMAGE_UPLOAD_PROGRESS, IMAGE_UPLOAD_FINISHED, LAST_SIGNAL }; static guint compose_job_signals[LAST_SIGNAL] = { 0 }; static void image_upload_free (ImageUpload *u) { g_clear_pointer (&u->filename, g_free); g_clear_object (&u->cancellable); } static char * build_image_id_string (CbComposeJob *self) { const ImageUpload *uploads[MAX_UPLOADS]; guint n_uploads = 0; guint n_unfinished_uploads = 0; GString *str; guint i; for (i = 0; i < MAX_UPLOADS; i ++) { const ImageUpload *upload = &self->image_uploads[i]; if (upload->filename == NULL) continue; uploads[n_uploads] = upload; n_uploads ++; if (upload->id == 0) n_unfinished_uploads ++; } g_assert (n_unfinished_uploads == 0); g_assert (n_uploads <= 4); if (n_uploads == 0) return NULL; str = g_string_new (NULL); /* n_uploads is at least 1 at this point */ g_string_append_printf (str, "%" G_GINT64_FORMAT, uploads[0]->id); for (i = 1; i < n_uploads; i ++) { g_assert (uploads[i]->id != 0); g_string_append_printf (str, ",%" G_GINT64_FORMAT, uploads[i]->id); } return g_string_free (str, FALSE); } static void cancelled_cb (GCancellable *cancellable, gpointer user_data) { CbComposeJob *self = user_data; guint i; /* Abort mission */ for (i = 0; i < MAX_UPLOADS; i ++) { const ImageUpload *upload = &self->image_uploads[i]; if (upload->filename != NULL && upload->id == 0) { g_cancellable_cancel (upload->cancellable); } } } static guint cb_compose_job_get_n_unfinished_uploads (CbComposeJob *self) { guint i; guint n = 0; for (i = 0; i < MAX_UPLOADS; i ++) { const ImageUpload *upload = &self->image_uploads[i]; if (upload->filename != NULL && upload->id == 0) n ++; } return n; } static void cb_compose_job_finalize (GObject *object) { CbComposeJob *self = CB_COMPOSE_JOB (object); guint i; for (i = 0; i < MAX_UPLOADS; i ++) { ImageUpload *upload = &self->image_uploads[i]; if (upload->filename == NULL) continue; image_upload_free (upload); } g_clear_object (&self->account_proxy); g_clear_object (&self->upload_proxy); g_clear_object (&self->cancellable); g_clear_object (&self->send_call); g_free (self->text); G_OBJECT_CLASS (cb_compose_job_parent_class)->finalize (object); } static void cb_compose_job_class_init (CbComposeJobClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS (class); gobject_class->finalize = cb_compose_job_finalize; compose_job_signals[IMAGE_UPLOAD_PROGRESS] = g_signal_new ("image-upload-progress", G_OBJECT_CLASS_TYPE (gobject_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_DOUBLE); compose_job_signals[IMAGE_UPLOAD_FINISHED] = g_signal_new ("image-upload-finished", G_OBJECT_CLASS_TYPE (gobject_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); } static void cb_compose_job_init (CbComposeJob *self) { } CbComposeJob * cb_compose_job_new (RestProxy *account_proxy, RestProxy *upload_proxy, GCancellable *cancellable) { CbComposeJob *self = CB_COMPOSE_JOB (g_object_new (CB_TYPE_COMPOSE_JOB, NULL)); g_set_object (&self->account_proxy, account_proxy); g_set_object (&self->upload_proxy, upload_proxy); g_set_object (&self->cancellable, cancellable); g_signal_connect (cancellable, "cancelled", G_CALLBACK (cancelled_cb), self); return self; } static void image_upload_cb (RestProxyCall *call, gsize total, gsize uploaded, const GError *error, GObject *weak_object, gpointer user_data) { CbComposeJob *self = CB_COMPOSE_JOB (weak_object); ImageUpload *upload = user_data; double percent = (double)uploaded / (double)total; if (error != NULL) { /* We also get here when aborting, in which case @upload is already garbage */ if (error->code != REST_PROXY_ERROR_CANCELLED && upload->filename != NULL) g_signal_emit (self, compose_job_signals[IMAGE_UPLOAD_FINISHED], 0, upload->filename, error->message); return; } if (g_cancellable_is_cancelled (upload->cancellable)) { /* Note that this case can happen more than once. */ rest_proxy_call_cancel (call); g_object_unref (upload->cancellable); return; } g_signal_emit (self, compose_job_signals[IMAGE_UPLOAD_PROGRESS], 0, upload->filename, percent); if (uploaded == total) { JsonParser *parser = json_parser_new (); JsonObject *root_object; GError *json_error = NULL; const char *error_message = NULL; json_parser_load_from_data (parser, rest_proxy_call_get_payload (call), -1, &json_error); if (json_error) { g_warning ("Could not upload %s: %s", upload->filename, json_error->message); error_message = json_error->message; } else { root_object = json_node_get_object (json_parser_get_root (parser)); upload->id = json_object_get_int_member (root_object, "media_id"); } g_debug ("%s ID: %" G_GINT64_FORMAT, upload->filename, upload->id); g_signal_emit (self, compose_job_signals[IMAGE_UPLOAD_FINISHED], 0, upload->filename, error_message); g_object_unref (parser); /* Now, check whether send() has already been called and if so, finish that one */ if (self->send_task != NULL) { g_assert (self->send_call != NULL); if (cb_compose_job_get_n_unfinished_uploads (self) == 0) { /* Obviously, this was the last image to be uploaded before we could start * sending the actual tweet, so do that now... */ do_send (self); } } } } void cb_compose_job_upload_image_async (CbComposeJob *self, const char *image_path) { ImageUpload *upload = NULL; RestProxyCall *call; RestParam *param; GFile *file; char *contents; gsize contents_length; guint i; for (i = 0; i < MAX_UPLOADS; i ++) { ImageUpload *u = &self->image_uploads[i]; if (u->filename == NULL) { upload = u; break; } } g_assert (upload != NULL); upload->filename = g_strdup (image_path); upload->cancellable = g_cancellable_new (); file = g_file_new_for_path (image_path); /* TODO: Error checking? */ g_file_load_contents (file, NULL, &contents, &contents_length, NULL, NULL); g_object_unref (file); call = rest_proxy_new_call (self->upload_proxy); rest_proxy_call_set_function (call, "1.1/media/upload.json"); rest_proxy_call_set_method (call, "POST"); param = rest_param_new_full ("media", REST_MEMORY_TAKE, contents, contents_length, "multipart/form-data", image_path); rest_proxy_call_add_param_full (call, param); #ifdef DEBUG { char *s = cb_utils_rest_proxy_call_to_string (call); g_debug ("%s: %s", G_STRLOC, s); g_free (s); } #endif rest_proxy_call_upload (call, image_upload_cb, G_OBJECT (self), upload->cancellable, upload, NULL); g_object_unref (call); } void cb_compose_job_abort_image_upload (CbComposeJob *self, const char *image_path) { guint i; for (i = 0; i < MAX_UPLOADS; i ++) { ImageUpload *upload = &self->image_uploads[i]; if (upload->filename != NULL && strcmp (upload->filename, image_path) == 0) { g_cancellable_cancel (upload->cancellable); image_upload_free (upload); break; } } } void cb_compose_job_set_reply_id (CbComposeJob *self, gint64 reply_id) { self->reply_id = reply_id; } void cb_compose_job_set_quoted_tweet (CbComposeJob *self, CbTweet *quoted_tweet) { g_set_object (&self->quoted_tweet, quoted_tweet); } void cb_compose_job_set_text (CbComposeJob *self, const char *text) { self->text = g_strdup (text); } static void send_tweet_call_completed_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { RestProxyCall *call = REST_PROXY_CALL (source_object); GTask *send_task = user_data; GError *error = NULL; rest_proxy_call_invoke_finish (call, result, &error); if (error) { g_warning ("Could not send tweet: %s", error->message); g_task_return_error (send_task, error); } else { g_task_return_boolean (send_task, TRUE); } g_object_unref (send_task); } static void do_send (CbComposeJob *self) { char *media_ids = build_image_id_string (self); g_assert (cb_compose_job_get_n_unfinished_uploads (self) == 0); g_assert (self->send_call != NULL); g_assert (self->send_task != NULL); if (media_ids) rest_proxy_call_take_param (self->send_call, "media_ids", media_ids); #ifdef DEBUG { char *s = cb_utils_rest_proxy_call_to_string (self->send_call); g_debug ("%s: %s", G_STRLOC, s); g_free (s); } #endif rest_proxy_call_invoke_async (self->send_call, self->cancellable, send_tweet_call_completed_cb, self->send_task); } void cb_compose_job_send_async (CbComposeJob *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; RestProxyCall *call; g_assert (self->send_task == NULL); task = g_task_new (self, cancellable, callback, user_data); call = rest_proxy_new_call (self->account_proxy); rest_proxy_call_set_function (call, "1.1/statuses/update.json"); rest_proxy_call_set_method (call, "POST"); rest_proxy_call_add_param (call, "auto_populate_reply_metadata", "true"); if (self->reply_id != 0) { char *id_str = g_strdup_printf ("%" G_GINT64_FORMAT, self->reply_id); g_assert (self->quoted_tweet == NULL); rest_proxy_call_take_param (call, "in_reply_to_status_id", id_str); } else if (self->quoted_tweet != NULL) { const CbMiniTweet *mt = self->quoted_tweet->retweeted_tweet != NULL ? self->quoted_tweet->retweeted_tweet : &self->quoted_tweet->source_tweet; char *quoted_url = g_strdup_printf ("https://twitter.com/%s/status/%" G_GINT64_FORMAT, mt->author.screen_name, mt->id); g_assert (self->reply_id == 0); rest_proxy_call_take_param (call, "attachment_url", quoted_url); } rest_proxy_call_take_param (call, "status", g_steal_pointer (&self->text)); self->send_call = call; self->send_task = task; if (cb_compose_job_get_n_unfinished_uploads (self) > 0) { /* In this case, we need to wait until ALL uploads are complete and successful. */ } else { do_send (self); } } gboolean cb_compose_job_send_finish (CbComposeJob *self, GAsyncResult *result, GError **error) { return g_task_propagate_boolean (G_TASK (result), error); } corebird-1.7.4/src/CbComposeJob.h000066400000000000000000000060621324604713000165650ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef __CB_COMPOSE_JOB_H__ #define __CB_COMPOSE_JOB_H__ #include #include "CbTweet.h" #include "rest/rest-proxy.h" G_BEGIN_DECLS #define CB_TYPE_COMPOSE_JOB (cb_compose_job_get_type ()) G_DECLARE_FINAL_TYPE (CbComposeJob, cb_compose_job, CB, COMPOSE_JOB, GObject); typedef struct { GCancellable *cancellable; char *filename; gint64 id; /* 0 when not uploaded yet */ } ImageUpload; struct _CbComposeJob { GObject parent_instance; ImageUpload image_uploads[4]; RestProxy *account_proxy; RestProxy *upload_proxy; gint64 reply_id; CbTweet *quoted_tweet; char *text; GCancellable *cancellable; RestProxyCall *send_call; GTask *send_task; }; typedef struct _CbComposeJob CbComposeJob; CbComposeJob *cb_compose_job_new (RestProxy *account_proxy, RestProxy *upload_proxy, GCancellable *cancellable); void cb_compose_job_upload_image_async (CbComposeJob *self, const char *image_path); void cb_compose_job_abort_image_upload (CbComposeJob *self, const char *image_path); void cb_compose_job_set_reply_id (CbComposeJob *self, gint64 reply_id); void cb_compose_job_set_quoted_tweet (CbComposeJob *self, CbTweet *quoted_tweet); void cb_compose_job_set_text (CbComposeJob *self, const char *text); void cb_compose_job_send_async (CbComposeJob *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean cb_compose_job_send_finish (CbComposeJob *self, GAsyncResult *result, GError **error); G_END_DECLS #endif corebird-1.7.4/src/CbDeltaUpdater.c000066400000000000000000000060751324604713000171020ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbDeltaUpdater.h" G_DEFINE_TYPE(CbDeltaUpdater, cb_delta_updater, G_TYPE_OBJECT) static gboolean minutely_cb (gpointer user_data) { CbDeltaUpdater *self = user_data; GDateTime *now; GList *widgets; GList *l; if (!GTK_IS_WIDGET (self->listbox)) return G_SOURCE_REMOVE; widgets = gtk_container_get_children (GTK_CONTAINER (self->listbox)); now = g_date_time_new_now_local (); for (l = widgets; l != NULL; l = l->next) { GtkWidget *row = GTK_WIDGET (l->data); if (CB_IS_TWITTER_ITEM (row)) { CbTwitterItem *item = CB_TWITTER_ITEM (row); gint64 timestamp = cb_twitter_item_get_timestamp (item); gint64 last_set_timediff = cb_twitter_item_get_last_set_timediff (item); GDateTime *item_time = g_date_time_new_from_unix_local (timestamp); GTimeSpan time_diff = g_date_time_difference (now, item_time); int seconds = time_diff / 1000 / 1000; if (last_set_timediff < 60) { /* New minute */ cb_twitter_item_update_time_delta (item, now); cb_twitter_item_set_last_set_timediff (item, seconds / 60); } else if (seconds > last_set_timediff + 60) { /* New hour */ cb_twitter_item_update_time_delta (item, now); cb_twitter_item_set_last_set_timediff (item, seconds / 60); } g_date_time_unref (item_time); } } g_date_time_unref (now); g_list_free (widgets); return G_SOURCE_CONTINUE; } static void cb_delta_updater_finalize (GObject *object) { CbDeltaUpdater *self = CB_DELTA_UPDATER (object); if (self->minutely_id != 0) g_source_remove (self->minutely_id); G_OBJECT_CLASS (cb_delta_updater_parent_class)->finalize (object); } static void cb_delta_updater_init (CbDeltaUpdater *self) { self->minutely_id = g_timeout_add (1000 * 60, minutely_cb, self); } static void cb_delta_updater_class_init (CbDeltaUpdaterClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = cb_delta_updater_finalize; } CbDeltaUpdater * cb_delta_updater_new (GtkWidget *listbox) { CbDeltaUpdater *self = g_object_new (CB_TYPE_DELTA_UPDATER, NULL); g_return_val_if_fail (GTK_IS_LIST_BOX (listbox), self); self->listbox = listbox; return self; } corebird-1.7.4/src/CbDeltaUpdater.h000066400000000000000000000035411324604713000171020ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef DELTA_UPDATER_H #define DELTA_UPDATER_H #include #include #include "CbTwitterItem.h" typedef struct _CbDeltaUpdater CbDeltaUpdater; typedef struct _CbDeltaUpdaterClass CbDeltaUpdaterClass; #define CB_TYPE_DELTA_UPDATER (cb_delta_updater_get_type ()) #define CB_DELTA_UPDATER(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, CB_TYPE_DELTA_UPDATER, CbDeltaUpdater)) #define CB_DELTA_UPDATER_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST(cls, CB_TYPE_DELTA_UPDATER, CbDeltaUpdaterClass)) #define CB_IS_DELTA_UPDATER(obj) (G_TYPE_CHECK_INSTANCE_TYPE(obj, CB_TYPE_DELTA_UPDATER)) #define CB_IS_DELTA_UPDATER_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE(cls, CB_TYPE_DELTA_UPDATER)) #define CB_DELTA_UPDATER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS(obj, CB_TYPE_DELTA_UPDATER, CbDeltaUpdaterClass)) struct _CbDeltaUpdater { GObject parent_instance; GtkWidget *listbox; guint minutely_id; }; struct _CbDeltaUpdaterClass { GObjectClass parent_class; }; GType cb_delta_updater_get_type (void) G_GNUC_CONST; CbDeltaUpdater * cb_delta_updater_new (GtkWidget *listbox); #endif corebird-1.7.4/src/CbEmojiChooser.c000066400000000000000000000505441324604713000171120ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ /* * This is basically GtkEmojiChooser from GTK+ but with less features. */ #include "CbEmojiChooser.h" #include #define EMOJI_PER_ROW 7 /* From 2017-10-18 */ #define EMOJI_DATA_CHECKSUM "2ad33472d280d83737884a0e60a9236793653111" enum { EMOJI_PICKED, LAST_SIGNAL }; static int signals[LAST_SIGNAL]; G_DEFINE_TYPE (CbEmojiChooser, cb_emoji_chooser, GTK_TYPE_BOX); static void scroll_to_section (GtkButton *button, gpointer data) { EmojiSection *section = data; CbEmojiChooser *chooser; GtkAdjustment *adj; GtkAllocation alloc = { 0, 0, 0, 0 }; double page_increment, value; gboolean dummy; chooser = CB_EMOJI_CHOOSER (gtk_widget_get_ancestor (GTK_WIDGET (button), CB_TYPE_EMOJI_CHOOSER)); adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window)); if (section->heading) gtk_widget_get_allocation (section->heading, &alloc); page_increment = gtk_adjustment_get_page_increment (adj); value = gtk_adjustment_get_value (adj); gtk_adjustment_set_page_increment (adj, alloc.y - value); g_signal_emit_by_name (chooser->scrolled_window, "scroll-child", GTK_SCROLL_PAGE_FORWARD, FALSE, &dummy); gtk_adjustment_set_page_increment (adj, page_increment); } static void add_emoji (GtkWidget *box, gboolean prepend, GVariant *item, gunichar modifier); #define MAX_RECENT (EMOJI_PER_ROW * 3) static void populate_recent_section (CbEmojiChooser *chooser) { GVariant *variant; GVariant *item; GVariantIter iter; variant = g_settings_get_value (chooser->settings, "recent-emoji"); g_variant_iter_init (&iter, variant); while ((item = g_variant_iter_next_value (&iter))) { GVariant *emoji_data; gunichar modifier; emoji_data = g_variant_get_child_value (item, 0); g_variant_get_child (item, 1, "u", &modifier); add_emoji (chooser->recent.box, FALSE, emoji_data, modifier); g_variant_unref (emoji_data); g_variant_unref (item); } g_variant_unref (variant); } static void add_recent_item (CbEmojiChooser *chooser, GVariant *item, gunichar modifier) { GList *children, *l; int i; GVariantBuilder builder; g_variant_ref (item); g_variant_builder_init (&builder, G_VARIANT_TYPE ("a((auss)u)")); g_variant_builder_add (&builder, "(@(auss)u)", item, modifier); children = gtk_container_get_children (GTK_CONTAINER (chooser->recent.box)); for (l = children, i = 1; l; l = l->next, i++) { GVariant *item2 = g_object_get_data (G_OBJECT (l->data), "emoji-data"); gunichar modifier2 = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (l->data), "modifier")); if (modifier == modifier2 && g_variant_equal (item, item2)) { gtk_widget_destroy (GTK_WIDGET (l->data)); i--; continue; } if (i >= MAX_RECENT) { gtk_widget_destroy (GTK_WIDGET (l->data)); continue; } g_variant_builder_add (&builder, "(@(auss)u)", item2, modifier2); } g_list_free (children); add_emoji (chooser->recent.box, TRUE, item, modifier); g_settings_set_value (chooser->settings, "recent-emoji", g_variant_builder_end (&builder)); g_variant_unref (item); } static void emoji_activated (GtkFlowBox *box, GtkFlowBoxChild *child, gpointer data) { CbEmojiChooser *chooser = data; char *text; GtkWidget *label; GVariant *item; gunichar modifier; label = gtk_bin_get_child (GTK_BIN (child)); text = g_strdup (gtk_label_get_label (GTK_LABEL (label))); item = (GVariant*) g_object_get_data (G_OBJECT (child), "emoji-data"); modifier = (gunichar) GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (child), "modifier")); add_recent_item (chooser, item, modifier); g_signal_emit (data, signals[EMOJI_PICKED], 0, text); g_free (text); } static void add_emoji (GtkWidget *box, gboolean prepend, GVariant *item, gunichar modifier) { GtkWidget *child; GtkWidget *label; PangoAttrList *attrs; GVariant *codes; char text[64]; char *p = text; int i; codes = g_variant_get_child_value (item, 0); for (i = 0; i < g_variant_n_children (codes); i++) { gunichar code; g_variant_get_child (codes, i, "u", &code); if (code == 0) code = modifier; if (code != 0) p += g_unichar_to_utf8 (code, p); } g_variant_unref (codes); p[0] = 0; label = gtk_label_new (text); attrs = pango_attr_list_new (); pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_X_LARGE)); gtk_label_set_attributes (GTK_LABEL (label), attrs); pango_attr_list_unref (attrs); child = gtk_flow_box_child_new (); gtk_style_context_add_class (gtk_widget_get_style_context (child), "emoji"); g_object_set_data_full (G_OBJECT (child), "emoji-data", g_variant_ref (item), (GDestroyNotify)g_variant_unref); if (modifier != 0) g_object_set_data (G_OBJECT (child), "modifier", GUINT_TO_POINTER (modifier)); gtk_container_add (GTK_CONTAINER (child), label); gtk_widget_show_all (child); gtk_flow_box_insert (GTK_FLOW_BOX (box), child, prepend ? 0 : -1); } typedef struct { CbEmojiChooser *chooser; GVariantIter iter; GtkWidget *box; /* We need to keep this around so subsequent emojis get added to the rigth section */ } PopulateData; static gboolean populate_one_emoji (gpointer user_data) { const int N = 4; /* Kinda-sorta sweetspot on my system... */ PopulateData *data = user_data; GVariant *item; const char *name; guint i = 0; while (i < N) { item = g_variant_iter_next_value (&data->iter); if (item == NULL) { data->chooser->populate_idle_id = 0; return G_SOURCE_REMOVE; } g_variant_get_child (item, 1, "&s", &name); if (strcmp (name, data->chooser->body.first) == 0) data->box = data->chooser->body.box; else if (strcmp (name, data->chooser->nature.first) == 0) data->box = data->chooser->nature.box; else if (strcmp (name, data->chooser->food.first) == 0) data->box = data->chooser->food.box; else if (strcmp (name, data->chooser->travel.first) == 0) data->box = data->chooser->travel.box; else if (strcmp (name, data->chooser->activities.first) == 0) data->box = data->chooser->activities.box; else if (strcmp (name, data->chooser->objects.first) == 0) data->box = data->chooser->objects.box; else if (strcmp (name, data->chooser->symbols.first) == 0) data->box = data->chooser->symbols.box; else if (strcmp (name, data->chooser->flags.first) == 0) data->box = data->chooser->flags.box; add_emoji (data->box, FALSE, item, 0); g_variant_unref (item); i ++; } return G_SOURCE_CONTINUE; } static void populate_emoji_chooser (CbEmojiChooser *self) { PopulateData *data = g_malloc0 (sizeof (PopulateData)); data->chooser = self; g_variant_iter_init (&data->iter, self->data); data->box = self->people.box; self->populate_idle_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, populate_one_emoji, data, g_free); } static void adj_value_changed (GtkAdjustment *adj, gpointer data) { CbEmojiChooser *chooser = data; double value = gtk_adjustment_get_value (adj); EmojiSection const *sections[] = { &chooser->recent, &chooser->people, &chooser->body, &chooser->nature, &chooser->food, &chooser->travel, &chooser->activities, &chooser->objects, &chooser->symbols, &chooser->flags, }; EmojiSection const *select_section = sections[0]; gsize i; /* Figure out which section the current scroll position is within */ for (i = 0; i < G_N_ELEMENTS (sections); ++i) { EmojiSection const *section = sections[i]; GtkAllocation alloc; if (section->heading) gtk_widget_get_allocation (section->heading, &alloc); else gtk_widget_get_allocation (section->box, &alloc); if (value < alloc.y) break; select_section = section; } /* Un/Check the section buttons accordingly */ for (i = 0; i < G_N_ELEMENTS (sections); ++i) { EmojiSection const *section = sections[i]; if (section == select_section) gtk_widget_set_state_flags (section->button, GTK_STATE_FLAG_CHECKED, FALSE); else gtk_widget_unset_state_flags (section->button, GTK_STATE_FLAG_CHECKED); } } static gboolean filter_func (GtkFlowBoxChild *child, gpointer data) { EmojiSection *section = data; CbEmojiChooser *chooser; GVariant *emoji_data; const char *text; const char *name; gboolean res; res = TRUE; chooser = CB_EMOJI_CHOOSER (gtk_widget_get_ancestor (GTK_WIDGET (child), CB_TYPE_EMOJI_CHOOSER)); text = gtk_entry_get_text (GTK_ENTRY (chooser->search_entry)); emoji_data = (GVariant *) g_object_get_data (G_OBJECT (child), "emoji-data"); if (text[0] == 0) goto out; if (!emoji_data) goto out; g_variant_get_child (emoji_data, 1, "&s", &name); res = strstr (name, text) != NULL; out: if (res) section->empty = FALSE; return res; } static void invalidate_section (EmojiSection *section) { section->empty = TRUE; gtk_flow_box_invalidate_filter (GTK_FLOW_BOX (section->box)); } static void update_headings (CbEmojiChooser *chooser) { gtk_widget_set_visible (chooser->people.heading, !chooser->people.empty); gtk_widget_set_visible (chooser->people.box, !chooser->people.empty); gtk_widget_set_visible (chooser->body.heading, !chooser->body.empty); gtk_widget_set_visible (chooser->body.box, !chooser->body.empty); gtk_widget_set_visible (chooser->nature.heading, !chooser->nature.empty); gtk_widget_set_visible (chooser->nature.box, !chooser->nature.empty); gtk_widget_set_visible (chooser->food.heading, !chooser->food.empty); gtk_widget_set_visible (chooser->food.box, !chooser->food.empty); gtk_widget_set_visible (chooser->travel.heading, !chooser->travel.empty); gtk_widget_set_visible (chooser->travel.box, !chooser->travel.empty); gtk_widget_set_visible (chooser->activities.heading, !chooser->activities.empty); gtk_widget_set_visible (chooser->activities.box, !chooser->activities.empty); gtk_widget_set_visible (chooser->objects.heading, !chooser->objects.empty); gtk_widget_set_visible (chooser->objects.box, !chooser->objects.empty); gtk_widget_set_visible (chooser->symbols.heading, !chooser->symbols.empty); gtk_widget_set_visible (chooser->symbols.box, !chooser->symbols.empty); gtk_widget_set_visible (chooser->flags.heading, !chooser->flags.empty); gtk_widget_set_visible (chooser->flags.box, !chooser->flags.empty); if (chooser->recent.empty && chooser->people.empty && chooser->body.empty && chooser->nature.empty && chooser->food.empty && chooser->travel.empty && chooser->activities.empty && chooser->objects.empty && chooser->symbols.empty && chooser->flags.empty) gtk_stack_set_visible_child_name (GTK_STACK (chooser->stack), "empty"); else gtk_stack_set_visible_child_name (GTK_STACK (chooser->stack), "list"); } static void search_changed (GtkEntry *entry, gpointer data) { CbEmojiChooser *chooser = data; invalidate_section (&chooser->recent); invalidate_section (&chooser->people); invalidate_section (&chooser->body); invalidate_section (&chooser->nature); invalidate_section (&chooser->food); invalidate_section (&chooser->travel); invalidate_section (&chooser->activities); invalidate_section (&chooser->objects); invalidate_section (&chooser->symbols); invalidate_section (&chooser->flags); update_headings (chooser); } static void setup_section (CbEmojiChooser *chooser, EmojiSection *section, const char *first, gunichar label) { char text[14]; char *p; GtkAdjustment *adj; section->first = first; p = text; p += g_unichar_to_utf8 (label, p); p += g_unichar_to_utf8 (0xfe0e, p); p[0] = 0; gtk_button_set_label (GTK_BUTTON (section->button), text); adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (chooser->scrolled_window)); gtk_container_set_focus_vadjustment (GTK_CONTAINER (section->box), adj); gtk_flow_box_set_filter_func (GTK_FLOW_BOX (section->box), filter_func, section, NULL); g_signal_connect (section->button, "clicked", G_CALLBACK (scroll_to_section), section); } static void cb_emoji_chooser_finalize (GObject *object) { CbEmojiChooser *self = CB_EMOJI_CHOOSER (object); if (self->data != NULL) g_variant_unref (self->data); g_clear_object (&self->settings); if (self->populate_idle_id != 0) g_source_remove (self->populate_idle_id); G_OBJECT_CLASS (cb_emoji_chooser_parent_class)->finalize (object); } static void cb_emoji_chooser_init (CbEmojiChooser *self) { GtkAdjustment *adj; gtk_widget_init_template (GTK_WIDGET (self)); adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (self->scrolled_window)); g_signal_connect (adj, "value-changed", G_CALLBACK (adj_value_changed), self); setup_section (self, &self->recent, NULL, 0x1f557); setup_section (self, &self->people, "grinning face", 0x1f642); setup_section (self, &self->body, "selfie", 0x1f44d); setup_section (self, &self->nature, "monkey face", 0x1f337); setup_section (self, &self->food, "grapes", 0x1f374); setup_section (self, &self->travel, "globe showing Europe-Africa", 0x2708); setup_section (self, &self->activities, "jack-o-lantern", 0x1f3c3); setup_section (self, &self->objects, "muted speaker", 0x1f514); setup_section (self, &self->symbols, "ATM sign", 0x2764); setup_section (self, &self->flags, "chequered flag", 0x1f3f4); /* We scroll to the top on show, so check the right button for the 1st time */ gtk_widget_set_state_flags (self->recent.button, GTK_STATE_FLAG_CHECKED, FALSE); gtk_widget_show_all (GTK_WIDGET (self)); } static void cb_emoji_chooser_class_init (CbEmojiChooserClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = cb_emoji_chooser_finalize; signals[EMOJI_PICKED] = g_signal_new ("emoji-picked", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_STRING|G_SIGNAL_TYPE_STATIC_SCOPE); gtk_widget_class_set_template_from_resource (widget_class, "/org/baedert/corebird/ui/cb-emoji-chooser.ui"); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, search_entry); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, stack); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, scrolled_window); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, recent.box); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, recent.button); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, people.box); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, people.heading); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, people.button); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, body.box); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, body.heading); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, body.button); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, nature.box); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, nature.heading); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, nature.button); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, food.box); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, food.heading); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, food.button); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, travel.box); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, travel.heading); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, travel.button); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, activities.box); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, activities.heading); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, activities.button); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, objects.box); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, objects.heading); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, objects.button); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, symbols.box); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, symbols.heading); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, symbols.button); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, flags.box); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, flags.heading); gtk_widget_class_bind_template_child (widget_class, CbEmojiChooser, flags.button); gtk_widget_class_bind_template_callback (widget_class, emoji_activated); gtk_widget_class_bind_template_callback (widget_class, search_changed); } GtkWidget * cb_emoji_chooser_new (void) { return GTK_WIDGET (g_object_new (CB_TYPE_EMOJI_CHOOSER, NULL)); } void cb_emoji_chooser_populate (CbEmojiChooser *self) { if (self->populated) return; self->populated = TRUE; populate_emoji_chooser (self); } gboolean cb_emoji_chooser_try_init (CbEmojiChooser *self) { GBytes *bytes; char *checksum; GVariant *settings_test; gboolean recent_in_correct_format = FALSE; gboolean correct_checksum = FALSE; GSettingsSchemaSource *schema_source; GSettingsSchema *schema; schema_source = g_settings_schema_source_get_default (); schema = g_settings_schema_source_lookup (schema_source, "org.gtk.Settings.EmojiChooser", TRUE); if (schema == NULL) { g_message ("Emoji chooser: Schema not found"); return FALSE; } g_settings_schema_unref (schema); schema = NULL; self->settings = g_settings_new ("org.gtk.Settings.EmojiChooser"); settings_test = g_settings_get_value (self->settings, "recent-emoji"); recent_in_correct_format = g_variant_is_of_type (settings_test, G_VARIANT_TYPE ("a((auss)u)")); g_variant_unref (settings_test); if (!recent_in_correct_format) { g_message ("Emoji chooser: Recent variant in wrong format"); return FALSE; } bytes = g_resources_lookup_data ("/org/gtk/libgtk/emoji/emoji.data", 0, NULL); if (bytes == NULL) { g_message ("Emoji chooser: resources not available"); return FALSE; } checksum = g_compute_checksum_for_bytes (G_CHECKSUM_SHA1, bytes); correct_checksum = strcmp (checksum, EMOJI_DATA_CHECKSUM) == 0; if (!correct_checksum) { g_message ("Emoji chooser: checksum mismatch. %s != %s", checksum, EMOJI_DATA_CHECKSUM); g_free (checksum); g_bytes_unref (bytes); return FALSE; } g_free (checksum); self->data = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE ("a(auss)"), bytes, TRUE)); g_bytes_unref (bytes); populate_recent_section (self); return TRUE; } corebird-1.7.4/src/CbEmojiChooser.h000066400000000000000000000034271324604713000171150ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef __CB_EMOJI_CHOOSER_H__ #define __CB_EMOJI_CHOOSER_H__ #include typedef struct { GtkWidget *box; GtkWidget *heading; GtkWidget *button; const char *first; gunichar label; gboolean empty; } EmojiSection; struct _CbEmojiChooser { GtkBox parent_instance; guint populated : 1; guint populate_idle_id; GtkWidget *search_entry; GtkWidget *stack; GtkWidget *scrolled_window; EmojiSection recent; EmojiSection people; EmojiSection body; EmojiSection nature; EmojiSection food; EmojiSection travel; EmojiSection activities; EmojiSection objects; EmojiSection symbols; EmojiSection flags; GVariant *data; GSettings *settings; }; typedef struct _CbEmojiChooser CbEmojiChooser; #define CB_TYPE_EMOJI_CHOOSER cb_emoji_chooser_get_type () G_DECLARE_FINAL_TYPE (CbEmojiChooser, cb_emoji_chooser, CB, EMOJI_CHOOSER, GtkBox); GtkWidget * cb_emoji_chooser_new (void); gboolean cb_emoji_chooser_try_init (CbEmojiChooser *self); void cb_emoji_chooser_populate (CbEmojiChooser *self); #endif corebird-1.7.4/src/CbFilter.c000066400000000000000000000050751324604713000157500ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbFilter.h" G_DEFINE_TYPE (CbFilter, cb_filter, G_TYPE_OBJECT); static void cb_filter_finalize (GObject *obj) { CbFilter *filter = CB_FILTER (obj); g_free (filter->contents); if (filter->regex != NULL) g_regex_unref (filter->regex); G_OBJECT_CLASS (cb_filter_parent_class)->finalize (obj); } static void cb_filter_init (CbFilter *filter) { filter->contents = NULL; filter->regex = NULL; } static void cb_filter_class_init (CbFilterClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = cb_filter_finalize; } CbFilter * cb_filter_new (const char *expr) { CbFilter *filter = CB_FILTER (g_object_new (CB_TYPE_FILTER, NULL)); cb_filter_reset (filter, expr); return filter; } void cb_filter_reset (CbFilter *filter, const char *expr) { g_return_if_fail (CB_IS_FILTER (filter)); g_return_if_fail (expr != NULL); filter->regex = g_regex_new (expr, G_REGEX_CASELESS, 0, /* No match flags */ NULL); filter->contents = g_strdup (expr); } gboolean cb_filter_matches (CbFilter *filter, const char *text) { g_return_val_if_fail (CB_IS_FILTER (filter), FALSE); g_return_val_if_fail (text != NULL, FALSE); if (filter->regex == NULL) return FALSE; return g_regex_match (filter->regex, text, 0, NULL); } const char * cb_filter_get_contents (CbFilter *filter) { g_return_val_if_fail (CB_IS_FILTER (filter), ""); return filter->contents; } int cb_filter_get_id (CbFilter *filter) { g_return_val_if_fail (CB_IS_FILTER (filter), 0); return filter->id; } void cb_filter_set_id (CbFilter *filter, int id) { g_return_if_fail (CB_IS_FILTER (filter)); g_return_if_fail (id >= 0); filter->id = id; } corebird-1.7.4/src/CbFilter.h000066400000000000000000000027741324604713000157600ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef FILTER_H #define FILTER_H #include G_BEGIN_DECLS typedef struct _CbFilter CbFilter; struct _CbFilter { GObject parent_instance; int id; char *contents; GRegex *regex; }; #define CB_TYPE_FILTER cb_filter_get_type () G_DECLARE_FINAL_TYPE (CbFilter, cb_filter, CB, FILTER, GObject); GType cb_filter_get_type (void) G_GNUC_CONST; CbFilter *cb_filter_new (const char *expr); void cb_filter_reset (CbFilter *filter, const char *expr); gboolean cb_filter_matches (CbFilter *filter, const char *text); const char *cb_filter_get_contents (CbFilter *filter); int cb_filter_get_id (CbFilter *filter); void cb_filter_set_id (CbFilter *filter, int id); G_END_DECLS #endif corebird-1.7.4/src/CbGtkCompat.h000066400000000000000000000040151324604713000164120ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef CB_GTK_COMPAT_H #define CB_GTK_COMPAT_H #include #if GTK_CHECK_VERSION(3, 9, 1) void gtk_widget_measure (GtkWidget *widget, GtkOrientation orientation, int for_size, int *minimum, int *natural, int *minimum_baseline, int *natural_baseline) { if (orientation == GTK_ORIENTATION_HORIZONTAL) { g_assert (minimum_baseline == NULL); g_assert (natural_baseline == NULL); if (for_size == -1) gtk_widget_get_preferred_width (widget, minimum, natural); else gtk_widget_get_preferred_width_for_height (widget, for_size, minimum, natural); } else /* VERTICAL */ { gtk_widget_get_preferred_height_and_baseline_for_width (widget, for_size, minimum, natural, minimum_baseline, natural_baseline); } } #endif #endif corebird-1.7.4/src/CbMedia.c000066400000000000000000000064171324604713000155430ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2016 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbMedia.h" G_DEFINE_TYPE (CbMedia, cb_media, G_TYPE_OBJECT); enum { PROGRESS, LAST_SIGNAL }; static guint media_signals[LAST_SIGNAL] = { 0 }; static void cb_media_finalize (GObject *object) { CbMedia *media = CB_MEDIA (object); cairo_surface_destroy (media->surface); g_free (media->thumb_url); g_free (media->target_url); g_free (media->url); if (media->animation) g_object_unref (media->animation); G_OBJECT_CLASS (cb_media_parent_class)->finalize (object); } static void cb_media_class_init (CbMediaClass *class) { GObjectClass *gobject_class = G_OBJECT_CLASS (class); gobject_class->finalize = cb_media_finalize; media_signals[PROGRESS] = g_signal_new ("progress", G_OBJECT_CLASS_TYPE (gobject_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } static void cb_media_init (CbMedia *media) { media->surface = NULL; media->animation = NULL; media->loaded = FALSE; media->invalid = FALSE; media->surface = NULL; media->url = NULL; media->percent_loaded = 0; media->width = -1; media->height = -1; } CbMedia * cb_media_new (void) { return CB_MEDIA (g_object_new (CB_TYPE_MEDIA, NULL)); } gboolean cb_media_is_video (CbMedia *media) { switch (media->type) { case CB_MEDIA_TYPE_ANIMATED_GIF: case CB_MEDIA_TYPE_TWITTER_VIDEO: case CB_MEDIA_TYPE_INSTAGRAM_VIDEO: return TRUE; default: return FALSE; } return FALSE; } static gboolean emit_media_progress (gpointer data) { CbMedia *media = data; g_return_val_if_fail (CB_IS_MEDIA (media), G_SOURCE_REMOVE); g_signal_emit (data, media_signals[PROGRESS], 0); return G_SOURCE_REMOVE; } void cb_media_update_progress (CbMedia *media, double progress) { g_return_if_fail (CB_IS_MEDIA (media)); g_return_if_fail (progress >= 0); media->percent_loaded = progress; g_main_context_invoke (NULL, emit_media_progress, media); } void cb_media_loading_finished (CbMedia *media) { g_return_if_fail (CB_IS_MEDIA (media)); media->loaded = TRUE; cb_media_update_progress (media, 1.0); } CbMediaType cb_media_type_from_url (const char *url) { if (g_str_has_suffix (url, "/photo/1")) return CB_MEDIA_TYPE_ANIMATED_GIF; if (g_str_has_suffix (url, ".gif")) return CB_MEDIA_TYPE_GIF; return CB_MEDIA_TYPE_IMAGE; } corebird-1.7.4/src/CbMedia.h000066400000000000000000000034731324604713000155470ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2016 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef MEDIA_H #define MEDIA_H #include #include #include G_BEGIN_DECLS typedef enum { CB_MEDIA_TYPE_IMAGE, CB_MEDIA_TYPE_GIF, CB_MEDIA_TYPE_ANIMATED_GIF, CB_MEDIA_TYPE_TWITTER_VIDEO, CB_MEDIA_TYPE_INSTAGRAM_VIDEO, CB_MEDIA_TYPE_UNKNOWN } CbMediaType; struct _CbMedia { GObject parent_instance; char *url; char *thumb_url; char *target_url; int width; int height; CbMediaType type; guint loaded : 1; guint invalid : 1; double percent_loaded; cairo_surface_t *surface; GdkPixbufAnimation *animation; }; typedef struct _CbMedia CbMedia; #define CB_TYPE_MEDIA cb_media_get_type () G_DECLARE_FINAL_TYPE (CbMedia, cb_media, CB, MEDIA, GObject); CbMedia * cb_media_new (void); gboolean cb_media_is_video (CbMedia *media); void cb_media_loading_finished (CbMedia *media); void cb_media_update_progress (CbMedia *media, double progress); CbMediaType cb_media_type_from_url (const char *url); G_END_DECLS #endif corebird-1.7.4/src/CbMediaDownloader.c000066400000000000000000000341711324604713000175600ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2016 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbMediaDownloader.h" #include #include #include G_DEFINE_TYPE (CbMediaDownloader, cb_media_downloader, G_TYPE_OBJECT); typedef struct { CbMedia *media; SoupSession *soup_session; } LoadingData; static void loading_data_free (LoadingData *data) { g_object_unref (data->media); g_object_unref (data->soup_session); g_free (data); } CbMediaDownloader * cb_media_downloader_get_default (void) { static CbMediaDownloader *d = NULL; if (G_UNLIKELY (d == NULL)) { d = CB_MEDIA_DOWNLOADER (g_object_new (CB_TYPE_MEDIA_DOWNLOADER, NULL)); } return d; } static void mark_invalid (CbMedia *media) { media->invalid = TRUE; media->loaded = TRUE; cb_media_loading_finished (media); } static const char * canonicalize_url (const char *url) { int ret = 0; if (g_str_has_prefix (url,"http://")) ret += 7; else if (g_str_has_prefix (url, "https://")) ret += 8; if (g_str_has_prefix(url + ret, "www.")) ret += 4; return url + ret; } static void load_animation (GInputStream *input_stream, CbMedia *media, GCancellable *cancellable) { GdkPixbufAnimation *animation; GdkPixbuf *frame; GError *error = NULL; cairo_surface_t *surface; cairo_t *ct; gboolean has_alpha; animation = gdk_pixbuf_animation_new_from_stream (input_stream, NULL, &error); if (error) { g_warning ("Couldn't load pixbuf: %s (%s)", error->message, media->url); mark_invalid (media); g_error_free (error); return; } frame = gdk_pixbuf_animation_get_static_image (animation); if (g_cancellable_is_cancelled (cancellable)) { g_object_unref (animation); return; } if (!gdk_pixbuf_animation_is_static_image (animation)) media->animation = animation; /* Takes ref */ else media->animation = NULL; has_alpha = gdk_pixbuf_get_has_alpha (frame); surface = cairo_image_surface_create (has_alpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, gdk_pixbuf_get_width (frame), gdk_pixbuf_get_height (frame)); ct = cairo_create (surface); gdk_cairo_set_source_pixbuf (ct, frame, 0.0, 0.0); cairo_paint (ct); cairo_destroy (ct); media->surface = surface; if (media->surface == NULL) { g_warning ("Surface of %p is null", media); mark_invalid (media); goto out; } media->width = gdk_pixbuf_get_width (frame); media->height = gdk_pixbuf_get_height (frame); media->loaded = TRUE; media->invalid = FALSE; out: if (media->animation == NULL) g_object_unref (animation); cb_media_loading_finished (media); } static void cb_media_downloader_get_instagram_url (CbMediaDownloader *downloader, LoadingData *task_data) { CbMedia *media = task_data->media; SoupMessage *msg = soup_message_new ("GET", media->url); GRegex *medium_regex; GRegex *url_regex; GMatchInfo *match_info; soup_session_send_message (task_data->soup_session, msg); if (msg->status_code != SOUP_STATUS_OK) { g_object_unref (msg); media->url = NULL; return; } medium_regex = g_regex_new ("", 0, 0, NULL); g_regex_match (medium_regex, (const char *)msg->response_body->data, 0, &match_info); if (g_match_info_get_match_count (match_info) > 0) { g_match_info_free (match_info); /* Video! */ url_regex = g_regex_new ("response_body->data, 0, &match_info); media->url = g_match_info_fetch (match_info, 1); g_regex_unref (url_regex); media->type = CB_MEDIA_TYPE_INSTAGRAM_VIDEO; } g_match_info_free (match_info); url_regex = g_regex_new ("response_body->data, 0, &match_info); media->thumb_url = g_match_info_fetch (match_info, 1); g_free (media->target_url); media->target_url = g_strdup (media->thumb_url); g_regex_unref (url_regex); g_regex_unref (medium_regex); g_match_info_free (match_info); g_object_unref (msg); } static void cb_media_downloader_load_twitter_video (CbMediaDownloader *downloader, LoadingData *task_data) { CbMedia *media = task_data->media; SoupMessage *msg = soup_message_new ("GET", media->url); GRegex *regex; GMatchInfo *match_info; soup_session_send_message (task_data->soup_session, msg); if (msg->status_code != SOUP_STATUS_OK) { mark_invalid (media); g_object_unref (msg); return; } regex = g_regex_new ("response_body->data, 0, &match_info); if (g_match_info_get_match_count (match_info) > 0) { g_assert (media->type == CB_MEDIA_TYPE_ANIMATED_GIF); media->url = g_match_info_fetch (match_info, 1); g_regex_unref (regex); g_match_info_free (match_info); g_object_unref (msg); return; } else { g_regex_unref (regex); g_match_info_free (match_info); regex = g_regex_new ("response_body->data, 0, &match_info); media->url = g_match_info_fetch (match_info, 1); media->type = CB_MEDIA_TYPE_TWITTER_VIDEO; } g_regex_unref (regex); g_match_info_free (match_info); regex = g_regex_new ("poster=\"(.*?)\"", 0, 0, NULL); g_regex_match (regex, (const char *)msg->response_body->data, 0, &match_info); media->thumb_url = g_match_info_fetch (match_info, 1); g_regex_unref (regex); g_match_info_free (match_info); g_object_unref (msg); } static void cb_media_downloader_load_real_url (CbMediaDownloader *downloader, LoadingData *task_data, const char *regex_str1, int match_index1) { CbMedia *media = task_data->media; SoupMessage *msg = soup_message_new ("GET", media->url); GRegex *regex; GMatchInfo *match_info; soup_session_send_message (task_data->soup_session, msg); if (msg->status_code != SOUP_STATUS_OK) { /* Will mark it invalid later */ media->url = NULL; g_object_unref (msg); return; } regex = g_regex_new (regex_str1, 0, 0, NULL); g_regex_match (regex, (const char *)msg->response_body->data, 0, &match_info); media->thumb_url = g_match_info_fetch (match_info, match_index1); g_regex_unref (regex); g_match_info_free (match_info); g_object_unref (msg); } static void update_media_progress (SoupMessage *msg, SoupBuffer *chunk, gpointer user_data) { CbMedia *media = user_data; if (msg->response_headers == NULL) return; double chunk_percent = chunk->length / (double)soup_message_headers_get_content_length (msg->response_headers); cb_media_update_progress (media, media->percent_loaded + chunk_percent); } static void cb_media_downloader_load_threaded (CbMediaDownloader *downloader, LoadingData *task_data, GCancellable *cancellable) { const char *url; SoupMessage *msg; GInputStream *input_stream; CbMedia *media; g_return_if_fail (CB_IS_MEDIA_DOWNLOADER (downloader)); media = task_data->media; url = canonicalize_url (media->url); if (g_cancellable_is_cancelled (cancellable)) return; /* For these, we first need to download some html and get the real URL of the image we want to display */ if (g_str_has_prefix (url, "instagr.am") || g_str_has_prefix (url, "instagram.com/p/")) { cb_media_downloader_get_instagram_url (downloader, task_data); } else if (g_str_has_prefix (url, "ow.ly/i/") || g_str_has_prefix (url, "flickr.com/photos/") || g_str_has_prefix (url, "flic.kr/p/") || g_str_has_prefix (url, "flic.kr/s/")) { cb_media_downloader_load_real_url (downloader, task_data, "url == NULL) { g_warning ("Media is invalid. (url %s)", url); mark_invalid (media); return; } if (g_cancellable_is_cancelled (cancellable)) return; msg = soup_message_new ("GET", media->thumb_url ? media->thumb_url : media->url); if (msg == NULL) { mark_invalid (media); g_warning ("soup_message_new failed for URI '%s'", media->thumb_url ? media->thumb_url : media->url); return; } g_signal_connect (msg, "got-chunk", G_CALLBACK (update_media_progress), media); soup_session_send_message (task_data->soup_session, msg); if (msg->status_code != SOUP_STATUS_OK) { g_debug ("Request on '%s' returned status '%s'", media->thumb_url ? media->thumb_url : media->url, soup_status_get_phrase (msg->status_code)); mark_invalid (media); g_object_unref (msg); return; } if (g_cancellable_is_cancelled (cancellable)) return; input_stream = g_memory_input_stream_new_from_data (msg->response_body->data, msg->response_body->length, NULL); load_animation (input_stream, media, cancellable); g_input_stream_close (input_stream, NULL, NULL); g_object_unref (input_stream); g_object_unref (msg); } void load_in_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { CbMediaDownloader *downloader = source_object; LoadingData *data = task_data; cb_media_downloader_load_threaded (downloader, data, cancellable); g_task_return_boolean (task, TRUE); g_object_unref (task); } void cb_media_downloader_load_async (CbMediaDownloader *downloader, CbMedia *media, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; LoadingData *data; g_return_if_fail (CB_IS_MEDIA_DOWNLOADER (downloader)); g_return_if_fail (CB_IS_MEDIA (media)); g_return_if_fail (!media->loaded); g_return_if_fail (media->surface == NULL); task = g_task_new (downloader, downloader->cancellable, callback, user_data); data = g_new0 (LoadingData, 1); data->media = g_object_ref (media); data->soup_session = soup_session_new (); g_task_set_task_data (task, data, (GDestroyNotify)loading_data_free); g_task_run_in_thread (task, load_in_thread); } gboolean cb_media_downloader_load_finish (CbMediaDownloader *downloader, GAsyncResult *result, GError **error) { g_return_val_if_fail (g_task_is_valid (result, downloader), FALSE); return g_task_propagate_boolean (G_TASK (result), error); } void cb_media_downloader_load_all (CbMediaDownloader *downloader, CbMiniTweet *t) { guint i; g_return_if_fail (CB_IS_MEDIA_DOWNLOADER (downloader)); if (downloader->disabled) return; for (i = 0; i < t->n_medias; i ++) cb_media_downloader_load_async (downloader, t->medias[i], NULL, NULL); } void cb_media_downloader_disable (CbMediaDownloader *downloader) { g_return_if_fail (CB_IS_MEDIA_DOWNLOADER (downloader)); downloader->disabled = TRUE; } void cb_media_downloader_shutdown (CbMediaDownloader *downloader) { g_debug ("MediaDownloader shutdown"); g_cancellable_cancel (downloader->cancellable); g_object_unref (downloader->cancellable); // XXX OK? g_object_unref (downloader); } gboolean is_media_candidate (const char *url) { url = canonicalize_url (url); return g_str_has_prefix (url, "instagr.am") || g_str_has_prefix (url, "instagram.com/p/") || (g_str_has_prefix (url, "i.imgur.com") && !g_str_has_suffix (url, "gifv")) || g_str_has_prefix (url, "d.pr/i/") || g_str_has_prefix (url, "ow.ly/i/") || g_str_has_prefix (url, "flickr.com/photos/") || g_str_has_prefix (url, "flic.kr/p/") || g_str_has_prefix (url, "flic.kr/s/") || #ifdef VIDEO g_str_has_suffix (url, "/photo/1/") || g_str_has_prefix (url, "video.twimg.com/ext_tw_video") || #endif g_str_has_prefix (url, "pbs.twimg.com/media/") || g_str_has_prefix (url, "twitpic.com") ; } static void cb_media_downloader_init (CbMediaDownloader *downloader) { downloader->disabled = FALSE; downloader->cancellable = g_cancellable_new (); } static void cb_media_downloader_class_init (CbMediaDownloaderClass *class) { } corebird-1.7.4/src/CbMediaDownloader.h000066400000000000000000000044451324604713000175660ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2016 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef _CB_INLINE_MEDIA_DOWNLOADER_H_ #define _CB_INLINE_MEDIA_DOWNLOADER_H_ #include #include #include "CbMedia.h" #include "CbTypes.h" G_BEGIN_DECLS struct _CbMediaDownloader { GObject parent_instance; GCancellable *cancellable; guint disabled : 1; }; typedef struct _CbMediaDownloader CbMediaDownloader; #define CB_TYPE_MEDIA_DOWNLOADER (cb_media_downloader_get_type ()) G_DECLARE_FINAL_TYPE (CbMediaDownloader, cb_media_downloader, CB, MEDIA_DOWNLOADER, GObject); CbMediaDownloader * cb_media_downloader_get_default (void); void cb_media_downloader_load_all (CbMediaDownloader *downloader, CbMiniTweet *t); void cb_media_downloader_load_async (CbMediaDownloader *downloader, CbMedia *media, GAsyncReadyCallback callback, gpointer user_data); gboolean cb_media_downloader_load_finish (CbMediaDownloader *downloader, GAsyncResult *result, GError **error); void cb_media_downloader_disable (CbMediaDownloader *downloader); void cb_media_downloader_shutdown (CbMediaDownloader *downloader); gboolean is_media_candidate (const char *url); G_END_DECLS #endif corebird-1.7.4/src/CbMediaImageWidget.c000066400000000000000000000146501324604713000176500ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbMediaImageWidget.h" G_DEFINE_TYPE (CbMediaImageWidget, cb_media_image_widget, GTK_TYPE_SCROLLED_WINDOW) static void cb_media_image_widget_finalize (GObject *object) { CbMediaImageWidget *self = CB_MEDIA_IMAGE_WIDGET (object); g_clear_object (&self->drag_gesture); G_OBJECT_CLASS (cb_media_image_widget_parent_class)->finalize (object); } static void drag_begin_cb (GtkGestureDrag *gesture, double start_x, double start_y, gpointer user_data) { CbMediaImageWidget *self = user_data; GtkAdjustment *adjustment; adjustment = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (self)); self->drag_start_hvalue = gtk_adjustment_get_value (adjustment); adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (self)); self->drag_start_vvalue = gtk_adjustment_get_value (adjustment); gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); } static void drag_update_cb (GtkGestureDrag *gesture, double offset_x, double offset_y, gpointer user_data) { CbMediaImageWidget *self = user_data; GtkAdjustment *adjustment; adjustment = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (self)); gtk_adjustment_set_value (adjustment, self->drag_start_hvalue - offset_x); adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (self)); gtk_adjustment_set_value (adjustment, self->drag_start_vvalue - offset_y); gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED); } static void cb_media_image_widget_class_init (CbMediaImageWidgetClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = cb_media_image_widget_finalize; } static void cb_media_image_widget_init (CbMediaImageWidget *self) { self->image = gtk_image_new (); gtk_container_add (GTK_CONTAINER (self), self->image); self->initial_scroll_x = 0.5; self->initial_scroll_y = 0.5; self->drag_gesture = gtk_gesture_drag_new (GTK_WIDGET (self)); gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (self->drag_gesture), GDK_BUTTON_MIDDLE); gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (self->drag_gesture), GTK_PHASE_CAPTURE); g_signal_connect (self->drag_gesture, "drag-begin", G_CALLBACK (drag_begin_cb), self); g_signal_connect (self->drag_gesture, "drag-update", G_CALLBACK (drag_update_cb), self); } GtkWidget * cb_media_image_widget_new (CbMedia *media) { CbMediaImageWidget *self; int img_width; int img_height; int win_width; int win_height; g_return_val_if_fail (CB_IS_MEDIA (media), NULL); g_return_val_if_fail (!media->invalid, NULL); g_return_val_if_fail (media->surface != NULL, NULL); self = CB_MEDIA_IMAGE_WIDGET (g_object_new (CB_TYPE_MEDIA_IMAGE_WIDGET, NULL)); if (media->type == CB_MEDIA_TYPE_GIF) gtk_image_set_from_animation (GTK_IMAGE (self->image), media->animation); else gtk_image_set_from_surface (GTK_IMAGE (self->image), media->surface); img_width = cairo_image_surface_get_width (media->surface); img_height = cairo_image_surface_get_height (media->surface); win_width = 800; win_height = 600; /* TODO: Replace the GdkScreen usage here */ if (img_width <= gdk_screen_get_width (gdk_screen_get_default ()) * 0.9) { win_width = img_width; g_object_set (self, "hscrollbar-policy", GTK_POLICY_NEVER, NULL); } if (img_height <= gdk_screen_get_height (gdk_screen_get_default ()) * 0.9) { win_height = img_height; g_object_set (self, "vscrollbar-policy", GTK_POLICY_NEVER, NULL); } gtk_widget_set_size_request (GTK_WIDGET (self), win_width, win_height); return GTK_WIDGET (self); } static void hadjustment_changed_cb (GtkAdjustment *adjustment, gpointer user_data) { CbMediaImageWidget *self = user_data; double upper; double new_value; upper = gtk_adjustment_get_upper (adjustment); new_value = upper * self->initial_scroll_x - (gtk_adjustment_get_page_size (adjustment) / 2.0); gtk_adjustment_set_value (adjustment, new_value); g_signal_handler_disconnect (adjustment, self->hadj_changed_id); } static void vadjustment_changed_cb (GtkAdjustment *adjustment, gpointer user_data) { CbMediaImageWidget *self = user_data; double upper; double new_value; upper = gtk_adjustment_get_upper (adjustment); new_value = upper * self->initial_scroll_y - (gtk_adjustment_get_page_size (adjustment) / 2.0); gtk_adjustment_set_value (adjustment, new_value); g_signal_handler_disconnect (adjustment, self->vadj_changed_id); } void cb_media_image_widget_scroll_to (CbMediaImageWidget *self, double px, double py) { GtkAdjustment *hadj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (self)); GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (self)); self->initial_scroll_x = px; self->initial_scroll_y = py; /* Defer the scrolling to a point where the adjustment actually has values */ self->hadj_changed_id = g_signal_connect (G_OBJECT (hadj), "changed", G_CALLBACK (hadjustment_changed_cb), self); self->vadj_changed_id = g_signal_connect (G_OBJECT (vadj), "changed", G_CALLBACK (vadjustment_changed_cb), self); } corebird-1.7.4/src/CbMediaImageWidget.h000066400000000000000000000031751324604713000176550ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef _CB_MEDIA_IMAGE_WIDGET_H_ #define _CB_MEDIA_IMAGE_WIDGET_H_ #include #include "CbMedia.h" #define CB_TYPE_MEDIA_IMAGE_WIDGET cb_media_image_widget_get_type () G_DECLARE_FINAL_TYPE (CbMediaImageWidget, cb_media_image_widget, CB, MEDIA_IMAGE_WIDGET, GtkScrolledWindow); struct _CbMediaImageWidget { GtkScrolledWindow parent_instance; GtkWidget *image; GtkGesture *drag_gesture; double drag_start_hvalue; double drag_start_vvalue; double initial_scroll_x; double initial_scroll_y; gulong hadj_changed_id; gulong vadj_changed_id; }; typedef struct _CbMediaImageWidget CbMediaImageWidget; GtkWidget * cb_media_image_widget_new (CbMedia *media); void cb_media_image_widget_scroll_to (CbMediaImageWidget *self, double px, double py); #endif corebird-1.7.4/src/CbMediaVideoWidget.c000066400000000000000000000266011324604713000176730ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbMediaVideoWidget.h" #include "CbGtkCompat.h" G_DEFINE_TYPE(CbMediaVideoWidget, cb_media_video_widget, GTK_TYPE_STACK) static void cb_media_video_widget_show_error (CbMediaVideoWidget *self, const char *error_message) { gtk_stack_set_visible_child (GTK_STACK (self), self->error_label); gtk_label_set_label (GTK_LABEL (self->error_label), error_message); } static void cb_media_video_widget_start_video (CbMediaVideoWidget *self) { #ifdef VIDEO g_assert (self->media_url != NULL); g_object_set (self->src, "uri", self->media_url, NULL); /* Will set to PLAYING once we get the ASYNC_DONE message */ gst_element_set_state (self->src, GST_STATE_PAUSED); #endif } static void cb_media_video_widget_stop_video (CbMediaVideoWidget *self) { #ifdef VIDEO gst_element_set_state (self->src, GST_STATE_NULL); #endif if (self->video_progress_id != 0) { g_source_remove (self->video_progress_id); self->video_progress_id = 0; } g_cancellable_cancel (self->cancellable); } static void soup_message_received_cb (SoupSession *session, SoupMessage *message, gpointer user_data) { CbMediaVideoWidget *self = user_data; GRegex *regex; GMatchInfo *match_info; char *match = NULL; if (message->status_code != SOUP_STATUS_OK) { if (message->status_code != SOUP_STATUS_CANCELLED) { char *msg = g_strdup_printf ("%u %s", message->status_code, soup_status_get_phrase (message->status_code)); cb_media_video_widget_show_error (self, msg); g_free (msg); } return; } regex = g_regex_new ("response_body->data, 0, &match_info); match = g_match_info_fetch (match_info, 1); g_debug ("Real url: %s", match); if (match == NULL) { cb_media_video_widget_show_error (self, "Error: Could not get real URL"); goto out; } else { self->media_url = match; cb_media_video_widget_start_video (self); } out: g_regex_unref (regex); } static void cancelled_cb (GCancellable *cancellable, gpointer user_data) { CbMediaVideoWidget *self = user_data; if (self->session != NULL && self->message != NULL) { soup_session_cancel_message (self->session, self->message, SOUP_STATUS_CANCELLED); } } static void fetch_real_url (CbMediaVideoWidget *self, const char *first_url) { self->session = soup_session_new (); self->message = soup_message_new ("GET", first_url); g_signal_connect (self->cancellable, "cancelled", G_CALLBACK (cancelled_cb), self); soup_session_queue_message (self->session, self->message, soup_message_received_cb, self); } static gboolean video_progress_timeout_cb (gpointer user_data) { CbMediaVideoWidget *self = user_data; gint64 duration_ns; gint64 position_ns; #ifdef VIDEO gst_element_query_duration (self->src, GST_FORMAT_TIME, &duration_ns); if (duration_ns > 0) { double fraction; gst_element_query_position (self->src, GST_FORMAT_TIME, &position_ns); fraction = (double)position_ns / (double)duration_ns; gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (self->video_progress), fraction); } #endif return G_SOURCE_CONTINUE; } #ifdef VIDEO static gboolean watch_cb (GstBus *bus, GstMessage *message, gpointer user_data) { CbMediaVideoWidget *self = user_data; switch (message->type) { case GST_MESSAGE_BUFFERING: { int percent; CbSurfaceProgress *sp = CB_SURFACE_PROGRESS (self->surface_progress); gst_message_parse_buffering (message, &percent); cb_surface_progress_set_progress (sp, MAX (cb_surface_progress_get_progress (sp), percent / 100.0)); g_debug ("Buffering: %d", percent); } break; case GST_MESSAGE_EOS: { /* Loop */ gst_element_seek (self->src, 1.0, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, GST_SEEK_TYPE_SET, 0, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); } break; case GST_MESSAGE_ASYNC_DONE: { g_debug ("ASYNC DONE"); gtk_stack_set_visible_child_name (GTK_STACK (self), "video"); gst_element_set_state (self->src, GST_STATE_PLAYING); if (self->video_progress_id == 0) self->video_progress_id = g_timeout_add (50, video_progress_timeout_cb, self); } break; case GST_MESSAGE_ERROR: { GError *error; char *msg; gst_message_parse_error (message, &error, &msg); g_critical ("%s", msg); cb_media_video_widget_show_error (self, msg); g_free (msg); g_error_free (error); } break; default: {} } return TRUE; } #endif static void cb_media_video_widget_destroy (GtkWidget *widget) { CbMediaVideoWidget *self = CB_MEDIA_VIDEO_WIDGET (widget); cb_media_video_widget_stop_video (self); GTK_WIDGET_CLASS (cb_media_video_widget_parent_class)->destroy (widget); } static gboolean cb_media_video_widget_key_press_event (GtkWidget *widget, GdkEventKey *event) { CbMediaVideoWidget *self = CB_MEDIA_VIDEO_WIDGET (widget); cb_media_video_widget_stop_video (self); return GDK_EVENT_PROPAGATE; } static void cb_media_video_widget_finalize (GObject *object) { CbMediaVideoWidget *self = CB_MEDIA_VIDEO_WIDGET (object); g_object_unref (self->cancellable); if (self->session) g_object_unref (self->session); if (self->message) g_object_unref (self->message); g_free (self->media_url); G_OBJECT_CLASS (cb_media_video_widget_parent_class)->finalize (object); } static void cb_media_video_widget_init (CbMediaVideoWidget *self) { #ifdef VIDEO GstBus *bus; #endif GtkWidget *box; guint flags; self->error_label = gtk_label_new (""); gtk_label_set_line_wrap (GTK_LABEL (self->error_label), TRUE); gtk_label_set_selectable (GTK_LABEL (self->error_label), TRUE); gtk_widget_set_halign (self->error_label, GTK_ALIGN_CENTER); gtk_widget_set_valign (self->error_label, GTK_ALIGN_CENTER); gtk_widget_show (self->error_label); self->video_progress = gtk_progress_bar_new (); gtk_widget_show (self->video_progress); self->surface_progress = cb_surface_progress_new (); gtk_widget_show (self->surface_progress); gtk_container_add (GTK_CONTAINER (self), self->surface_progress); gtk_container_add (GTK_CONTAINER (self), self->error_label); self->cancellable = g_cancellable_new (); gtk_stack_set_visible_child (GTK_STACK (self), self->surface_progress); /* Init gstreamer stuff */ #ifdef VIDEO self->src = gst_element_factory_make ("playbin", "video"); self->sink = gst_element_factory_make ("gtksink", "gtksink"); if (self->sink == NULL) { cb_media_video_widget_show_error (self, "Could not create gtksink. Need gst-plugins-bad >= 1.6"); return; } g_object_get (self->sink, "widget", &self->area, NULL); gtk_widget_set_hexpand (self->area, TRUE); gtk_widget_set_vexpand (self->area, TRUE); /* We will switch to the "video" child later after getting an ASYNC_DONE message from gstreamer */ bus = gst_element_get_bus (self->src); gst_bus_add_watch (bus, watch_cb, self); g_object_set (self->src, "video-sink", self->sink, "ring-buffer-max-size", (10 * 1024 * 1024), NULL); g_object_get (self->src, "flags", &flags, NULL); g_object_set (self->src, "flags", flags | (1 << 7) /* DOWNLOAD */, NULL); #endif box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_container_add (GTK_CONTAINER (box), self->area); gtk_style_context_add_class (gtk_widget_get_style_context (self->video_progress), "embedded-progress"); gtk_container_add (GTK_CONTAINER (box), self->video_progress); gtk_stack_add_named (GTK_STACK (self), box, "video"); } static void cb_media_video_widget_class_init (CbMediaVideoWidgetClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = cb_media_video_widget_finalize; widget_class->destroy = cb_media_video_widget_destroy; widget_class->key_press_event = cb_media_video_widget_key_press_event; } CbMediaVideoWidget * cb_media_video_widget_new (CbMedia *media) { int h; int width; int height; int monitor_width; int monitor_height; double scale, scale_x = 1.0, scale_y = 1.0; CbMediaVideoWidget *self = CB_MEDIA_VIDEO_WIDGET (g_object_new (CB_TYPE_MEDIA_VIDEO_WIDGET, NULL)); g_return_val_if_fail (CB_IS_MEDIA (media), self); g_return_val_if_fail (media->surface != NULL, self); g_return_val_if_fail (media->url != NULL, self); cb_surface_progress_set_surface (CB_SURFACE_PROGRESS (self->surface_progress), media->surface); gtk_widget_measure (self->video_progress, GTK_ORIENTATION_VERTICAL, -1, &h, NULL, NULL, NULL); /* TODO: Replace GdkScreen usage */ width = cairo_image_surface_get_width (media->surface); height = cairo_image_surface_get_height (media->surface) + h; monitor_width = gdk_screen_get_width (gdk_screen_get_default ()); monitor_height = gdk_screen_get_height (gdk_screen_get_default ()); if (width > monitor_width * 0.9) scale_x = (monitor_width * 0.9) / width; if (height > monitor_height * 0.9) scale_y = (monitor_height * 0.9) / height; scale = MIN (scale_x, scale_y); gtk_widget_set_size_request (GTK_WIDGET (self), (int)(width * scale), (int)(height * scale)); switch (media->type) { case CB_MEDIA_TYPE_TWITTER_VIDEO: case CB_MEDIA_TYPE_INSTAGRAM_VIDEO: self->media_url = g_strdup (media->url); break; case CB_MEDIA_TYPE_ANIMATED_GIF: fetch_real_url (self, media->url); break; default: g_warn_if_reached (); } return self; } void cb_media_video_widget_start (CbMediaVideoWidget *self) { g_assert (gtk_widget_get_parent (GTK_WIDGET (self)) != NULL); /* We can only do this now, since the GtkDrawingArea from gstreamer needs to have a * parent set when the video starts, otherwise it will create its own GtkWindow. */ if (self->media_url != NULL) cb_media_video_widget_start_video (self); else g_debug ("Retrieving real URL first..."); } corebird-1.7.4/src/CbMediaVideoWidget.h000066400000000000000000000032311324604713000176720ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef _CB_MEDIA_VIDEO_WIDGET_H_ #define _CB_MEDIA_VIDEO_WIDGET_H_ #ifdef VIDEO #include #endif #include #include #include "CbMedia.h" #include "CbSurfaceProgress.h" #define CB_TYPE_MEDIA_VIDEO_WIDGET cb_media_video_widget_get_type () G_DECLARE_FINAL_TYPE (CbMediaVideoWidget, cb_media_video_widget, CB, MEDIA_VIDEO_WIDGET, GtkStack); struct _CbMediaVideoWidget { GtkStack parent_instance; #ifdef VIDEO GstElement *src; GstElement *sink; #endif SoupSession *session; SoupMessage *message; GtkWidget *area; GtkWidget *surface_progress; GtkWidget *video_progress; GtkWidget *error_label; GCancellable *cancellable; guint video_progress_id; char *media_url; }; typedef struct _CbMediaVideoWidget CbMediaVideoWidget; CbMediaVideoWidget * cb_media_video_widget_new (CbMedia *media); void cb_media_video_widget_start (CbMediaVideoWidget *self); #endif corebird-1.7.4/src/CbMessageReceiver.c000066400000000000000000000026021324604713000175650ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbMessageReceiver.h" G_DEFINE_INTERFACE (CbMessageReceiver, cb_message_receiver, G_TYPE_OBJECT); static void cb_message_receiver_default_init (CbMessageReceiverInterface *self) { self->stream_message_received = NULL; } void cb_message_receiver_stream_message_received (CbMessageReceiver *self, CbStreamMessageType type, JsonNode *node) { CbMessageReceiverInterface *iface; g_return_if_fail (CB_IS_MESSAGE_RECEIVER (self)); iface = CB_MESSAGE_RECEIVER_GET_IFACE (self); return iface->stream_message_received (self, type, node); } corebird-1.7.4/src/CbMessageReceiver.h000066400000000000000000000030271324604713000175740ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef __CB_MESSAGE_RECEIVER_H__ #define __CB_MESSAGE_RECEIVER_H__ #include #include #include "CbTypes.h" #define CB_TYPE_MESSAGE_RECEIVER (cb_message_receiver_get_type()) G_DECLARE_INTERFACE (CbMessageReceiver, cb_message_receiver, CB, MESSAGE_RECEIVER, GObject) struct _CbMessageReceiverInterface { GTypeInterface base_iface; void (*stream_message_received) (CbMessageReceiver *self, CbStreamMessageType type, JsonNode *node); }; void cb_message_receiver_stream_message_received (CbMessageReceiver *self, CbStreamMessageType type, JsonNode *node); #endif corebird-1.7.4/src/CbSnippetManager.c000066400000000000000000000131341324604713000174330ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbSnippetManager.h" #include G_DEFINE_TYPE (CbSnippetManager, cb_snippet_manager, G_TYPE_OBJECT); static int load_snippet_cb (void *data, int n_cols, char **col_text, char **col_name) { CbSnippetManager *self = data; g_hash_table_insert (self->snippets, g_strdup (col_text[1]), g_strdup (col_text[2])); return 0; } static void cb_snippet_manager_load_snippets (CbSnippetManager *self) { char *err = NULL; g_assert (!self->inited); sqlite3_exec (self->db, "SELECT `id`, `key`, `value` FROM `snippets` ORDER BY `id`;", load_snippet_cb, self, &err); if (err != NULL) { g_warning ("Couldn't load snippets: %s", err); g_free (err); return; } } static void cb_snippet_manager_finalize (GObject *object) { CbSnippetManager *self = CB_SNIPPET_MANAGER (object); g_hash_table_destroy (self->snippets); G_OBJECT_CLASS (cb_snippet_manager_parent_class)->finalize (object); } static void cb_snippet_manager_class_init (CbSnippetManagerClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = cb_snippet_manager_finalize; } CbSnippetManager * cb_snippet_manager_new (sqlite3 *db) { CbSnippetManager *sm = CB_SNIPPET_MANAGER (g_object_new (CB_TYPE_SNIPPET_MANAGER, NULL)); sm->db = db; return sm; } static void cb_snippet_manager_init (CbSnippetManager *self) { self->inited = FALSE; self->snippets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); } guint cb_snippet_manager_n_snippets (CbSnippetManager *self) { if (!self->inited) cb_snippet_manager_load_snippets (self); return g_hash_table_size (self->snippets); } void cb_snippet_manager_remove_snippet (CbSnippetManager *self, const char *snippet_key) { sqlite3_stmt *stmt; if (!self->inited) cb_snippet_manager_load_snippets (self); g_hash_table_remove (self->snippets, snippet_key); sqlite3_prepare_v2 (self->db, "DELETE FROM `snippets` WHERE `key`=?;", -1, &stmt, NULL); sqlite3_bind_text (stmt, 1, snippet_key, -1, NULL); if (sqlite3_step (stmt) != SQLITE_DONE) { g_warning ("Couldn't remove snippet %s", snippet_key); } sqlite3_finalize (stmt); } void cb_snippet_manager_insert_snippet (CbSnippetManager *self, const char *key, const char *value) { sqlite3_stmt *stmt; if (!self->inited) cb_snippet_manager_load_snippets (self); g_hash_table_insert (self->snippets, g_strdup (key), g_strdup (value)); sqlite3_prepare_v2 (self->db, "INSERT INTO `snippets`(`key`, `value`) VALUES (?, ?);", -1, &stmt, NULL); sqlite3_bind_text (stmt, 1, key, -1, NULL); sqlite3_bind_text (stmt, 2, value, -1, NULL); if (sqlite3_step (stmt) != SQLITE_DONE) g_warning ("Couldn't insert snippet %s", key); sqlite3_finalize (stmt); } static gboolean has_snippet_n_predicate (gpointer key, gpointer value, gpointer user_data) { const struct { const char *k; gsize k_len; } *data = user_data; return strlen (key) == data->k_len && strncmp (data->k, key, data->k_len) == 0; } gboolean cb_snippet_manager_has_snippet_n (CbSnippetManager *self, const char *key, gsize key_length_in_bytes) { static struct { const char *k; gsize k_len; } data; data.k = key; data.k_len = key_length_in_bytes; if (!self->inited) cb_snippet_manager_load_snippets (self); return g_hash_table_find (self->snippets, has_snippet_n_predicate, &data) != NULL; } const char * cb_snippet_manager_get_snippet (CbSnippetManager *self, const char *key) { if (!self->inited) cb_snippet_manager_load_snippets (self); return g_hash_table_lookup (self->snippets, key); } void cb_snippet_manager_query_snippets (CbSnippetManager *self, GHFunc func, gpointer user_data) { if (!self->inited) cb_snippet_manager_load_snippets (self); g_hash_table_foreach (self->snippets, func, user_data); } void cb_snippet_manager_set_snippet (CbSnippetManager *self, const char *old_key, const char *key, const char *value) { if (!self->inited) cb_snippet_manager_load_snippets (self); if (!g_hash_table_contains (self->snippets, old_key)) { g_warning ("No snippet '%s' found in database", old_key); return; } cb_snippet_manager_remove_snippet (self, old_key); cb_snippet_manager_insert_snippet (self, key, value); } corebird-1.7.4/src/CbSnippetManager.h000066400000000000000000000055471324604713000174510ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef CB_SNIPPET_MANAGER_H #define CB_SNIPPET_MANAGER_H #include #include #include struct _CbSnippetManager { GObject parent_instance; GHashTable *snippets; sqlite3 *db; guint inited : 1; }; typedef struct _CbSnippetManager CbSnippetManager; #define CB_TYPE_SNIPPET_MANAGER cb_snippet_manager_get_type() G_DECLARE_FINAL_TYPE (CbSnippetManager, cb_snippet_manager, CB, SNIPPET_MANAGER, GObject); /* * TODO: This is only a GObject because we can bind that properly in the vapi, * but a SnippetManager exists only once and for the entire lifetime of the GtkApplication * object we have... */ CbSnippetManager * cb_snippet_manager_new (sqlite3 *db); guint cb_snippet_manager_n_snippets (CbSnippetManager *self); void cb_snippet_manager_remove_snippet (CbSnippetManager *self, const char *snippet_key); void cb_snippet_manager_insert_snippet (CbSnippetManager *self, const char *key, const char *value); gboolean cb_snippet_manager_has_snippet_n (CbSnippetManager *self, const char *key, gsize key_length_in_bytes); const char * cb_snippet_manager_get_snippet (CbSnippetManager *self, const char *key); void cb_snippet_manager_query_snippets (CbSnippetManager *self, GHFunc func, gpointer user_data); void cb_snippet_manager_set_snippet (CbSnippetManager *self, const char *old_key, const char *key, const char *value); #endif corebird-1.7.4/src/CbSurfaceProgress.c000066400000000000000000000117461324604713000176420ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbSurfaceProgress.h" G_DEFINE_TYPE(CbSurfaceProgress, cb_surface_progress, GTK_TYPE_WIDGET) static gboolean cb_surface_progress_draw (GtkWidget *widget, cairo_t *ct) { CbSurfaceProgress *self = CB_SURFACE_PROGRESS (widget); int width, height; cairo_surface_t *tmp_surface; cairo_t *ctx; double arc_size, cx, cy, radius; double scale; if (self->surface == NULL) return GDK_EVENT_PROPAGATE; width = gtk_widget_get_allocated_width (widget); height = gtk_widget_get_allocated_height (widget); scale = MIN ((double)width / (double)cairo_image_surface_get_width (self->surface), (double)height / (double)cairo_image_surface_get_height (self->surface)); tmp_surface = cairo_surface_create_similar (self->surface, CAIRO_CONTENT_COLOR_ALPHA, width, height); ctx = cairo_create (tmp_surface); /* Draw the surface slightly translucent on the widget's surface */ cairo_save (ct); cairo_rectangle (ct, 0, 0, width, height); cairo_scale (ct, scale, scale); cairo_set_source_surface (ct, self->surface, 0, 0); cairo_paint_with_alpha (ct, 0.5); cairo_restore (ct); /* Draw self->surface on tmp surface */ cairo_save (ctx); cairo_rectangle (ctx, 0, 0, width, height); cairo_scale (ctx, scale, scale); cairo_set_source_surface (ctx, self->surface, 0, 0); cairo_fill (ctx); cairo_restore (ctx); arc_size = MIN (width, height) * 2.0; cx = width / 2.0; cy = height / 2.0; radius = (arc_size / 2.0) - 0.5; cairo_set_operator (ctx, CAIRO_OPERATOR_DEST_IN); cairo_set_source_rgba (ctx, 1.0, 0.0, 0.0, 1.0); cairo_move_to (ctx, width / 2.0, height / 2.0); cairo_arc (ctx, cx, cy, radius, 0, 0); cairo_arc (ctx, cx, cy, radius, 0, 2 * G_PI * self->progress); cairo_move_to (ctx, cx, cy); cairo_fill (ctx); /* Draw the tmp surface */ cairo_rectangle (ct, 0, 0, width, height); cairo_set_source_surface (ct, tmp_surface, 0, 0); cairo_fill (ct); cairo_surface_destroy (tmp_surface); return GDK_EVENT_PROPAGATE; } static void cb_surface_progress_get_preferred_width (GtkWidget *widget, int *minimum, int *natural) { *minimum = *natural = 1; } static void cb_surface_progress_get_preferred_height (GtkWidget *widget, int *minimum, int *natural) { *minimum = *natural = 1; } static void cb_surface_progress_finalize (GObject *object) { CbSurfaceProgress *self = CB_SURFACE_PROGRESS (object); if (self->surface) cairo_surface_destroy (self->surface); G_OBJECT_CLASS (cb_surface_progress_parent_class)->finalize (object); } static void cb_surface_progress_init (CbSurfaceProgress *self) { gtk_widget_set_has_window (GTK_WIDGET (self), FALSE); } static void cb_surface_progress_class_init (CbSurfaceProgressClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->finalize = cb_surface_progress_finalize; widget_class->draw = cb_surface_progress_draw; widget_class->get_preferred_width = cb_surface_progress_get_preferred_width; widget_class->get_preferred_height = cb_surface_progress_get_preferred_height; } GtkWidget * cb_surface_progress_new (void) { return GTK_WIDGET (g_object_new (CB_TYPE_SURFACE_PROGRESS, NULL)); } double cb_surface_progress_get_progress (CbSurfaceProgress *self) { return self->progress; } void cb_surface_progress_set_progress (CbSurfaceProgress *self, double progress) { if (progress >= 1.0) self->progress = 1.0; else if (progress < 0.0) self->progress = 0.0; else self->progress = progress; gtk_widget_queue_draw (GTK_WIDGET (self)); } cairo_surface_t * cb_surface_progress_get_surface (CbSurfaceProgress *self) { return self->surface; } void cb_surface_progress_set_surface (CbSurfaceProgress *self, cairo_surface_t *surface) { if (self->surface) cairo_surface_destroy (self->surface); self->surface = cairo_surface_reference (surface); gtk_widget_queue_resize (GTK_WIDGET (self)); } corebird-1.7.4/src/CbSurfaceProgress.h000066400000000000000000000032341324604713000176400ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef _CB_SURFACE_PROGRESS_H_ #define _CB_SURFACE_PROGRESS_H_ #include #define CB_TYPE_SURFACE_PROGRESS cb_surface_progress_get_type () G_DECLARE_FINAL_TYPE (CbSurfaceProgress, cb_surface_progress, CB, SURFACE_PROGRESS, GtkWidget); struct _CbSurfaceProgress { GtkWidget parent_instance; cairo_surface_t *surface; double progress; }; typedef struct _CbSurfaceProgress CbSurfaceProgress; GtkWidget * cb_surface_progress_new (void); double cb_surface_progress_get_progress (CbSurfaceProgress *self); void cb_surface_progress_set_progress (CbSurfaceProgress *self, double progress); cairo_surface_t * cb_surface_progress_get_surface (CbSurfaceProgress *self); void cb_surface_progress_set_surface (CbSurfaceProgress *self, cairo_surface_t *surface); #endif corebird-1.7.4/src/CbTextTransform.c000066400000000000000000000143711324604713000173420ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2016 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbTextTransform.h" #include "CbMediaDownloader.h" #include "CbTypes.h" #include "CbUtils.h" #include #include char * cb_text_transform_tweet (const CbMiniTweet *tweet, guint flags, guint64 quote_id) { return cb_text_transform_text (tweet->text, tweet->entities, tweet->n_entities, flags, tweet->n_medias, quote_id, tweet->display_range_start); } const int TRAILING = 1 << 0; static inline gboolean is_hashtag (const char *s) { return s[0] == '#'; } static inline gboolean is_link (const char *s) { return s != NULL && (g_str_has_prefix (s, "http://") || g_str_has_prefix (s, "https://")); } static inline gboolean is_quote_link (const CbTextEntity *e, gint64 quote_id) { char *suffix = g_strdup_printf ("/status/%" G_GINT64_FORMAT, quote_id); gboolean ql; ql = (e->target != NULL) && (g_str_has_prefix (e->target, "https://twitter.com/") && g_str_has_suffix (e->target, suffix)); g_free (suffix); return ql; } static inline gboolean is_media_url (const char *url, const char *display_text, gsize media_count) { return (is_media_candidate (url != NULL ? url : display_text) && media_count == 1) || g_str_has_prefix (display_text, "pic.twitter.com/"); } static inline gboolean is_whitespace (const char *s) { while (*s != '\0') { if (!isspace (*s)) return FALSE; s++; } return TRUE; } char * cb_text_transform_text (const char *text, CbTextEntity *entities, gsize n_entities, guint flags, gsize n_medias, gint64 quote_id, guint display_range_start) { GString *str; const guint text_len = g_utf8_strlen (text, -1); int i; char *end_str; gboolean last_entity_was_trailing = FALSE; guint last_end = 0; guint cur_end = text_len; if (text_len == 0) return g_strdup (text); str = g_string_new (NULL); for (i = (int)n_entities - 1; i >= 0; i --) { char *btw; guint entity_to; gsize btw_length = cur_end - entities[i].to; if (entities[i].to <= display_range_start) continue; entity_to = entities[i].to - display_range_start; btw = g_utf8_substring (text, entity_to, cur_end); if (!is_whitespace (btw) && btw_length > 0) { g_free (btw); break; } else cur_end = entity_to; if (entities[i].to == cur_end && (is_hashtag (entities[i].display_text) || is_link (entities[i].target))) { entities[i].info |= TRAILING; cur_end = entities[i].from - display_range_start; } else { g_free (btw); break; } g_free (btw); } for (i = 0; i < (int)n_entities; i ++) { CbTextEntity *entity = &entities[i]; char *before; guint entity_to; if (entity->to <= display_range_start) continue; entity_to = entity->to - display_range_start; before = g_utf8_substring (text, last_end, entity->from - display_range_start); if (!(last_entity_was_trailing && is_whitespace (before))) g_string_append (str, before); if ((flags & CB_TEXT_TRANSFORM_REMOVE_TRAILING_HASHTAGS) > 0 && (entity->info & TRAILING) > 0 && is_hashtag (entity->display_text)) { last_end = entity_to; last_entity_was_trailing = TRUE; g_free (before); continue; } last_entity_was_trailing = FALSE; if (((flags & CB_TEXT_TRANSFORM_REMOVE_MEDIA_LINKS) > 0 && is_media_url (entity->target, entity->display_text, n_medias)) || (quote_id != 0 && is_quote_link (entity, quote_id))) { last_end = entity_to; g_free (before); continue; } if ((flags & CB_TEXT_TRANSFORM_EXPAND_LINKS) > 0) { if (entity->display_text[0] == '@') g_string_append (str, entity->display_text); else g_string_append (str, entity->target ? entity->target : entity->display_text); } else { g_string_append (str, "target ? entity->target : entity->display_text); g_string_append (str, "\""); if (entity->tooltip_text != NULL) { char *c = cb_utils_escape_ampersands (entity->tooltip_text); char *cc = cb_utils_escape_quotes (c); g_string_append (str, " title=\""); g_string_append (str, cc); g_string_append (str, "\""); g_free (cc); g_free (c); } g_string_append (str, ">"); g_string_append (str, entity->display_text); g_string_append (str,""); } last_end = entity_to; g_free (before); } end_str = g_utf8_substring (text, last_end, text_len); g_string_append (str, end_str); g_free (end_str); return g_string_free (str, FALSE); } corebird-1.7.4/src/CbTextTransform.h000066400000000000000000000031401324604713000173370ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef __TEXT_TRANSFORM_H #define __TEXT_TRANSFORM_H #include #include "CbTypes.h" typedef enum { CB_TEXT_TRANSFORM_REMOVE_MEDIA_LINKS = 1 << 0, CB_TEXT_TRANSFORM_REMOVE_TRAILING_HASHTAGS = 1 << 1, CB_TEXT_TRANSFORM_EXPAND_LINKS = 1 << 2 } CbTransformFlags; char *cb_text_transform_tweet (const CbMiniTweet *tweet, guint flags, guint64 quote_id); char *cb_text_transform_text (const char *text, CbTextEntity *entities, gsize n_entities, guint flags, gsize n_medias, gint64 quote_id, guint display_range_start); #endif corebird-1.7.4/src/CbTweet.c000066400000000000000000000345121324604713000156110ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2016 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbTweet.h" #include "CbTextTransform.h" #include /* TODO: We might want to put this into a utils.c later */ static gboolean usable_json_value (JsonObject *object, const char *name) { if (!json_object_has_member (object, name)) return FALSE; return !json_object_get_null_member (object, name); } static guint json_array_size (JsonObject *object, const char *name) { if (!json_object_has_member (object, name)) return 0; return json_array_get_length (json_object_get_array_member (object, name)); } G_DEFINE_TYPE (CbTweet, cb_tweet, G_TYPE_OBJECT); enum { STATE_CHANGED, LAST_SIGNAL }; static guint tweet_signals[LAST_SIGNAL] = { 0 }; gboolean cb_tweet_is_hidden (CbTweet *tweet) { g_return_val_if_fail (CB_IS_TWEET (tweet), TRUE); return (tweet->state & (CB_TWEET_STATE_HIDDEN_FORCE | CB_TWEET_STATE_HIDDEN_UNFOLLOWED | CB_TWEET_STATE_HIDDEN_FILTERED | CB_TWEET_STATE_HIDDEN_RTS_DISABLED | CB_TWEET_STATE_HIDDEN_RT_BY_USER | CB_TWEET_STATE_HIDDEN_RT_BY_FOLLOWEE | CB_TWEET_STATE_HIDDEN_AUTHOR_BLOCKED | CB_TWEET_STATE_HIDDEN_RETWEETER_BLOCKED | CB_TWEET_STATE_HIDDEN_AUTHOR_MUTED | CB_TWEET_STATE_HIDDEN_RETWEETER_MUTED)) > 0; } gboolean cb_tweet_has_inline_media (CbTweet *tweet) { g_return_val_if_fail (CB_IS_TWEET (tweet), FALSE); if (tweet->quoted_tweet != NULL) return tweet->quoted_tweet->n_medias > 0; else if (tweet->retweeted_tweet != NULL) return tweet->retweeted_tweet->n_medias > 0; return tweet->source_tweet.n_medias > 0; } /* TODO: Replace these 3 functinos with one that returns a pointer to a CbUserIdentity? */ gint64 cb_tweet_get_user_id (CbTweet *tweet) { if (tweet->retweeted_tweet != NULL) return tweet->retweeted_tweet->author.id; return tweet->source_tweet.author.id; } const char * cb_tweet_get_screen_name (CbTweet *tweet) { if (tweet->retweeted_tweet != NULL) return tweet->retweeted_tweet->author.screen_name; return tweet->source_tweet.author.screen_name; } const char * cb_tweet_get_user_name (CbTweet *tweet) { if (tweet->retweeted_tweet != NULL) return tweet->retweeted_tweet->author.user_name; return tweet->source_tweet.author.user_name; } CbMedia ** cb_tweet_get_medias (CbTweet *tweet, int *n_medias) { g_return_val_if_fail (CB_IS_TWEET (tweet), NULL); g_return_val_if_fail (n_medias != NULL, NULL); if (tweet->quoted_tweet != NULL) { *n_medias = tweet->quoted_tweet->n_medias; return tweet->quoted_tweet->medias; } else if (tweet->retweeted_tweet != NULL) { *n_medias = tweet->retweeted_tweet->n_medias; return tweet->retweeted_tweet->medias; } else { *n_medias = tweet->source_tweet.n_medias; return tweet->source_tweet.medias; } } char ** cb_tweet_get_mentions (CbTweet *tweet, int *n_mentions) { CbTextEntity *entities; gsize n_entities; gsize i, x; char **mentions; g_return_val_if_fail (CB_IS_TWEET (tweet), NULL); g_return_val_if_fail (n_mentions != NULL, NULL); if (tweet->retweeted_tweet != NULL) { entities = tweet->retweeted_tweet->entities; n_entities = tweet->retweeted_tweet->n_entities; } else { entities = tweet->source_tweet.entities; n_entities = tweet->source_tweet.n_entities; } *n_mentions = 0; for (i = 0; i < n_entities; i ++) if (entities[i].display_text[0] == '@') (*n_mentions) ++; if (*n_mentions == 0) return NULL; mentions = g_malloc (sizeof(char*) * (*n_mentions)); x = 0; for (i = 0; i < n_entities; i ++) if (entities[i].display_text[0] == '@') { mentions[x] = g_strdup (entities[i].display_text); x ++; } return mentions; } void cb_tweet_load_from_json (CbTweet *tweet, JsonNode *status_node, gint64 account_id, GDateTime *now) { JsonObject *status; JsonObject *user; gboolean has_media; g_return_if_fail (CB_IS_TWEET (tweet)); g_return_if_fail (status_node != NULL); g_return_if_fail (now != NULL); status = json_node_get_object (status_node); user = json_object_get_object_member (status, "user"); tweet->id = json_object_get_int_member (status, "id"); tweet->retweet_count = (guint) json_object_get_int_member (status, "retweet_count"); tweet->favorite_count = (guint) json_object_get_int_member (status, "favorite_count"); cb_mini_tweet_parse (&tweet->source_tweet, status); has_media = json_array_size (json_object_get_object_member (status, "entities"), "media") > 0; if (json_object_has_member (status, "retweeted_status")) { JsonObject *rt = json_object_get_object_member (status, "retweeted_status"); JsonObject *rt_user = json_object_get_object_member (rt, "user"); tweet->retweeted_tweet = g_malloc (sizeof(CbMiniTweet)); cb_mini_tweet_init (tweet->retweeted_tweet); cb_mini_tweet_parse (tweet->retweeted_tweet, rt); cb_mini_tweet_parse_entities (tweet->retweeted_tweet, rt); tweet->avatar_url = g_strdup (json_object_get_string_member (rt_user, "profile_image_url")); if (json_object_get_boolean_member (rt_user, "protected")) tweet->state |= CB_TWEET_STATE_PROTECTED; if (json_object_get_boolean_member (rt_user, "verified")) tweet->state |= CB_TWEET_STATE_VERIFIED; if (usable_json_value (rt, "possibly_sensitive") && json_object_get_boolean_member (rt, "possibly_sensitive")) tweet->state |= CB_TWEET_STATE_NSFW; } else { cb_mini_tweet_parse_entities (&tweet->source_tweet, status); tweet->avatar_url = g_strdup (json_object_get_string_member (user, "profile_image_url")); if (json_object_get_boolean_member (user, "protected")) tweet->state |= CB_TWEET_STATE_PROTECTED; if (json_object_get_boolean_member (user, "verified")) tweet->state |= CB_TWEET_STATE_VERIFIED; if (usable_json_value (status, "possibly_sensitive") && json_object_get_boolean_member (status, "possibly_sensitive")) tweet->state |= CB_TWEET_STATE_NSFW; } if (json_object_has_member (status, "quoted_status") && !has_media) { JsonObject *quote = json_object_get_object_member (status, "quoted_status"); tweet->quoted_tweet = g_malloc (sizeof (CbMiniTweet)); cb_mini_tweet_init (tweet->quoted_tweet); cb_mini_tweet_parse (tweet->quoted_tweet, quote); cb_mini_tweet_parse_entities (tweet->quoted_tweet, quote); if (usable_json_value (quote, "possibly_sensitive") && json_object_get_boolean_member (quote, "possibly_sensitive")) tweet->state |= CB_TWEET_STATE_NSFW; else tweet->state &= ~CB_TWEET_STATE_NSFW; } else if (tweet->retweeted_tweet != NULL && tweet->retweeted_tweet->n_medias == 0 && json_object_has_member (json_object_get_object_member (status, "retweeted_status"), "quoted_status")) { JsonObject *quote = json_object_get_object_member (json_object_get_object_member (status, "retweeted_status"), "quoted_status"); tweet->quoted_tweet = g_malloc (sizeof (CbMiniTweet)); cb_mini_tweet_init (tweet->quoted_tweet); cb_mini_tweet_parse (tweet->quoted_tweet, quote); cb_mini_tweet_parse_entities (tweet->quoted_tweet, quote); if (usable_json_value (quote, "possibly_sensitive") && json_object_get_boolean_member (quote, "possibly_sensitive")) tweet->state |= CB_TWEET_STATE_NSFW; else tweet->state &= ~CB_TWEET_STATE_NSFW; } if (json_object_get_boolean_member (status, "favorited")) tweet->state |= CB_TWEET_STATE_FAVORITED; if (json_object_has_member (status, "current_user_retweet")) { JsonObject *cur_rt = json_object_get_object_member (status, "current_user_retweet"); tweet->my_retweet = json_object_get_int_member (cur_rt, "id"); tweet->state |= CB_TWEET_STATE_RETWEETED; } else if (json_object_get_boolean_member (status, "retweeted") || (tweet->retweeted_tweet != NULL && tweet->source_tweet.author.id == account_id)) { /* The 'retweeted' flag is not reliable so we additionally check if the tweet is authored by the authenticating user */ tweet->my_retweet = tweet->id; tweet->state |= CB_TWEET_STATE_RETWEETED; } #ifdef DEBUG { JsonGenerator *generator = json_generator_new (); json_generator_set_root (generator, status_node); json_generator_set_pretty (generator, TRUE); tweet->json_data = json_generator_to_data (generator, NULL); g_object_unref (generator); } #endif } gboolean cb_tweet_is_flag_set (CbTweet *tweet, guint flag) { return (tweet->state & flag) > 0; } void cb_tweet_set_flag (CbTweet *tweet, guint flag) { guint prev_state; g_return_if_fail (CB_IS_TWEET (tweet)); prev_state = tweet->state; tweet->state |= flag; if (tweet->state != prev_state) g_signal_emit (tweet, tweet_signals[STATE_CHANGED], 0); } void cb_tweet_unset_flag (CbTweet *tweet, guint flag) { guint prev_state; g_return_if_fail (CB_IS_TWEET (tweet)); prev_state = tweet->state; tweet->state &= ~flag; if (tweet->state != prev_state) g_signal_emit (tweet, tweet_signals[STATE_CHANGED], 0); } char * cb_tweet_get_formatted_text (CbTweet *tweet) { g_return_val_if_fail (CB_IS_TWEET (tweet), NULL); if (tweet->retweeted_tweet != NULL) return cb_text_transform_tweet (tweet->retweeted_tweet, 0, 0); else return cb_text_transform_tweet (&tweet->source_tweet, 0, 0); } char * cb_tweet_get_trimmed_text (CbTweet *tweet, guint transform_flags) { gint64 quote_id; g_return_val_if_fail (CB_IS_TWEET (tweet), NULL); quote_id = tweet->quoted_tweet != NULL ? tweet->quoted_tweet->id : 0; if (tweet->retweeted_tweet != NULL) return cb_text_transform_tweet (tweet->retweeted_tweet, transform_flags, quote_id); else return cb_text_transform_tweet (&tweet->source_tweet, transform_flags, quote_id); } char * cb_tweet_get_real_text (CbTweet *tweet) { g_return_val_if_fail (CB_IS_TWEET (tweet), NULL); if (tweet->retweeted_tweet != NULL) return cb_text_transform_tweet (tweet->retweeted_tweet, CB_TEXT_TRANSFORM_EXPAND_LINKS, 0); else return cb_text_transform_tweet (&tweet->source_tweet, CB_TEXT_TRANSFORM_EXPAND_LINKS, 0); } char * cb_tweet_get_filter_text (CbTweet *tweet) { GString *string; char *text; g_return_val_if_fail (CB_IS_TWEET (tweet), NULL); string = g_string_new (0); if (tweet->retweeted_tweet != NULL) text = cb_text_transform_tweet (tweet->retweeted_tweet, CB_TEXT_TRANSFORM_EXPAND_LINKS, 0); else text = cb_text_transform_tweet (&tweet->source_tweet, CB_TEXT_TRANSFORM_EXPAND_LINKS ,0); g_string_append (string, text); g_free (text); g_string_append_c (string, '['); if (tweet->retweeted_tweet != NULL) g_string_append (string, "rt"); if (tweet->quoted_tweet != NULL) g_string_append (string, ",quote"); g_string_append_c (string, ']'); return g_string_free (string, FALSE); } gboolean cb_tweet_get_seen (CbTweet *tweet) { g_return_val_if_fail (CB_IS_TWEET (tweet), FALSE); return tweet->seen; } void cb_tweet_set_seen (CbTweet *tweet, gboolean value) { g_return_if_fail (CB_IS_TWEET (tweet)); value = !!value; if (value && !tweet->seen && tweet->notification_id != NULL) { GApplication *app = g_application_get_default (); g_application_withdraw_notification (app, tweet->notification_id); tweet->notification_id = NULL; } tweet->seen = value; } CbUserIdentity * cb_tweet_get_reply_users (CbTweet *tweet, guint *n_reply_users) { g_return_val_if_fail (CB_IS_TWEET (tweet), NULL); g_return_val_if_fail (n_reply_users != NULL, NULL); if (tweet->retweeted_tweet != NULL) { *n_reply_users = tweet->retweeted_tweet->n_reply_users; return tweet->retweeted_tweet->reply_users; } else { *n_reply_users = tweet->source_tweet.n_reply_users; return tweet->source_tweet.reply_users; } *n_reply_users = 0; return NULL; /* shrug */ } CbTweet * cb_tweet_new (void) { return (CbTweet *)g_object_new (CB_TYPE_TWEET, NULL); } static void cb_tweet_finalize (GObject *object) { CbTweet *tweet = (CbTweet *)object; g_free (tweet->avatar_url); g_free (tweet->notification_id); cb_mini_tweet_free (&tweet->source_tweet); if (tweet->retweeted_tweet != NULL) { cb_mini_tweet_free (tweet->retweeted_tweet); g_free (tweet->retweeted_tweet); } if (tweet->quoted_tweet != NULL) { cb_mini_tweet_free (tweet->quoted_tweet); g_free (tweet->quoted_tweet); } #ifdef DEBUG g_free (tweet->json_data); #endif G_OBJECT_CLASS (cb_tweet_parent_class)->finalize (object); } static void cb_tweet_init (CbTweet *tweet) { tweet->state = 0; tweet->quoted_tweet = NULL; tweet->retweeted_tweet = NULL; tweet->notification_id = NULL; tweet->seen = TRUE; } static void cb_tweet_class_init (CbTweetClass *class) { GObjectClass *gobject_class = (GObjectClass *)class; gobject_class->finalize = cb_tweet_finalize; tweet_signals[STATE_CHANGED] = g_signal_new ("state-changed", G_OBJECT_CLASS_TYPE (gobject_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } corebird-1.7.4/src/CbTweet.h000066400000000000000000000113721324604713000156150ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2016 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef _CB_TWEET_H_ #define _CB_TWEET_H_ #include #include #include "CbTypes.h" #include "CbMedia.h" #define CB_TWEET_MAX_LENGTH 280 typedef enum { /** Force hiding (there's no way this flag will ever get flipped...)*/ CB_TWEET_STATE_HIDDEN_FORCE = 1 << 0, /** Hidden because we unfollowed the author */ CB_TWEET_STATE_HIDDEN_UNFOLLOWED = 1 << 1, /** Hidden because one of the filters matched the tweet */ CB_TWEET_STATE_HIDDEN_FILTERED = 1 << 2, CB_TWEET_STATE_HIDDEN_RTS_DISABLED = 1 << 3, /** Hidden because it's a RT by the authenticating user */ CB_TWEET_STATE_HIDDEN_RT_BY_USER = 1 << 4, CB_TWEET_STATE_HIDDEN_RT_BY_FOLLOWEE = 1 << 5, /** Hidden because the author is blocked */ CB_TWEET_STATE_HIDDEN_AUTHOR_BLOCKED = 1 << 6, /** Hidden because the author of a retweet is blocked */ CB_TWEET_STATE_HIDDEN_RETWEETER_BLOCKED = 1 << 7, /** Hidden because the author was muted */ CB_TWEET_STATE_HIDDEN_AUTHOR_MUTED = 1 << 8, /** Hidden because the author of a retweet is muted */ CB_TWEET_STATE_HIDDEN_RETWEETER_MUTED = 1 << 9, /* The authenticating user retweeted this tweet */ CB_TWEET_STATE_RETWEETED = 1 << 10, /* The authenticating user favorited this tweet */ CB_TWEET_STATE_FAVORITED = 1 << 11, /* This tweet has been deleted by its author */ CB_TWEET_STATE_DELETED = 1 << 12, /* The author of this tweet is verified */ CB_TWEET_STATE_VERIFIED = 1 << 13, /* The author of this tweet is protected */ CB_TWEET_STATE_PROTECTED = 1 << 14, /* At least one media attached to this tweet is marked sensitive */ CB_TWEET_STATE_NSFW = 1 << 15 } CbTweetState; struct _CbTweet { GObject parent_instance; guint state : 16; gint64 id; CbMiniTweet source_tweet; CbMiniTweet *retweeted_tweet; CbMiniTweet *quoted_tweet; char *avatar_url; gint64 my_retweet; char *notification_id; guint seen : 1; guint retweet_count; guint favorite_count; #ifdef DEBUG /* In debug mode, we save the entire json we got from Twitter so we can later look at it */ char *json_data; #endif }; typedef struct _CbTweet CbTweet; #define CB_TYPE_TWEET cb_tweet_get_type () G_DECLARE_FINAL_TYPE (CbTweet, cb_tweet, CB, TWEET, GObject); CbTweet * cb_tweet_new (void); gboolean cb_tweet_is_hidden (CbTweet *tweet); CbUserIdentity * cb_tweet_get_reply_users (CbTweet *tweet, guint *n_reply_users); CbMedia ** cb_tweet_get_medias (CbTweet *tweet, int *n_medias); char ** cb_tweet_get_mentions (CbTweet *tweet, int *n_mentions); gboolean cb_tweet_has_inline_media (CbTweet *tweet); gint64 cb_tweet_get_user_id (CbTweet *tweet); const char * cb_tweet_get_screen_name (CbTweet *tweet); const char * cb_tweet_get_user_name (CbTweet *tweet); void cb_tweet_load_from_json (CbTweet *tweet, JsonNode *status_node, gint64 account_id, GDateTime *now); /* Flag stuff */ gboolean cb_tweet_is_flag_set (CbTweet *tweet, guint flag); void cb_tweet_set_flag (CbTweet *tweet, guint flag); void cb_tweet_unset_flag (CbTweet *tweet, guint flag); char * cb_tweet_get_formatted_text (CbTweet *tweet); char * cb_tweet_get_trimmed_text (CbTweet *tweet, guint transform_flags); char * cb_tweet_get_real_text (CbTweet *tweet); char * cb_tweet_get_filter_text (CbTweet *tweet); gboolean cb_tweet_get_seen (CbTweet *tweet); void cb_tweet_set_seen (CbTweet *tweet, gboolean value); #endif corebird-1.7.4/src/CbTweetModel.c000066400000000000000000000424331324604713000165730ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbTweetModel.h" static void cb_tweet_model_iface_init (GListModelInterface *iface); G_DEFINE_TYPE_WITH_CODE (CbTweetModel, cb_tweet_model, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, cb_tweet_model_iface_init)); static GType cb_tweet_model_get_item_type (GListModel *model) { return CB_TYPE_TWEET; } static guint cb_tweet_model_get_n_items (GListModel *model) { CbTweetModel *self = CB_TWEET_MODEL (model); return self->tweets->len; } static gpointer cb_tweet_model_get_item (GListModel *model, guint index) { CbTweetModel *self = CB_TWEET_MODEL (model); CbTweet *tweet; g_assert (index < self->tweets->len); tweet = g_ptr_array_index (self->tweets, index); return g_object_ref (tweet); } static void cb_tweet_model_iface_init (GListModelInterface *iface) { iface->get_item_type = cb_tweet_model_get_item_type; iface->get_n_items = cb_tweet_model_get_n_items; iface->get_item = cb_tweet_model_get_item; } static inline void emit_items_changed (CbTweetModel *self, guint position, guint removed, guint added) { g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added); } static void cb_tweet_model_init (CbTweetModel *self) { self->tweets = g_ptr_array_new_with_free_func (g_object_unref); self->hidden_tweets = g_ptr_array_new_with_free_func (g_object_unref); self->min_id = G_MAXINT64; self->max_id = G_MININT64; } static void cb_tweet_model_finalize (GObject *object) { CbTweetModel *self = CB_TWEET_MODEL (object); g_ptr_array_unref (self->tweets); g_ptr_array_unref (self->hidden_tweets); G_OBJECT_CLASS (cb_tweet_model_parent_class)->finalize (object); } static void cb_tweet_model_class_init (CbTweetModelClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = cb_tweet_model_finalize; } CbTweetModel * cb_tweet_model_new (void) { return CB_TWEET_MODEL (g_object_new (CB_TYPE_TWEET_MODEL, NULL)); } static inline void update_min_max_id (CbTweetModel *self, gint64 old_id) { int i; #ifdef DEBUG /* This should be called *after* the * tweet has been removed from self->tweets! */ for (i = 0; i < self->tweets->len; i ++) { CbTweet *t = g_ptr_array_index (self->tweets, i); g_assert (t->id != old_id); } #endif if (old_id == self->max_id) { if (self->tweets->len > 0) { CbTweet *t = g_ptr_array_index (self->tweets, 0); self->max_id = t->id; /* We just removed the tweet with the max_id, so now remove all * hidden tweets that have a id greater than the now max id! */ for (i = 0; i < self->hidden_tweets->len; i ++) { CbTweet *t = g_ptr_array_index (self->hidden_tweets, i); if (t->id > self->max_id) { g_ptr_array_remove_index (self->hidden_tweets, i); i --; } } } else { self->max_id = G_MININT64; g_ptr_array_remove_range (self->hidden_tweets, 0, self->hidden_tweets->len); } } if (old_id == self->min_id) { if (self->tweets->len > 0) { CbTweet *t = g_ptr_array_index (self->tweets, self->tweets->len - 1); self->min_id = t->id; /* We just removed the tweet with the min_id, so now remove all hidden tweets * with an id lower than the new min_id */ for (i = 0; i < self->hidden_tweets->len; i ++) { CbTweet *t = g_ptr_array_index (self->hidden_tweets, i); if (t->id < self->min_id) { g_ptr_array_remove_index (self->hidden_tweets, i); i --; } } } else { self->min_id = G_MAXINT64; g_ptr_array_remove_range (self->hidden_tweets, 0, self->hidden_tweets->len); } } } static void remove_tweet_at_pos (CbTweetModel *self, guint index) { CbTweet *tweet = g_ptr_array_index (self->tweets, index); gint64 id = tweet->id; g_assert (index < self->tweets->len); g_ptr_array_remove_index (self->tweets, index); tweet = NULL; /* We just unreffed it, so potentially freed */ /* TODO: If this tweet was the one with id == min_id or id == max_id, * we should remove tweets from self->hidden_tweets with id * greater or lower than its id. */ update_min_max_id (self, id); emit_items_changed (self, index, 1, 0); } static inline void insert_sorted (CbTweetModel *self, CbTweet *tweet) { int insert_pos = -1; if (tweet->id > self->max_id) { insert_pos = 0; } else if (tweet->id < self->min_id) { insert_pos = self->tweets->len; } else { /* This case should be relatively rare in real life since * we only ever add tweets at the top or bottom of a list */ int i; for (i = 0; i < self->tweets->len - 1; i ++) { CbTweet *cur = g_ptr_array_index (self->tweets, i); CbTweet *next = g_ptr_array_index (self->tweets, i + 1); if (cur->id >= tweet->id && next->id <= tweet->id) { insert_pos = i + 1; break; } } } if (insert_pos == -1) { /* This can happen if the same tweet gets inserted into an empty model twice. * Generally, we'd like to ignore double insertions, at least right now I can't * think of a good use case for it. (2017-06-13) */ return; } g_object_ref (tweet); g_ptr_array_insert (self->tweets, insert_pos, tweet); emit_items_changed (self, insert_pos, 0, 1); } static void hide_tweet_internal (CbTweetModel *self, guint index) { CbTweet *tweet = g_ptr_array_index (self->tweets, index); gint64 id = tweet->id; g_object_ref (tweet); g_ptr_array_remove_index (self->tweets, index); g_object_ref (tweet); /* Have to ref manually */ g_ptr_array_add (self->hidden_tweets, tweet); g_object_unref (tweet); update_min_max_id (self, id); } static void show_tweet_internal (CbTweetModel *self, guint index) { CbTweet *tweet = g_ptr_array_index (self->hidden_tweets, index); g_object_ref (tweet); g_ptr_array_remove_index (self->hidden_tweets, index); insert_sorted (self, tweet); g_object_unref (tweet); if (tweet->id > self->max_id) self->max_id = tweet->id; if (tweet->id < self->min_id) self->min_id = tweet->id; } gboolean cb_tweet_model_contains_id (CbTweetModel *self, gint64 id) { int i; g_return_val_if_fail (CB_IS_TWEET_MODEL (self), FALSE); for (i = 0; i < self->tweets->len; i ++) { CbTweet *tweet = g_ptr_array_index (self->tweets, i); if (tweet->id == id) return TRUE; } return FALSE; } void cb_tweet_model_clear (CbTweetModel *self) { int l; g_return_if_fail (CB_IS_TWEET_MODEL (self)); l = self->tweets->len; g_ptr_array_remove_range (self->tweets, 0, l); g_ptr_array_remove_range (self->hidden_tweets, 0, self->hidden_tweets->len); self->min_id = G_MAXINT64; self->max_id = G_MININT64; emit_items_changed (self, 0, l, 0); } CbTweet * cb_tweet_model_get_for_id (CbTweetModel *self, gint64 id, int diff) { int i; g_return_val_if_fail (CB_IS_TWEET_MODEL (self), NULL); for (i = 0; i < self->tweets->len; i ++) { CbTweet *tweet = g_ptr_array_index (self->tweets, i); if (tweet->id == id) { if (i + diff < self->tweets->len && i + diff >= 0) return g_ptr_array_index (self->tweets, i + diff); return NULL; } } return NULL; } gboolean cb_tweet_model_delete_id (CbTweetModel *self, gint64 id, gboolean *seen) { int i; g_return_val_if_fail (CB_IS_TWEET_MODEL (self), FALSE); for (i = 0; i < self->tweets->len; i ++) { CbTweet *tweet = g_ptr_array_index (self->tweets, i); if (tweet->id == id) { *seen = tweet->seen; g_assert (!cb_tweet_is_hidden (tweet)); cb_tweet_set_flag (tweet, CB_TWEET_STATE_DELETED); return TRUE; } else if (cb_tweet_is_flag_set (tweet, CB_TWEET_STATE_RETWEETED) && tweet->my_retweet == id) { cb_tweet_unset_flag (tweet, CB_TWEET_STATE_RETWEETED); } } *seen = FALSE; return FALSE; } void cb_tweet_model_remove_tweet (CbTweetModel *self, CbTweet *tweet) { int i; g_return_if_fail (CB_IS_TWEET_MODEL (self)); g_return_if_fail (CB_IS_TWEET (tweet)); #ifdef DEBUG if (!cb_tweet_is_hidden (tweet)) g_assert (cb_tweet_model_contains_id (self, tweet->id)); #endif if (cb_tweet_is_hidden (tweet)) { for (i = 0; i < self->hidden_tweets->len; i ++) { CbTweet *t = g_ptr_array_index (self->hidden_tweets, i); if (t == tweet) { g_ptr_array_remove_index (self->hidden_tweets, i); break; } } } else { int pos = -1; for (i = 0; i < self->tweets->len; i ++) { CbTweet *t = g_ptr_array_index (self->tweets, i); if (t == tweet) { pos = i; break; } } g_assert (pos != -1); remove_tweet_at_pos (self, pos); } } void cb_tweet_model_toggle_flag_on_user_tweets (CbTweetModel *self, gint64 user_id, CbTweetState flag, gboolean active) { int i; g_return_if_fail (CB_IS_TWEET_MODEL (self)); for (i = 0; i < self->tweets->len; i ++) { CbTweet *t = g_ptr_array_index (self->tweets, i); if (cb_tweet_get_user_id (t) == user_id) { if (active) { if (cb_tweet_model_set_tweet_flag (self, t, flag)) i --; } else { if (cb_tweet_model_unset_tweet_flag (self, t, flag)) i --; } } } /* Aaaand now the same thing for hidden tweets */ for (i = 0; i < self->hidden_tweets->len; i ++) { CbTweet *t = g_ptr_array_index (self->hidden_tweets, i); if (cb_tweet_get_user_id (t) == user_id) { if (active) { if (cb_tweet_model_set_tweet_flag (self, t, flag)) i --; } else { if (cb_tweet_model_unset_tweet_flag (self, t, flag)) i --; } } } } void cb_tweet_model_toggle_flag_on_user_retweets (CbTweetModel *self, gint64 user_id, CbTweetState flag, gboolean active) { int i; g_return_if_fail (CB_IS_TWEET_MODEL (self)); for (i = 0; i < self->tweets->len; i ++) { CbTweet *t = g_ptr_array_index (self->tweets, i); if (t->retweeted_tweet != NULL && t->source_tweet.author.id == user_id) { if (active) { if (cb_tweet_model_set_tweet_flag (self, t, flag)) i --; } else { if (cb_tweet_model_unset_tweet_flag (self, t, flag)) i --; } } } /* Aaaand now the same thing for hidden tweets */ for (i = 0; i < self->hidden_tweets->len; i ++) { CbTweet *t = g_ptr_array_index (self->hidden_tweets, i); if (t->retweeted_tweet != NULL && t->source_tweet.author.id == user_id) { if (active) { if (cb_tweet_model_set_tweet_flag (self, t, flag)) i --; } else { if (cb_tweet_model_unset_tweet_flag (self, t, flag)) i --; } } } } gboolean cb_tweet_model_set_tweet_flag (CbTweetModel *self, CbTweet *tweet, CbTweetState flag) { int i; if (cb_tweet_is_hidden (tweet)) { #ifdef DEBUG gboolean found = FALSE; /* Should be in hidden_tweets now, hu? */ for (i = 0; self->hidden_tweets->len; i ++) { CbTweet *t = g_ptr_array_index (self->hidden_tweets, i); if (t == tweet) { found = TRUE; break; } } g_assert (found); #endif cb_tweet_set_flag (tweet, flag); } else { #ifdef DEBUG /* Now it should be is self->tweets */ gboolean found = FALSE; /* Should be in hidden_tweets now, hu? */ for (i = 0; self->tweets->len; i ++) { CbTweet *t = g_ptr_array_index (self->tweets, i); if (t == tweet) { found = TRUE; break; } } g_assert (found); #endif cb_tweet_set_flag (tweet, flag); if (cb_tweet_is_hidden (tweet)) { /* Could be hidden now. */ for (i = 0; i < self->tweets->len; i ++) { CbTweet *t = g_ptr_array_index (self->tweets, i); if (t == tweet) { hide_tweet_internal (self, i); emit_items_changed (self, i, 1, 0); break; } } return TRUE; } } return FALSE; } gboolean cb_tweet_model_unset_tweet_flag (CbTweetModel *self, CbTweet *tweet, CbTweetState flag) { int i; if (cb_tweet_is_hidden (tweet)) { #ifdef DEBUG gboolean found = FALSE; /* Should be in hidden_tweets now, hu? */ for (i = 0; self->hidden_tweets->len; i ++) { CbTweet *t = g_ptr_array_index (self->hidden_tweets, i); if (t == tweet) { found = TRUE; break; } } g_assert (found); #endif cb_tweet_unset_flag (tweet, flag); if (!cb_tweet_is_hidden (tweet)) { /* Tweet not hidden anymore. Move to self->tweets * and emit items-changed. */ for (i = 0; i < self->hidden_tweets->len; i ++) { CbTweet *t = g_ptr_array_index (self->hidden_tweets, i); if (t == tweet) { show_tweet_internal (self, i); return TRUE; } } } } else { #ifdef DEBUG /* Now it should be is self->tweets */ gboolean found = FALSE; /* Should be in hidden_tweets now, hu? */ for (i = 0; self->tweets->len; i ++) { CbTweet *t = g_ptr_array_index (self->tweets, i); if (t == tweet) { found = TRUE; break; } } g_assert (found); #endif cb_tweet_unset_flag (tweet, flag); } return FALSE; } void cb_tweet_model_add (CbTweetModel *self, CbTweet *tweet) { g_return_if_fail (CB_IS_TWEET_MODEL (self)); g_return_if_fail (CB_IS_TWEET (tweet)); if (cb_tweet_is_hidden (tweet)) { g_object_ref (tweet); g_ptr_array_add (self->hidden_tweets, tweet); } else { insert_sorted (self, tweet); if (tweet->id > self->max_id) self->max_id = tweet->id; if (tweet->id < self->min_id) self->min_id = tweet->id; } } void cb_tweet_model_remove_last_n_visible (CbTweetModel *self, guint amount) { int size_before; g_return_if_fail (CB_IS_TWEET_MODEL (self)); g_assert (amount <= self->tweets->len); size_before = self->tweets->len; g_ptr_array_remove_range (self->tweets, size_before - amount, amount); update_min_max_id (self, self->min_id); emit_items_changed (self, size_before - amount, amount, 0); } void cb_tweet_model_remove_tweets_above (CbTweetModel *self, gint64 id) { g_return_if_fail (CB_IS_TWEET_MODEL (self)); while (self->tweets->len > 0) { CbTweet *first = g_ptr_array_index (self->tweets, 0); if (first->id < id) break; remove_tweet_at_pos (self, 0); } } corebird-1.7.4/src/CbTweetModel.h000066400000000000000000000074041324604713000165770ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef CB_TWEET_MODEL_H #define CB_TWEET_MODEL_H #include #include #include "CbTweet.h" typedef struct _CbTweetModel CbTweetModel; typedef struct _CbTweetModelClass CbTweetModelClass; #define CB_TYPE_TWEET_MODEL (cb_tweet_model_get_type ()) #define CB_TWEET_MODEL(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, CB_TYPE_TWEET_MODEL, CbTweetModel)) #define CB_TWEET_MODEL_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST(cls, CB_TYPE_TWEET_MODEL, CbTweetModelClass)) #define CB_IS_TWEET_MODEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE(obj, CB_TYPE_TWEET_MODEL)) #define CB_IS_TWEET_MODEL_CLASS(cls) (G_TYPE_CHECK_CLASS_TYPE(cls, CB_TYPE_TWEET_MODEL)) #define CB_TWEET_MODEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS(obj, CB_TYPE_TWEET_MODEL, CbTweetModelClass)) struct _CbTweetModel { GObject parent_instance; GPtrArray *tweets; GPtrArray *hidden_tweets; gint64 min_id; gint64 max_id; }; struct _CbTweetModelClass { GObjectClass parent_class; }; GType cb_tweet_model_get_type (void) G_GNUC_CONST; CbTweetModel *cb_tweet_model_new (void); gboolean cb_tweet_model_contains_id (CbTweetModel *self, gint64 id); void cb_tweet_model_clear (CbTweetModel *self); CbTweet *cb_tweet_model_get_for_id (CbTweetModel *self, gint64 id, int diff); gboolean cb_tweet_model_delete_id (CbTweetModel *self, gint64 id, gboolean *seen); void cb_tweet_model_remove_tweet (CbTweetModel *self, CbTweet *tweet); void cb_tweet_model_toggle_flag_on_user_tweets (CbTweetModel *self, gint64 user_id, CbTweetState flag, gboolean active); void cb_tweet_model_toggle_flag_on_user_retweets (CbTweetModel *self, gint64 user_id, CbTweetState flag, gboolean active); gboolean cb_tweet_model_set_tweet_flag (CbTweetModel *self, CbTweet *tweet, CbTweetState flag); gboolean cb_tweet_model_unset_tweet_flag (CbTweetModel *self, CbTweet *tweet, CbTweetState flag); void cb_tweet_model_add (CbTweetModel *self, CbTweet *tweet); void cb_tweet_model_remove_last_n_visible (CbTweetModel *self, guint amount); void cb_tweet_model_remove_tweets_above (CbTweetModel *self, gint64 id); #endif corebird-1.7.4/src/CbTwitterItem.c000066400000000000000000000050751324604713000170040ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbTwitterItem.h" G_DEFINE_INTERFACE (CbTwitterItem, cb_twitter_item, G_TYPE_OBJECT); static int default_update_time_delta (CbTwitterItem *self, GDateTime *now) { /* NOPE */ return 0; } static void cb_twitter_item_default_init (CbTwitterItemInterface *self) { self->get_sort_factor = NULL; self->update_time_delta = default_update_time_delta; self->get_timestamp = NULL; self->set_last_set_timediff = NULL; self->get_last_set_timediff = NULL; } gint64 cb_twitter_item_get_sort_factor (CbTwitterItem *self) { CbTwitterItemInterface *iface; g_return_val_if_fail (CB_IS_TWITTER_ITEM (self), 0); iface = CB_TWITTER_ITEM_GET_IFACE (self); return iface->get_sort_factor (self); } int cb_twitter_item_update_time_delta (CbTwitterItem *self, GDateTime *now) { CbTwitterItemInterface *iface; g_return_val_if_fail (CB_IS_TWITTER_ITEM (self), 0); iface = CB_TWITTER_ITEM_GET_IFACE (self); return iface->update_time_delta (self, now); } gint64 cb_twitter_item_get_timestamp (CbTwitterItem *self) { CbTwitterItemInterface *iface; g_return_val_if_fail (CB_IS_TWITTER_ITEM (self), 0); iface = CB_TWITTER_ITEM_GET_IFACE (self); return iface->get_timestamp (self); } void cb_twitter_item_set_last_set_timediff (CbTwitterItem *self, GTimeSpan span) { CbTwitterItemInterface *iface; g_return_if_fail (CB_IS_TWITTER_ITEM (self)); iface = CB_TWITTER_ITEM_GET_IFACE (self); iface->set_last_set_timediff (self, span); } GTimeSpan cb_twitter_item_get_last_set_timediff (CbTwitterItem *self) { CbTwitterItemInterface *iface; g_return_val_if_fail (CB_IS_TWITTER_ITEM (self), 0); iface = CB_TWITTER_ITEM_GET_IFACE (self); return iface->get_last_set_timediff (self); } corebird-1.7.4/src/CbTwitterItem.h000066400000000000000000000036231324604713000170060ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef _CB_TWITTER_ITEM_H_ #define _CB_TWITTER_ITEM_H_ #include #define CB_TYPE_TWITTER_ITEM (cb_twitter_item_get_type()) G_DECLARE_INTERFACE (CbTwitterItem, cb_twitter_item, CB, TWITTER_ITEM, GObject) struct _CbTwitterItemInterface { GTypeInterface base_iface; gint64 (*get_sort_factor) (CbTwitterItem *self); int (*update_time_delta) (CbTwitterItem *self, GDateTime *now); gint64 (*get_timestamp) (CbTwitterItem *self); void (*set_last_set_timediff) (CbTwitterItem *self, GTimeSpan span); GTimeSpan (*get_last_set_timediff) (CbTwitterItem *self); }; gint64 cb_twitter_item_get_sort_factor (CbTwitterItem *self); int cb_twitter_item_update_time_delta (CbTwitterItem *self, GDateTime *now); gint64 cb_twitter_item_get_timestamp (CbTwitterItem *self); /* Basically just for CbDeltaUpdater */ void cb_twitter_item_set_last_set_timediff (CbTwitterItem *self, GTimeSpan span); GTimeSpan cb_twitter_item_get_last_set_timediff (CbTwitterItem *self); #endif corebird-1.7.4/src/CbTypes.c000066400000000000000000000465521324604713000156340ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2016 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbTypes.h" #include "CbMediaDownloader.h" #include "CbUtils.h" #include #include void cb_user_identity_free (CbUserIdentity *id) { g_free (id->screen_name); g_free (id->user_name); } void cb_user_identity_copy (const CbUserIdentity *id, CbUserIdentity *id2) { g_free (id2->screen_name); id2->screen_name = g_strdup (id->screen_name); g_free (id2->user_name); id2->user_name = g_strdup (id->user_name); id2->id = id->id; id2->verified = id->verified; } void cb_user_identity_parse (CbUserIdentity *id, JsonObject *user_obj) { id->id = json_object_get_int_member (user_obj, "id"); id->screen_name = g_strdup (json_object_get_string_member (user_obj, "screen_name")); id->user_name = cb_utils_escape_ampersands (json_object_get_string_member (user_obj, "name")); id->verified = json_object_get_boolean_member (user_obj, "verified"); } void cb_text_entity_free (CbTextEntity *e) { g_free (e->display_text); g_free (e->tooltip_text); g_free (e->target); } void cb_text_entity_copy (const CbTextEntity *e1, CbTextEntity *e2) { e2->from = e1->from; e2->to = e1->to; g_free (e2->display_text); e2->display_text = g_strdup (e1->display_text); g_free (e2->tooltip_text); e2->tooltip_text = g_strdup (e1->tooltip_text); g_free (e2->target); e2->target = g_strdup (e1->target); e2->info = e1->info; } void cb_mini_tweet_free (CbMiniTweet *t) { guint i; g_free (t->text); for (i = 0; i < t->n_medias; i ++) g_object_unref (t->medias[i]); g_free (t->medias); for (i = 0; i < t->n_entities; i ++) cb_text_entity_free (&t->entities[i]); g_free (t->entities); for (i = 0; i < t->n_reply_users; i ++) cb_user_identity_free (&t->reply_users[i]); g_free (t->reply_users); cb_user_identity_free (&t->author); } void cb_mini_tweet_copy (const CbMiniTweet *t1, CbMiniTweet *t2) { guint i; t2->id = t1->id; t2->created_at = t1->created_at; cb_user_identity_copy (&t1->author, &t2->author); g_free (t2->text); t2->text = g_strdup (t1->text); t2->n_entities = t1->n_entities; t2->entities = g_new0 (CbTextEntity, t2->n_entities); for (i = 0; i < t2->n_entities; i ++) cb_text_entity_copy (&t1->entities[i], &t2->entities[i]); t2->n_medias = t1->n_medias; t2->medias = g_new0 (CbMedia*, t2->n_medias); for (i = 0; i < t2->n_medias; i ++) t2->medias[i] = g_object_ref (t1->medias[i]); } void cb_mini_tweet_init (CbMiniTweet *t) { t->reply_id = 0; t->medias = NULL; t->n_medias = 0; t->entities = NULL; t->n_entities = 0; t->reply_users = NULL; t->n_reply_users = 0; } void cb_mini_tweet_parse (CbMiniTweet *t, JsonObject *obj) { GDateTime *time; JsonObject *extended_object; const char *tweet_text; if (json_object_has_member (obj, "extended_tweet")) extended_object = json_object_get_object_member (obj, "extended_tweet"); else extended_object = obj; time = cb_utils_parse_date (json_object_get_string_member (obj, "created_at")); t->id = json_object_get_int_member (obj, "id"); if (json_object_has_member (extended_object, "full_text")) tweet_text = json_object_get_string_member (extended_object, "full_text"); else tweet_text = json_object_get_string_member (extended_object, "text"); if (json_object_has_member (extended_object, "display_text_range")) { /* We only remove the prefix */ guint start = (guint)json_array_get_int_element ( json_object_get_array_member (extended_object, "display_text_range"), 0); guint i; const char *p = tweet_text; /* Skip ahead */ for (i = 0; i < start; i ++) p = g_utf8_next_char (p); t->text = g_strdup (p); t->display_range_start = start; } else { t->text = g_strdup (tweet_text); t->display_range_start= 0; } t->created_at = g_date_time_to_unix (time); cb_user_identity_parse (&t->author, json_object_get_object_member (obj, "user")); g_date_time_unref (time); } static int json_object_get_member_size (JsonObject *obj, const char *member_name) { if (!obj || !json_object_has_member (obj, member_name)) return 0; return (int)json_array_get_length (json_object_get_array_member (obj, member_name)); } void cb_mini_tweet_parse_entities (CbMiniTweet *t, JsonObject *status) { JsonObject *extended_obj = status; JsonObject *entities; JsonArray *urls; JsonArray *hashtags; JsonArray *user_mentions; JsonArray *media_arrays[2]; int media_count; guint i, p; int url_index = 0; guint n_media_arrays = 0; guint n_reply_users = 0; guint non_reply_mentions = 0; int max_entities; gboolean direct_duplicate = FALSE; if (json_object_has_member (status, "extended_tweet")) extended_obj = json_object_get_object_member (status, "extended_tweet"); entities = json_object_get_object_member (extended_obj, "entities"); urls = json_object_get_array_member (entities, "urls"); hashtags = json_object_get_array_member (entities, "hashtags"); user_mentions = json_object_get_array_member (entities, "user_mentions"); media_count = json_object_get_member_size (entities, "media"); if (json_object_has_member (status, "extended_entities")) media_count += json_object_get_member_size (json_object_get_object_member (status, "extended_entities"), "media"); if (json_object_has_member (status, "in_reply_to_status_id") && !json_object_get_null_member (status, "in_reply_to_status_id")) { guint reply_index = 0; gint64 reply_to_user_id = 0; reply_to_user_id = json_object_get_int_member (status, "in_reply_to_user_id"); /* Check how many of the user mentions are reply mentions */ t->reply_id = json_object_get_int_member (status, "in_reply_to_status_id"); for (i = 0, p = json_array_get_length (user_mentions); i < p; i ++) { JsonObject *mention = json_node_get_object (json_array_get_element (user_mentions, i)); JsonArray *indices = json_object_get_array_member (mention, "indices"); gint64 user_id = json_object_get_int_member (mention, "id"); if (json_array_get_int_element (indices, 1) <= t->display_range_start) n_reply_users ++; else break; if (i == 0 && user_id == reply_to_user_id) direct_duplicate = TRUE; } if (!direct_duplicate) n_reply_users ++; t->reply_users = g_new0 (CbUserIdentity, n_reply_users); t->n_reply_users = n_reply_users; if (!direct_duplicate) { t->reply_users[0].id = reply_to_user_id; t->reply_users[0].screen_name = g_strdup (json_object_get_string_member (status, "in_reply_to_screen_name")); t->reply_users[0].user_name = g_strdup (""); reply_index = 1; } /* Now fill ->reply_users. The very first entry is always the user this tweet * *actually* replies to. */ for (i = 0; i < n_reply_users - (direct_duplicate ? 0 : 1); i ++) { JsonObject *mention = json_node_get_object (json_array_get_element (user_mentions, i)); t->reply_users[reply_index].id = json_object_get_int_member (mention, "id"); t->reply_users[reply_index].screen_name = g_strdup (json_object_get_string_member (mention, "screen_name")); t->reply_users[reply_index].user_name = g_strdup (json_object_get_string_member (mention, "name")); reply_index ++; } non_reply_mentions = n_reply_users - 1; } max_entities = json_array_get_length (urls) + json_array_get_length (hashtags) + json_array_get_length (user_mentions) - non_reply_mentions + media_count; media_count += (int)json_array_get_length (urls); t->medias = g_new0 (CbMedia*, media_count); t->entities = g_new0 (CbTextEntity, max_entities); /* * TODO: display_text and tooltip_text are often the same here, can we just set them to the * same value and only free one? */ /* URLS */ for (i = 0, p = json_array_get_length (urls); i < p; i ++) { JsonObject *url = json_node_get_object (json_array_get_element (urls, i)); const char *expanded_url = json_object_get_string_member (url, "expanded_url"); JsonArray *indices; if (is_media_candidate (expanded_url)) { t->medias[t->n_medias] = cb_media_new (); t->medias[t->n_medias]->url = g_strdup (expanded_url); t->medias[t->n_medias]->type = cb_media_type_from_url (expanded_url); t->medias[t->n_medias]->target_url = g_strdup (expanded_url); t->n_medias ++; } indices = json_object_get_array_member (url, "indices"); t->entities[url_index].from = json_array_get_int_element (indices, 0); t->entities[url_index].to = json_array_get_int_element (indices, 1); t->entities[url_index].display_text = cb_utils_escape_ampersands (json_object_get_string_member (url, "display_url")); t->entities[url_index].tooltip_text = cb_utils_escape_ampersands (expanded_url); t->entities[url_index].target = cb_utils_escape_ampersands (expanded_url); url_index ++; } /* HASHTAGS */ for (i = 0, p = json_array_get_length (hashtags); i < p; i ++) { JsonObject *hashtag = json_node_get_object (json_array_get_element (hashtags, i)); JsonArray *indices = json_object_get_array_member (hashtag, "indices"); const char *text = json_object_get_string_member (hashtag, "text"); t->entities[url_index].from = json_array_get_int_element (indices, 0); t->entities[url_index].to = json_array_get_int_element (indices, 1); t->entities[url_index].display_text = g_strdup_printf ("#%s", text); t->entities[url_index].tooltip_text = g_strdup_printf ("#%s", text); t->entities[url_index].target = NULL; url_index ++; } /* USER MENTIONS */ if (direct_duplicate) i = n_reply_users; else i = n_reply_users == 0 ? 0 : n_reply_users - 1; for (p = json_array_get_length (user_mentions); i < p; i ++) { JsonObject *mention = json_node_get_object (json_array_get_element (user_mentions, i)); JsonArray *indices = json_object_get_array_member (mention, "indices"); const char *screen_name = json_object_get_string_member (mention, "screen_name"); const char *id_str = json_object_get_string_member (mention, "id_str"); t->entities[url_index].from = json_array_get_int_element (indices, 0); t->entities[url_index].to = json_array_get_int_element (indices, 1); t->entities[url_index].display_text = g_strdup_printf ("@%s", screen_name); t->entities[url_index].tooltip_text = cb_utils_escape_ampersands (json_object_get_string_member (mention, "name")); t->entities[url_index].target = g_strdup_printf ("@%s/@%s", id_str, screen_name); url_index ++; } /* MEDIA */ if (json_object_has_member (entities, "media")) { JsonArray *medias = json_object_get_array_member (entities, "media"); for (i = 0, p = json_array_get_length (medias); i < p; i ++) { JsonObject *url = json_node_get_object (json_array_get_element (medias, i)); JsonArray *indices = json_object_get_array_member (url, "indices"); char *url_str = cb_utils_escape_ampersands (json_object_get_string_member (url, "url")); int k; gboolean duplicate = FALSE; /* Check for duplicates */ for (k = 0; k < url_index; k ++) { const char *target = t->entities[k].target; if (target != NULL && strcmp (target, url_str) == 0) { duplicate = TRUE; break; } } if (duplicate) { g_free (url_str); continue; } t->entities[url_index].from = json_array_get_int_element (indices, 0); t->entities[url_index].to = json_array_get_int_element (indices, 1); t->entities[url_index].display_text = cb_utils_escape_ampersands (json_object_get_string_member (url, "display_url")); t->entities[url_index].target = url_str; url_index ++; } } /* entities->media and extended_entities contain exactly the same media objects, but extended_entities is not always present, and entities->media doesn't contain all the attached media, so parse both the same way... */ if (json_object_has_member (entities, "media")) { media_arrays[n_media_arrays] = json_object_get_array_member (entities, "media"); n_media_arrays ++; } if (json_object_has_member (status, "extended_entities")) { media_arrays[n_media_arrays] = json_object_get_array_member (json_object_get_object_member (status, "extended_entities"), "media"); n_media_arrays ++; } for (i = 0; i < n_media_arrays; i ++) { guint x, k; for (x = 0, p = json_array_get_length (media_arrays[i]); x < p; x ++) { JsonObject *media_obj = json_node_get_object (json_array_get_element (media_arrays[i], x)); const char *media_type = json_object_get_string_member (media_obj, "type"); if (strcmp (media_type, "photo") == 0) { const char *url = json_object_get_string_member (media_obj, "media_url"); gboolean dup = FALSE; /* Remove duplicates */ for (k = 0; k < t->n_medias; k ++) { if (t->medias[k] != NULL && strcmp (t->medias[k]->url, url) == 0) { dup = TRUE; break; } } if (dup) continue; if (is_media_candidate (url)) { t->medias[t->n_medias] = cb_media_new (); t->medias[t->n_medias]->type = CB_MEDIA_TYPE_IMAGE; t->medias[t->n_medias]->url = g_strdup (url); t->medias[t->n_medias]->target_url = g_strdup_printf ("%s:orig", url); if (json_object_has_member (media_obj, "sizes")) { JsonObject *sizes = json_object_get_object_member (media_obj, "sizes"); JsonObject *medium = json_object_get_object_member (sizes, "medium"); t->medias[t->n_medias]->width = json_object_get_int_member (medium, "w"); t->medias[t->n_medias]->height = json_object_get_int_member (medium, "h"); } t->n_medias ++; } } else if (strcmp (media_type, "video") == 0 || strcmp (media_type, "animated_gif") == 0) { JsonObject *video_info = json_object_get_object_member (media_obj, "video_info"); JsonArray *variants = json_object_get_array_member (video_info, "variants"); JsonObject *variant = NULL; int thumb_width = -1; int thumb_height = -1; guint q; if (json_object_has_member (media_obj, "sizes")) { JsonObject *sizes = json_object_get_object_member (media_obj, "sizes"); JsonObject *medium = json_object_get_object_member (sizes, "medium"); thumb_width = json_object_get_int_member (medium, "w"); thumb_height = json_object_get_int_member (medium, "h"); } for (k = 0, q = json_array_get_length (variants); k < q; k ++) { JsonObject *v = json_node_get_object (json_array_get_element (variants, k)); if (strcmp (json_object_get_string_member (v, "content_type"), "application/x-mpegURL") == 0) { variant = v; break; } } if (variant == NULL && json_array_get_length (variants) > 0) variant = json_node_get_object (json_array_get_element (variants, 0)); if (variant != NULL) { int n_media = t->n_medias; const char *thumb_url = json_object_get_string_member (media_obj, "media_url"); /* Some tweets have both a video and a thumbnail for that video attached. The tweet json * will list the image first. The url of the image and the thumb_url of the video will match */ for (k = 0; k < t->n_medias; k ++) { if (t->medias[k] != NULL && t->medias[k]->type == CB_MEDIA_TYPE_IMAGE && strcmp (t->medias[k]->url, thumb_url) == 0) { /* Replace this media */ g_object_unref (t->medias[k]); n_media = k; break; } } t->medias[n_media] = cb_media_new (); t->medias[n_media]->url = g_strdup (json_object_get_string_member (variant, "url")); t->medias[n_media]->thumb_url = g_strdup (thumb_url); t->medias[n_media]->type = CB_MEDIA_TYPE_TWITTER_VIDEO; t->medias[n_media]->width = thumb_width; t->medias[n_media]->height = thumb_height; if (n_media == t->n_medias) t->n_medias ++; } } else { g_debug ("Unhandled media type: %s", media_type); } } } t->n_entities = url_index; #if 0 g_debug ("Wasted entities: %d", max_entities - t->n_entities); g_debug ("Wasted media : %d", media_count - t->n_medias); #endif if (t->n_medias > 0) cb_media_downloader_load_all (cb_media_downloader_get_default (), t); if (t->n_entities > 0) { guint i, k; /* Sort entities. */ for (i = 0; i < t->n_entities; i ++) for (k = 0; k < t->n_entities; k++) if (t->entities[i].from < t->entities[k].from) { CbTextEntity tmp = { 0 }; cb_text_entity_copy (&t->entities[i], &tmp); cb_text_entity_copy (&t->entities[k], &t->entities[i]); cb_text_entity_copy (&tmp, &t->entities[k]); cb_text_entity_free (&tmp); } } } corebird-1.7.4/src/CbTypes.h000066400000000000000000000061341324604713000156310ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2016 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef __TYPES_H #define __TYPES_H #include #include #include "CbMedia.h" typedef enum { CB_STREAM_MESSAGE_UNSUPPORTED, CB_STREAM_MESSAGE_DELETE, CB_STREAM_MESSAGE_DM_DELETE, CB_STREAM_MESSAGE_SCRUB_GEO, CB_STREAM_MESSAGE_LIMIT, CB_STREAM_MESSAGE_DISCONNECT, CB_STREAM_MESSAGE_FRIENDS, CB_STREAM_MESSAGE_EVENT, CB_STREAM_MESSAGE_WARNING, CB_STREAM_MESSAGE_DIRECT_MESSAGE, CB_STREAM_MESSAGE_TWEET, CB_STREAM_MESSAGE_EVENT_LIST_CREATED, CB_STREAM_MESSAGE_EVENT_LIST_DESTROYED, CB_STREAM_MESSAGE_EVENT_LIST_UPDATED, CB_STREAM_MESSAGE_EVENT_LIST_UNSUBSCRIBED, CB_STREAM_MESSAGE_EVENT_LIST_SUBSCRIBED, CB_STREAM_MESSAGE_EVENT_LIST_MEMBER_ADDED, CB_STREAM_MESSAGE_EVENT_LIST_MEMBER_REMOVED, CB_STREAM_MESSAGE_EVENT_FAVORITE, CB_STREAM_MESSAGE_EVENT_UNFAVORITE, CB_STREAM_MESSAGE_EVENT_FOLLOW, CB_STREAM_MESSAGE_EVENT_UNFOLLOW, CB_STREAM_MESSAGE_EVENT_BLOCK, CB_STREAM_MESSAGE_EVENT_UNBLOCK, CB_STREAM_MESSAGE_EVENT_MUTE, CB_STREAM_MESSAGE_EVENT_UNMUTE, CB_STREAM_MESSAGE_EVENT_USER_UPDATE, CB_STREAM_MESSAGE_EVENT_QUOTED_TWEET } CbStreamMessageType; struct _CbUserIdentity { gint64 id; guint verified : 1; char *screen_name; char *user_name; }; typedef struct _CbUserIdentity CbUserIdentity; void cb_user_identity_free (CbUserIdentity *id); void cb_user_identity_copy (const CbUserIdentity *id, CbUserIdentity *id2); void cb_user_identity_parse (CbUserIdentity *id, JsonObject *user_obj); struct _CbTextEntity { guint from; guint to; guint info : 1; char *display_text; char *tooltip_text; char *target; }; typedef struct _CbTextEntity CbTextEntity; void cb_text_entity_free (CbTextEntity *e); void cb_text_entity_copy (const CbTextEntity *e1, CbTextEntity *e2); struct _CbMiniTweet { gint64 id; gint64 created_at; guint display_range_start; CbUserIdentity author; char *text; gint64 reply_id; CbTextEntity *entities; guint n_entities; CbMedia **medias; guint n_medias; CbUserIdentity *reply_users; guint n_reply_users; }; typedef struct _CbMiniTweet CbMiniTweet; void cb_mini_tweet_free (CbMiniTweet *tweet); void cb_mini_tweet_copy (const CbMiniTweet *t1, CbMiniTweet *t2); void cb_mini_tweet_init (CbMiniTweet *t); void cb_mini_tweet_parse (CbMiniTweet *t, JsonObject *obj); void cb_mini_tweet_parse_entities (CbMiniTweet *t, JsonObject *obj); #endif corebird-1.7.4/src/CbUserCompletionModel.c000066400000000000000000000131641324604713000204520ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbUserCompletionModel.h" static void cb_user_completion_model_iface_init (GListModelInterface *iface); G_DEFINE_TYPE_WITH_CODE (CbUserCompletionModel, cb_user_completion_model, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, cb_user_completion_model_iface_init)); static GType cb_user_completion_model_get_item_type (GListModel *model) { return G_TYPE_POINTER; } static guint cb_user_completion_model_get_n_items (GListModel *model) { CbUserCompletionModel *self = CB_USER_COMPLETION_MODEL (model); return self->items->len; } static gpointer cb_user_completion_model_get_item (GListModel *model, guint index) { CbUserCompletionModel *self = CB_USER_COMPLETION_MODEL (model); CbUserIdentity *id = &g_array_index (self->items, CbUserIdentity, index); g_assert (index < self->items->len); return id; } static void cb_user_completion_model_iface_init (GListModelInterface *iface) { iface->get_item_type = cb_user_completion_model_get_item_type; iface->get_n_items = cb_user_completion_model_get_n_items; iface->get_item = cb_user_completion_model_get_item; } static void cb_user_completion_model_init (CbUserCompletionModel *self) { self->items = g_array_new (FALSE, FALSE, sizeof (CbUserIdentity)); g_array_set_clear_func (self->items, (GDestroyNotify)cb_user_identity_free); } static void cb_user_completion_model_finalize (GObject *object) { CbUserCompletionModel *self = CB_USER_COMPLETION_MODEL (object); g_array_free (self->items, TRUE); G_OBJECT_CLASS (cb_user_completion_model_parent_class)->finalize (object); } static void cb_user_completion_model_class_init (CbUserCompletionModelClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = cb_user_completion_model_finalize; } CbUserCompletionModel * cb_user_completion_model_new (void) { return CB_USER_COMPLETION_MODEL (g_object_new (CB_TYPE_USER_COMPLETION_MODEL, NULL)); } static inline gboolean cb_user_completion_model_has_id (CbUserCompletionModel *self, guint64 id) { guint x; /* Look for duplicate */ for (x = 0; x < self->items->len; x ++) { const CbUserIdentity *existing_id = &g_array_index (self->items, CbUserIdentity, x); if (existing_id->id == id) return TRUE; } return FALSE; } static inline void emit_items_changed (CbUserCompletionModel *self, guint position, guint removed, guint added) { g_list_model_items_changed (G_LIST_MODEL (self), position, removed, added); } void cb_user_completion_model_insert_items (CbUserCompletionModel *self, CbUserIdentity *ids, guint ids_len) { guint i; guint size_before; guint added = 0; g_return_if_fail (CB_IS_USER_COMPLETION_MODEL (self)); if (ids_len == 0) return; size_before = self->items->len; for (i = 0; i < ids_len; i ++) { CbUserIdentity *id = &ids[i]; CbUserIdentity *new_id; if (cb_user_completion_model_has_id (self, id->id)) continue; g_array_set_size (self->items, self->items->len + 1); new_id = &g_array_index (self->items, CbUserIdentity, self->items->len - 1); new_id->id = id->id; new_id->screen_name = g_steal_pointer (&id->screen_name); new_id->user_name = g_steal_pointer (&id->user_name); new_id->verified = id->verified; added ++; } emit_items_changed (self, size_before, 0, added); } void cb_user_completion_model_insert_infos (CbUserCompletionModel *self, CbUserInfo *infos, guint infos_len) { guint i; guint size_before; guint added = 0; g_return_if_fail (CB_IS_USER_COMPLETION_MODEL (self)); if (infos_len == 0) return; size_before = self->items->len; for (i = 0; i < infos_len; i ++) { CbUserInfo *info = &infos[i]; CbUserIdentity *new_id; if (cb_user_completion_model_has_id (self, info->user_id)) continue; g_array_set_size (self->items, self->items->len + 1); new_id = &g_array_index (self->items, CbUserIdentity, self->items->len - 1); new_id->id = info->user_id; new_id->screen_name = g_steal_pointer (&info->screen_name); new_id->user_name = g_steal_pointer (&info->user_name); new_id->verified = FALSE; added ++; } emit_items_changed (self, size_before, 0, added); } void cb_user_completion_model_clear (CbUserCompletionModel *self) { guint old_size; g_return_if_fail (CB_IS_USER_COMPLETION_MODEL (self)); old_size = self->items->len; g_array_remove_range (self->items, 0, self->items->len); emit_items_changed (self, 0, old_size, 0); } corebird-1.7.4/src/CbUserCompletionModel.h000066400000000000000000000036771324604713000204670ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef _CB_USER_COMPLETION_MODEL_H #define _CB_USER_COMPLETION_MODEL_H #include "CbTypes.h" #include "CbUserCounter.h" #include #include struct _CbUserCompletionModel { GObject parent_instance; GArray *items; }; typedef struct _CbUserCompletionModel CbUserCompletionModel; #define CB_TYPE_USER_COMPLETION_MODEL cb_user_completion_model_get_type () G_DECLARE_FINAL_TYPE (CbUserCompletionModel, cb_user_completion_model, CB, USER_COMPLETION_MODEL, GObject); CbUserCompletionModel * cb_user_completion_model_new (void); void cb_user_completion_model_insert_items (CbUserCompletionModel *self, CbUserIdentity *ids, guint ids_len); void cb_user_completion_model_insert_infos (CbUserCompletionModel *self, CbUserInfo *infos, guint infos_len); void cb_user_completion_model_clear (CbUserCompletionModel *self); #endif corebird-1.7.4/src/CbUserCounter.c000066400000000000000000000213061324604713000167740ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbUserCounter.h" #include void cb_user_info_destroy (CbUserInfo *info) { g_free (info->screen_name); g_free (info->user_name); } static int score_sort (gconstpointer p1, gconstpointer p2) { const CbUserInfo *ui1 = p1; const CbUserInfo *ui2 = p2; return ui1->score < ui2->score; } G_DEFINE_TYPE (CbUserCounter, cb_user_counter, G_TYPE_OBJECT); CbUserCounter * cb_user_counter_new (void) { return CB_USER_COUNTER (g_object_new (CB_TYPE_USER_COUNTER, NULL)); } void cb_user_counter_id_seen (CbUserCounter *counter, const CbUserIdentity *id) { g_return_if_fail (CB_IS_USER_COUNTER (counter)); g_return_if_fail (id != NULL); cb_user_counter_user_seen (counter, id->id, id->screen_name, id->user_name); } void cb_user_counter_user_seen (CbUserCounter *counter, gint64 user_id, const char *screen_name, const char *user_name) { gboolean found = FALSE; guint i; g_return_if_fail (CB_IS_USER_COUNTER (counter)); g_return_if_fail (screen_name != NULL); g_return_if_fail (user_name != NULL); for (i = 0; i < counter->user_infos->len; i ++) { CbUserInfo *ui = &g_array_index (counter->user_infos, CbUserInfo, i); if (ui->user_id == user_id) { ui->score ++; ui->changed = TRUE; found = TRUE; break; } } if (!found) { CbUserInfo *ui; g_array_set_size (counter->user_infos, counter->user_infos->len + 1); ui = &g_array_index (counter->user_infos, CbUserInfo, counter->user_infos->len - 1); ui->user_id = user_id; ui->screen_name = g_strdup (screen_name); ui->user_name = g_strdup (user_name); ui->changed = TRUE; /* Because we just inserted this, eh */ ui->score = 1; } } int cb_user_counter_save (CbUserCounter *counter, sqlite3 *db) { int count = 0; guint i; g_return_val_if_fail (CB_IS_USER_COUNTER (counter), 0); g_return_val_if_fail (db != NULL, 0); sqlite3_exec (db, "BEGIN TRANSACTION;", NULL, NULL, NULL); for (i = 0; i < counter->user_infos->len; i ++) { sqlite3_stmt *stmt; int ok; CbUserInfo *ui = &g_array_index (counter->user_infos, CbUserInfo, i); if (!ui->changed) continue; ui->changed = FALSE; /* Actually save entry in DB */ ok = sqlite3_prepare_v2 (db, "INSERT OR REPLACE INTO `user_cache` (id, screen_name, user_name, score) " "VALUES(?, ?, ?, ?)", -1, &stmt, NULL); if (ok != SQLITE_OK) { g_warning ("SQL Error: %s", sqlite3_errmsg (db)); continue; } sqlite3_bind_int64 (stmt, 1, ui->user_id); sqlite3_bind_text (stmt, 2, ui->screen_name, -1, NULL); sqlite3_bind_text (stmt, 3, ui->user_name, -1, NULL); sqlite3_bind_int (stmt, 4, ui->score); ok = sqlite3_step (stmt); if (ok != SQLITE_DONE) g_critical ("%s", sqlite3_errstr (ok)); sqlite3_finalize (stmt); count ++; } sqlite3_exec (db, "END TRANSACTION;", NULL, NULL, NULL); counter->changed = FALSE; g_array_remove_range (counter->user_infos, 0, counter->user_infos->len); return count; } static int query_sqlite_cb (void *user_data, int n_columns, char **columns, char **column_names) { struct { GArray *infos; int lowest_score; } *query_data = user_data; CbUserInfo *ui; guint i; gint64 user_id; g_assert (n_columns == 4); user_id = strtoull (columns[0], NULL, 10); /* Check for duplicates first */ for (i = 0; i < query_data->infos->len; i ++) { const CbUserInfo *_ui = &g_array_index (query_data->infos, CbUserInfo, i); if (_ui->user_id == user_id) return 0; } g_array_set_size (query_data->infos, query_data->infos->len + 1); ui = &g_array_index (query_data->infos, CbUserInfo, query_data->infos->len - 1); ui->user_id = user_id; ui->screen_name = g_strdup (columns[1]); ui->user_name = g_strdup (columns[2]); ui->score = atoi (columns[3]); query_data->lowest_score = MIN (query_data->lowest_score, ui->score); return 0; /* Go on */ } void cb_user_counter_query_by_prefix (CbUserCounter *counter, sqlite3 *db, const char *prefix, int max_results, CbUserInfo **results, int *n_results) { char *sql; char *err; int i; struct { GArray *infos; int lowest_score; } query_data; g_return_if_fail (CB_IS_USER_COUNTER (counter)); g_return_if_fail (prefix != NULL); g_return_if_fail (results != NULL); g_return_if_fail (max_results > 0); g_return_if_fail (n_results != NULL); query_data.infos = g_array_new (FALSE, TRUE, sizeof (CbUserInfo)); query_data.lowest_score = G_MAXINT; g_array_set_clear_func (query_data.infos, (GDestroyNotify)cb_user_info_destroy); for (i = 0; i < counter->user_infos->len; i ++) { const CbUserInfo *ui = &g_array_index (counter->user_infos, CbUserInfo, i); char *user_name; char *screen_name; gboolean full = query_data.infos->len >= max_results; /* Will already be in the results from the sql query */ if (!ui->changed) continue; if (full && ui->score < query_data.lowest_score) continue; screen_name = g_utf8_strdown (ui->screen_name, -1); user_name = g_utf8_strdown (ui->user_name, -1); if (g_str_has_prefix (screen_name, prefix) || g_str_has_prefix (user_name, prefix)) { CbUserInfo *new_ui; /* Copy user info into result array */ g_array_set_size (query_data.infos, query_data.infos->len + 1); new_ui = &g_array_index (query_data.infos, CbUserInfo, query_data.infos->len - 1); new_ui->user_id = ui->user_id; new_ui->screen_name = g_strdup (ui->screen_name); new_ui->user_name = g_strdup (ui->user_name); new_ui->score = ui->score; query_data.lowest_score = MIN (query_data.lowest_score, ui->score); } g_free (user_name); g_free (screen_name); } if (query_data.infos->len == 0) query_data.lowest_score = -1; sql = g_strdup_printf ("SELECT `id`, `screen_name`, `user_name`, `score` " "FROM `user_cache` WHERE `screen_name` LIKE '%s%%' " "OR `user_name` LIKE '%s%%' ORDER BY `score` DESC LIMIT %d " "COLLATE NOCASE;", prefix, prefix, max_results); sqlite3_exec (db, sql, query_sqlite_cb, &query_data, &err); if (err != NULL) { g_critical ("%s SQL Error: %s", __FUNCTION__, err); sqlite3_free (err); } /* Now sort after score */ g_array_sort (query_data.infos, score_sort); /* Remove everything after max_results */ if (query_data.infos->len > max_results) g_array_remove_range (query_data.infos, max_results, query_data.infos->len - max_results); g_assert (query_data.infos->len <= max_results); /* Just use the GArray's data */ *n_results = query_data.infos->len; *results = (CbUserInfo*) g_array_free (query_data.infos, FALSE); g_free (sql); } static void cb_user_counter_finalize (GObject *obj) { CbUserCounter *counter = CB_USER_COUNTER (obj); g_array_free (counter->user_infos, TRUE); G_OBJECT_CLASS (cb_user_counter_parent_class)->finalize (obj); } static void cb_user_counter_class_init (CbUserCounterClass *counter_class) { GObjectClass *object_class = G_OBJECT_CLASS (counter_class); object_class->finalize = cb_user_counter_finalize; } static void cb_user_counter_init (CbUserCounter *counter) { counter->changed = FALSE; counter->user_infos = g_array_new (FALSE, TRUE, sizeof (CbUserInfo)); g_array_set_clear_func (counter->user_infos, (GDestroyNotify)cb_user_info_destroy); } corebird-1.7.4/src/CbUserCounter.h000066400000000000000000000044031324604713000170000ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef USER_COUNTER_H #define USER_COUNTER_H #include #include #include "CbTypes.h" G_BEGIN_DECLS typedef struct _CbUserInfo CbUserInfo; struct _CbUserInfo { gint64 user_id; char *screen_name; char *user_name; guint score; guint changed : 1; }; typedef struct _CbUserCounter CbUserCOunter; struct _CbUserCounter { GObject parent_instance; guint changed : 1; GArray *user_infos; }; #define CB_TYPE_USER_COUNTER cb_user_counter_get_type () G_DECLARE_FINAL_TYPE (CbUserCounter, cb_user_counter, CB, USER_COUNTER, GObject); GType cb_user_counter_get_type (void) G_GNUC_CONST; CbUserCounter * cb_user_counter_new (void); void cb_user_counter_id_seen (CbUserCounter *counter, const CbUserIdentity *id); void cb_user_counter_user_seen (CbUserCounter *counter, gint64 user_id, const char *screen_name, const char *user_name); int cb_user_counter_save (CbUserCounter *counter, sqlite3 *db); void cb_user_counter_query_by_prefix (CbUserCounter *counter, sqlite3 *db, const char *prefix, int max_results, CbUserInfo **results, int *n_results); /* CbUserInfo */ void cb_user_info_destroy (CbUserInfo *info); G_END_DECLS #endif corebird-1.7.4/src/CbUserStream.c000066400000000000000000000370021324604713000166100ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbUserStream.h" #include "CbUtils.h" #include "rest/rest/oauth-proxy.h" #include G_DEFINE_TYPE (CbUserStream, cb_user_stream, G_TYPE_OBJECT); enum { INTERRUPTED, RESUMED, LAST_SIGNAL }; enum { STATE_STOPPED, /* Initial state */ STATE_RUNNING, /* Started and message received */ STATE_STARTED, /* Started, but no message/heartbeat received yet */ STATE_STOPPING, /* Stopping the stream */ }; static guint user_stream_signals[LAST_SIGNAL] = { 0 }; static void cb_user_stream_finalize (GObject *o) { CbUserStream *self = CB_USER_STREAM (o); cb_user_stream_stop (self); g_ptr_array_unref (self->receivers); g_string_free (self->data, TRUE); g_free (self->account_name); if (self->network_changed_id != 0) { g_signal_handler_disconnect (self->network_monitor, self->network_changed_id); } G_OBJECT_CLASS (cb_user_stream_parent_class)->finalize (o); } static void cb_user_stream_restart (CbUserStream *self) { self->restarting = TRUE; cb_user_stream_stop (self); cb_user_stream_start (self); } static gboolean network_cb (gpointer user_data) { CbUserStream *self = user_data; gboolean available; if (self->state == STATE_RUNNING) { self->network_timeout_id = 0; return G_SOURCE_REMOVE; } available = g_network_monitor_get_network_available (self->network_monitor); if (available) { g_debug ("%u Restarting stream (reason: network available (timeout))", self->state); self->network_timeout_id = 0; cb_user_stream_restart (self); return G_SOURCE_REMOVE; } return G_SOURCE_CONTINUE; } static void start_network_timeout (CbUserStream *self) { if (self->network_timeout_id != 0) return; self->network_timeout_id = g_timeout_add (1 * 1000, network_cb, self); } static void network_changed_cb (GNetworkMonitor *monitor, gboolean available, gpointer user_data) { CbUserStream *self = user_data; if (available == self->network_available) return; self->network_available = available; if (available) { g_debug ("%u Restarting stream (reason: Network available (callback))", self->state); cb_user_stream_restart (self); } else { g_debug ("%u Connection lost (%s) Reason: network unavailable", self->state, self->account_name); g_signal_emit (self, user_stream_signals[INTERRUPTED], 0); cb_clear_source (&self->heartbeat_timeout_id); start_network_timeout (self); } } static gboolean heartbeat_cb (gpointer user_data) { CbUserStream *self = user_data; g_debug ("%u Connection lost (%s) Reason: heartbeat. Restarting...", self->state, self->account_name); cb_user_stream_restart (self); /* We do NOT set heartbeat_timeout_id to 0 here since the _start call in the restart() above * will already create a new one... */ return G_SOURCE_REMOVE; } static void start_heartbeat_timeout (CbUserStream *self) { if (self->heartbeat_timeout_id != 0) return; self->heartbeat_timeout_id = g_timeout_add (45 * 1000, heartbeat_cb, self); } static void cb_user_stream_init (CbUserStream *self) { self->receivers = g_ptr_array_new (); self->data = g_string_new (NULL); self->restarting = FALSE; self->state = STATE_STOPPED; if (self->stresstest) { self->proxy = oauth_proxy_new ("0rvHLdbzRULZd5dz6X1TUA", "oGrvd6654nWLhzLcJywSW3pltUfkhP4BnraPPVNhHtY", "https://stream.twitter.com/", FALSE); } else { /* TODO: We should be getting these from the settings */ self->proxy = oauth_proxy_new ("0rvHLdbzRULZd5dz6X1TUA", "oGrvd6654nWLhzLcJywSW3pltUfkhP4BnraPPVNhHtY", "https://userstream.twitter.com/", FALSE); } self->proxy_data_set = FALSE; self->network_monitor = g_network_monitor_get_default (); self->network_available = g_network_monitor_get_network_available (self->network_monitor); self->network_changed_id = g_signal_connect (self->network_monitor, "network-changed", G_CALLBACK (network_changed_cb), self); if (!self->network_available) start_network_timeout (self); } static void cb_user_stream_class_init (CbUserStreamClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = cb_user_stream_finalize; user_stream_signals[INTERRUPTED] = g_signal_new ("interrupted", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); user_stream_signals[RESUMED] = g_signal_new ("resumed", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } CbUserStream * cb_user_stream_new (const char *account_name, gboolean stresstest) { CbUserStream *self = CB_USER_STREAM (g_object_new (CB_TYPE_USER_STREAM, NULL)); self->account_name = g_strdup (account_name); self->stresstest = stresstest; g_debug ("Creating stream for %s", account_name); return self; } static CbStreamMessageType get_event_type (const char *s) { gsize len = strlen (s); switch (len) { case 4: if (strcmp (s, "mute") == 0) return CB_STREAM_MESSAGE_EVENT_MUTE; break; case 5: if (strcmp (s, "block") == 0) return CB_STREAM_MESSAGE_EVENT_BLOCK; break; case 6: if (strcmp (s, "unmute") == 0) return CB_STREAM_MESSAGE_EVENT_UNMUTE; if (strcmp (s, "follow") == 0) return CB_STREAM_MESSAGE_EVENT_FOLLOW; break; case 7: if (strcmp (s, "unblock") == 0) return CB_STREAM_MESSAGE_EVENT_UNBLOCK; break; case 8: if (strcmp (s, "favorite") == 0) return CB_STREAM_MESSAGE_EVENT_FAVORITE; if (strcmp (s, "unfollow") ==0) return CB_STREAM_MESSAGE_EVENT_UNFOLLOW; break; case 10: if (strcmp (s, "unfavorite") == 0) return CB_STREAM_MESSAGE_EVENT_UNFAVORITE; break; case 11: if (strcmp (s, "user_update") == 0) return CB_STREAM_MESSAGE_EVENT_USER_UPDATE; break; case 12: if (strcmp (s, "list_created") == 0) return CB_STREAM_MESSAGE_EVENT_LIST_CREATED; if (strcmp (s, "quoted_tweet") == 0) return CB_STREAM_MESSAGE_EVENT_QUOTED_TWEET; if (strcmp (s, "list_updated") == 0) return CB_STREAM_MESSAGE_EVENT_LIST_UPDATED;; break; case 14: if (strcmp (s, "list_destroyed") == 0) return CB_STREAM_MESSAGE_EVENT_LIST_DESTROYED; break; case 17: if (strcmp (s, "list_member_added") == 0) return CB_STREAM_MESSAGE_EVENT_LIST_MEMBER_ADDED; break; case 19: if (strcmp (s, "list_member_removed") == 0) return CB_STREAM_MESSAGE_EVENT_LIST_MEMBER_REMOVED; break; case 20: if (strcmp (s, "list_user_subscribed") == 0) return CB_STREAM_MESSAGE_EVENT_LIST_SUBSCRIBED; break; case 22: if (strcmp (s, "list_user_unsubscribed") == 0) return CB_STREAM_MESSAGE_EVENT_LIST_UNSUBSCRIBED; break; } return CB_STREAM_MESSAGE_UNSUPPORTED; } static void continuous_cb (RestProxyCall *call, const gchar *buf, gsize len, const GError *error, GObject *weak_object, gpointer user_data) { CbUserStream *self = user_data; if (buf == NULL) { /* buff == NULL && error != NULL is what happens when the message gets cancelled. * This might happen a few seconds after the CbUserStream instance is finalized, so * make sure we don't use it here. */ if (error != NULL) return; if (self->state != STATE_STOPPING) { g_debug ("%u, buf(%s) == NULL. Starting timeout...", self->state, self->account_name); start_network_timeout (self); } return; } g_string_append_len (self->data, buf, len); /* Actual messages end with \r\n */ if ((len >= 2 && buf[len - 1] == '\n' && buf[len - 2] == '\r') || (len >= 1 && buf[len - 1] == '\r')) { if (self->restarting) { g_signal_emit (self, user_stream_signals[RESUMED], 0); self->restarting = FALSE; } self->state = STATE_RUNNING; /* Just \r\n messages are heartbeats. */ if (len == 2 && buf[0] == '\r' && buf[1] == '\n') { #if DEBUG char *date; GDateTime *now = g_date_time_new_now_local (); date = g_date_time_format (now, "%k:%M:%S"); g_debug ("%u HEARTBEAT (%s) %s", self->state, self->account_name, date); g_free (date); g_date_time_unref (now); #endif g_string_erase (self->data, 0, -1); cb_clear_source (&self->heartbeat_timeout_id); start_heartbeat_timeout (self); return; } /* TODO: Bring "OK" check back? */ { JsonParser *parser; JsonNode *root_node; JsonObject *root_object; CbStreamMessageType message_type; GError *error = NULL; guint i; parser = json_parser_new (); json_parser_load_from_data (parser, self->data->str, -1, &error); if (error != NULL) { g_warning ("%s: %s", __FUNCTION__, error->message); g_warning ("\n%s\n", self->data->str); g_string_erase (self->data, 0, -1); return; } root_node = json_parser_get_root (parser); root_object = json_node_get_object (root_node); message_type = CB_STREAM_MESSAGE_UNSUPPORTED; if (json_object_has_member (root_object, "text")) { message_type = CB_STREAM_MESSAGE_TWEET; } else if (json_object_has_member (root_object, "delete")) { JsonObject *d = json_object_get_object_member (root_object, "delete"); if (json_object_has_member (d, "direct_message")) message_type = CB_STREAM_MESSAGE_DM_DELETE; else message_type = CB_STREAM_MESSAGE_DELETE; } else if (json_object_has_member (root_object, "scrub_geo")) { message_type = CB_STREAM_MESSAGE_SCRUB_GEO; } else if (json_object_has_member (root_object, "limit")) { message_type = CB_STREAM_MESSAGE_LIMIT; } else if (json_object_has_member (root_object, "disconnect")) { message_type = CB_STREAM_MESSAGE_DISCONNECT; } else if (json_object_has_member (root_object, "friends")) { message_type = CB_STREAM_MESSAGE_FRIENDS; } else if (json_object_has_member (root_object, "event")) { const char *event_name = json_object_get_string_member (root_object, "event"); message_type = get_event_type (event_name); } else if (json_object_has_member (root_object, "warning")) { message_type = CB_STREAM_MESSAGE_WARNING; } else if (json_object_has_member (root_object, "direct_message")) { message_type = CB_STREAM_MESSAGE_DIRECT_MESSAGE; } else if (json_object_has_member (root_object, "status_withheld")) { message_type = CB_STREAM_MESSAGE_UNSUPPORTED; } #if DEBUG g_print ("Message with type %d on stream @%s\n", message_type, self->account_name); g_print ("%s\n\n", self->data->str); #endif for (i = 0; i < self->receivers->len; i++) cb_message_receiver_stream_message_received (g_ptr_array_index (self->receivers, i), message_type, root_node); g_object_unref (parser); g_string_erase (self->data, 0, -1); } /* Local block */ } } void cb_user_stream_start (CbUserStream *self) { g_debug ("%u Starting stream for %s", self->state, self->account_name); g_assert (self->proxy_data_set); if (self->proxy_call != NULL) rest_proxy_call_cancel (self->proxy_call); self->proxy_call = rest_proxy_new_call (self->proxy); if (self->stresstest) rest_proxy_call_set_function (self->proxy_call, "1.1/statuses/sample.json"); else rest_proxy_call_set_function (self->proxy_call, "1.1/user.json"); rest_proxy_call_set_method (self->proxy_call, "GET"); start_heartbeat_timeout (self); rest_proxy_call_continuous (self->proxy_call, continuous_cb, NULL, self, NULL/* error */); } void cb_user_stream_stop (CbUserStream *self) { g_debug ("%u Stopping %s's stream", self->state, self->account_name); cb_clear_source (&self->network_timeout_id); cb_clear_source (&self->heartbeat_timeout_id); if (self->proxy_call != NULL) { self->state = STATE_STOPPING; rest_proxy_call_cancel (self->proxy_call); g_object_unref (self->proxy_call); self->proxy_call = NULL; } self->state = STATE_STOPPED; } void cb_user_stream_set_proxy_data (CbUserStream *self, const char *token, const char *token_secret) { oauth_proxy_set_token (OAUTH_PROXY (self->proxy), token); oauth_proxy_set_token_secret (OAUTH_PROXY (self->proxy), token_secret); self->proxy_data_set = TRUE; } void cb_user_stream_register (CbUserStream *self, CbMessageReceiver *receiver) { g_ptr_array_add (self->receivers, receiver); } void cb_user_stream_unregister (CbUserStream *self, CbMessageReceiver *receiver) { guint i; for (i = 0; i < self->receivers->len; i ++) { CbMessageReceiver *r = g_ptr_array_index (self->receivers, i); if (r == receiver) { g_ptr_array_remove_index_fast (self->receivers, i); break; } } } void cb_user_stream_push_data (CbUserStream *self, const char *data) { continuous_cb (self->proxy_call, data, strlen (data), NULL, NULL, self); } corebird-1.7.4/src/CbUserStream.h000066400000000000000000000046461324604713000166250ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef __CB_USER_STREAM_H__ #define __CB_USER_STREAM_H__ #include #include "rest/rest/rest-proxy.h" #include "CbMessageReceiver.h" #include "CbTypes.h" G_BEGIN_DECLS #define CB_TYPE_USER_STREAM (cb_user_stream_get_type ()) G_DECLARE_FINAL_TYPE (CbUserStream, cb_user_stream, CB, USER_STREAM, GObject); struct _CbUserStream { GObject parent_instance; GString *data; GPtrArray *receivers; RestProxy *proxy; RestProxyCall *proxy_call; GNetworkMonitor *network_monitor; guint network_timeout_id; guint heartbeat_timeout_id; guint network_changed_id; char *account_name; guint state; guint restarting : 1; guint proxy_data_set : 1; guint network_available: 1; guint stresstest : 1; }; typedef struct _CbUserStream CbUserStream; CbUserStream * cb_user_stream_new (const char *account_name, gboolean stresstest); void cb_user_stream_set_proxy_data (CbUserStream *self, const char *token, const char *token_secret); void cb_user_stream_register (CbUserStream *self, CbMessageReceiver *receiver); void cb_user_stream_unregister (CbUserStream *self, CbMessageReceiver *receiver); void cb_user_stream_start (CbUserStream *self); void cb_user_stream_stop (CbUserStream *self); void cb_user_stream_push_data (CbUserStream *self, const char *data); G_END_DECLS; #endif corebird-1.7.4/src/CbUtils.c000066400000000000000000000425031324604713000156200ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #include "CbUtils.h" #include #include #include void cb_utils_bind_model (GtkWidget *listbox, GListModel *model, GtkListBoxCreateWidgetFunc func, void *data) { g_return_if_fail (GTK_IS_LIST_BOX (listbox)); g_return_if_fail (G_IS_LIST_MODEL (model)); /* This entire function is just a hack around valac ref'ing the listbox * in its own constructor when calling gtk_list_box_bind_model there */ gtk_list_box_bind_model (GTK_LIST_BOX (listbox), model, func, data, NULL); } typedef struct { GtkWidget *listbox; CbUtilsCreateWidgetFunc create_widget_func; gpointer create_widget_func_data; } ModelData; static void non_gobject_model_changed (GListModel *model, guint position, guint removed, guint added, gpointer user_data) { ModelData *data = user_data; GtkListBox *box = GTK_LIST_BOX (data->listbox); guint i; while (removed--) { GtkListBoxRow *row; row = gtk_list_box_get_row_at_index (box, position); gtk_container_remove (GTK_CONTAINER (box), GTK_WIDGET (row)); } for (i = 0; i < added; i++) { gpointer item; GtkWidget *widget; item = g_list_model_get_item (model, position + i); widget = data->create_widget_func (item, data->create_widget_func_data); /* We allow the create_widget_func to either return a full * reference or a floating reference. If we got the floating * reference, then turn it into a full reference now. That means * that gtk_list_box_insert() will take another full reference. * Finally, we'll release this full reference below, leaving only * the one held by the box. */ if (g_object_is_floating (widget)) g_object_ref_sink (widget); gtk_widget_show (widget); gtk_list_box_insert (box, widget, position + i); g_object_unref (widget); } } void cb_utils_bind_non_gobject_model (GtkWidget *listbox, GListModel *model, CbUtilsCreateWidgetFunc func, gpointer *user_data) { ModelData *data; g_return_if_fail (GTK_IS_LIST_BOX (listbox)); g_return_if_fail (G_IS_LIST_MODEL (model)); g_return_if_fail (g_list_model_get_item_type (model) == G_TYPE_POINTER); g_return_if_fail (g_object_get_data (G_OBJECT (listbox), "model-hack") == NULL); data = g_malloc (sizeof (*data)); data->listbox = listbox; data->create_widget_func = func; data->create_widget_func_data = user_data; g_signal_connect (model, "items-changed", G_CALLBACK (non_gobject_model_changed), data); g_object_set_data (G_OBJECT (listbox), "model-hack", data); } void cb_utils_unbind_non_gobject_model (GtkWidget *listbox, GListModel *model) { ModelData *data; g_return_if_fail (GTK_IS_LIST_BOX (listbox)); g_return_if_fail (G_IS_LIST_MODEL (model)); g_return_if_fail (g_object_get_data (G_OBJECT (listbox), "model-hack") != NULL); data = g_object_get_data (G_OBJECT (listbox), "model-hack"); g_signal_handlers_disconnect_by_func (model, non_gobject_model_changed, data); g_assert (data != NULL); g_free (data); } void cb_utils_linkify_user (const CbUserIdentity *user, GString *str) { g_string_append (str, "user_name) > 0) { char *s1, *s2, *s3, *s4; /* TODO: Write one function doing all 4 things, since we need that often * and execute it often? */ s1 = cb_utils_escape_quotes (user->user_name); s2 = cb_utils_escape_ampersands (s1); s3 = cb_utils_escape_quotes (s2); s4 = cb_utils_escape_ampersands (s3); g_string_append (str, "title=\""); g_string_append (str, s4); g_string_append_c (str, '"'); g_free (s1); g_free (s2); g_free (s3); g_free (s4); } g_string_append (str, ">@"); g_string_append (str, user->screen_name); g_string_append (str, ""); } void cb_utils_write_reply_text (const CbMiniTweet *t, GString *str) { g_return_if_fail (t->reply_id != 0); g_return_if_fail (t->n_reply_users > 0); /* TRANSLATORS: This is the start of a "Replying to" line in a tweet */ g_string_append (str, _("Replying to")); g_string_append_c (str, ' '); cb_utils_linkify_user (&t->reply_users[0], str); if (t->n_reply_users == 2) { g_string_append_c (str, ' '); /* TRANSLATORS: This gets appended to the "replying to" line * in a tweet. Example: "Replying to Foo and Bar" where * "and Bar" comes from this string. */ g_string_append (str, _("and")); g_string_append_c (str, ' '); cb_utils_linkify_user (&t->reply_users[1], str); } else if (t->n_reply_users > 2) { g_string_append_c (str, ' '); /* TRANSLATORS: This gets appended to the "replying to" line * in a tweet */ g_string_append_printf (str, _("and %d others"), t->n_reply_users - 1); } } char * cb_utils_escape_quotes (const char *in) { gsize bytes = strlen (in); gsize n_quotes = 0; const char *p = in; gunichar c; char *result; const char *last; char *out_pos; c = g_utf8_get_char (p); while (c != '\0') { if (c == '"') n_quotes ++; p = g_utf8_next_char (p); c = g_utf8_get_char (p); } result = g_malloc (bytes + (n_quotes * 5) + 1); result[bytes + (n_quotes * 5)] = '\0'; p = in; c = g_utf8_get_char (p); last = p; out_pos = result; while (c != '\0') { if (c == '"') { int bytes = p - last; memcpy (out_pos, last, bytes); last = p; out_pos[bytes + 0] = '&'; out_pos[bytes + 1] = 'q'; out_pos[bytes + 2] = 'u'; out_pos[bytes + 3] = 'o'; out_pos[bytes + 4] = 't'; out_pos[bytes + 5] = ';'; last += 1; /* Skip " */ out_pos += bytes + 6; } p = g_utf8_next_char (p); c = g_utf8_get_char (p); } memcpy (out_pos, last, p - last); return result; } /* TODO: Code duplication here with escape_quotes */ char * cb_utils_escape_ampersands (const char *in) { gsize bytes = strlen (in); gsize n_ampersands = 0; const char *p = in; gunichar c; char *result; const char *last; char *out_pos; c = g_utf8_get_char (p); while (c != '\0') { if (c == '&') n_ampersands ++; p = g_utf8_next_char (p); c = g_utf8_get_char (p); } /* 'amp;' and not '&' since the input already contains the '&' */ result = g_malloc (bytes + (n_ampersands * strlen ("amp;")) + 1); result[bytes + (n_ampersands * strlen ("amp;"))] = '\0'; p = in; c = g_utf8_get_char (p); last = p; out_pos = result; while (c != '\0') { if (c == '&') { int bytes = p - last; memcpy (out_pos, last, bytes); last = p; out_pos[bytes + 0] = '&'; out_pos[bytes + 1] = 'a'; out_pos[bytes + 2] = 'm'; out_pos[bytes + 3] = 'p'; out_pos[bytes + 4] = ';'; last += 1; /* Skip & */ out_pos += bytes + 5; } p = g_utf8_next_char (p); c = g_utf8_get_char (p); } memcpy (out_pos, last, p - last); return result; } GDateTime * cb_utils_parse_date (const char *_in) { char in[31]; const char *month_str; int year, month, hour, minute, day; GDateTime *result; GDateTime *result_local; GTimeZone *time_zone; GTimeZone *local_time_zone; double seconds; /* The input string is ASCII, in the form 'Wed Jun 20 19:01:28 +0000 2012' */ if (!_in) return g_date_time_new_now_local (); g_assert (strlen (_in) == 30); memcpy (in, _in, 30); in[3] = '\0'; in[7] = '\0'; in[10] = '\0'; in[13] = '\0'; in[16] = '\0'; in[19] = '\0'; in[25] = '\0'; in[30] = '\0'; year = atoi (in + 26); day = atoi (in + 8); hour = atoi (in + 11); minute = atoi (in + 14); seconds = atof (in + 17); month_str = in + 4; switch (month_str[0]) { case 'J': /* January */ if (month_str[1] == 'u' && month_str[2] == 'n') month = 6; else if (month_str[1] == 'u' && month_str[2] == 'l') month = 7; else month = 1; break; case 'F': month = 2; break; case 'M': if (month_str[1] == 'a' && month_str[2] == 'r') month = 3; else month = 5; break; case 'A': if (month_str[1] == 'p') month = 4; else month = 8; break; case 'S': month = 9; break; case 'O': month = 10; break; case 'N': month = 11; break; case 'D': month = 12; break; default: month = 0; g_warn_if_reached (); break; } time_zone = g_time_zone_new (in + 20); result = g_date_time_new (time_zone, year, month, day, hour, minute, seconds); g_assert (result); local_time_zone = g_time_zone_new_local (); result_local = g_date_time_to_timezone (result, local_time_zone); g_time_zone_unref (local_time_zone); g_time_zone_unref (time_zone); g_date_time_unref (result); return result_local; } char * cb_utils_get_file_type (const char *url) { const char *filename; const char *extension; char *type; filename = g_strrstr (url, "/"); if (filename == NULL) filename = url; else filename += 1; extension = g_strrstr (filename, "."); if (extension == NULL) return g_strdup (""); extension += 1; type = g_ascii_strdown (extension, -1); if (strcmp (type, "jpg") == 0) { g_free (type); return g_strdup ("jpeg"); } return type; } char * cb_utils_rest_proxy_call_to_string (RestProxyCall *call) { GString *str = g_string_new (NULL); RestParams *params = rest_proxy_call_get_params (call); GHashTable *params_table = rest_params_as_string_hash_table (params); g_string_append (str, rest_proxy_call_get_method (call)); g_string_append_c (str, ' '); g_string_append (str, rest_proxy_call_get_function (call)); if (g_hash_table_size (params_table) > 0) { GList *keys; GList *l; g_string_append_c (str, '?'); keys = g_hash_table_get_keys (params_table); for (l = keys; l; l = l->next) { const char *value = g_hash_table_lookup (params_table, l->data); g_assert (value); g_string_append (str, (const char *)l->data); g_string_append_c (str, '='); g_string_append (str, value); if (l->next != NULL) g_string_append_c (str, '&'); } g_list_free (keys); } g_hash_table_unref (params_table); return g_string_free (str, FALSE); } static void parse_json_async (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { const char *payload = task_data; JsonParser *parser; JsonNode *root_node; GError *error = NULL; parser = json_parser_new (); json_parser_load_from_data (parser, payload, -1, &error); if (error) { g_task_return_error (task, error); return; } if (g_cancellable_is_cancelled (cancellable)) { g_task_return_pointer (task, NULL, NULL); return; } root_node = json_parser_get_root (parser); g_assert (root_node); g_task_return_pointer (task, json_node_ref (root_node), (GDestroyNotify)json_node_unref); g_object_unref (parser); } static void call_done_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { RestProxyCall *call = REST_PROXY_CALL (source_object); GTask *task = user_data; GError *error = NULL; char *payload; /* Get the json data, run another GTask that actually parses the json and returns the root node */ rest_proxy_call_invoke_finish (call, result, &error); if (error != NULL) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_warning ("%s(%s): %p, %s", __FILE__, __FUNCTION__, call, error->message); } g_task_return_error (task, error); return; } payload = rest_proxy_call_take_payload (call); g_task_set_task_data (task, payload, g_free); g_task_run_in_thread (task, parse_json_async); } void cb_utils_load_threaded_async (RestProxyCall *call, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task = g_task_new (call, cancellable, callback, user_data); #ifdef DEBUG { char *s = cb_utils_rest_proxy_call_to_string (call); g_debug ("REST: %s", s); g_free (s); } #endif rest_proxy_call_invoke_async (call, cancellable, call_done_cb, task); } JsonNode * cb_utils_load_threaded_finish (GAsyncResult *result, GError **error) { JsonNode *node = g_task_propagate_pointer (G_TASK (result), error); g_object_unref (result); return node; } static void users_received_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { RestProxyCall *call = REST_PROXY_CALL (source_object); GTask *task = user_data; GError *error = NULL; JsonNode *root_node; JsonArray *root_arr; guint i, len; struct { CbUserIdentity *ids; int length; } *data; /* We are in the main thread again here. */ root_node = cb_utils_load_threaded_finish (result, &error); if (error != NULL) { g_task_return_error (task, error); goto out; } if (g_cancellable_is_cancelled (g_task_get_cancellable (task))) { g_task_return_pointer (task, NULL, NULL); goto out; } g_assert (root_node != NULL); root_arr = json_node_get_array (root_node); len = json_array_get_length (root_arr); data = g_malloc (sizeof (*data)); data->ids = g_new (CbUserIdentity, len); data->length = len; for (i = 0; i < len; i ++) { JsonObject *obj = json_array_get_object_element (root_arr, i); data->ids[i].id = json_object_get_int_member (obj, "id"); data->ids[i].user_name = cb_utils_escape_ampersands (json_object_get_string_member (obj, "name")); data->ids[i].screen_name = g_strdup (json_object_get_string_member (obj, "screen_name")); data->ids[i].verified = json_object_get_boolean_member (obj, "verified"); } json_node_unref (root_node); g_task_return_pointer (task, data, NULL); out: g_object_unref (G_OBJECT (call)); } void cb_utils_query_users_async (RestProxy *proxy, const char *query, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; RestProxyCall *call; g_return_if_fail (REST_IS_PROXY (proxy)); g_return_if_fail (query != NULL); call = rest_proxy_new_call (proxy); task = g_task_new (call, cancellable, callback, user_data); rest_proxy_call_set_function (call, "1.1/users/search.json"); rest_proxy_call_set_method (call, "GET"); rest_proxy_call_add_param (call, "q", query); rest_proxy_call_add_param (call, "count", "10"); rest_proxy_call_add_param (call, "include_entities", "false"); cb_utils_load_threaded_async (call, cancellable, users_received_cb, task); } CbUserIdentity * cb_utils_query_users_finish (GAsyncResult *result, int *out_length, GError **error) { struct { CbUserIdentity *ids; int length; } *data = g_task_propagate_pointer (G_TASK (result), error); CbUserIdentity *ids; if (data == NULL) { g_object_unref (result); *out_length = 0; return NULL; } *out_length = data->length; g_object_unref (result); ids = data->ids; g_free (data); return ids; } corebird-1.7.4/src/CbUtils.h000066400000000000000000000063441324604713000156300ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ #ifndef _CB_UTILS_H_ #define _CB_UTILS_H_ #include #include #include "CbTypes.h" #include "rest/rest-proxy-call.h" #include "rest/rest-proxy.h" typedef GtkWidget * (*CbUtilsCreateWidgetFunc) (gpointer *data, gpointer *user_data); void cb_utils_bind_model (GtkWidget *listbox, GListModel *model, GtkListBoxCreateWidgetFunc func, void *data); void cb_utils_bind_non_gobject_model (GtkWidget *listbox, GListModel *model, CbUtilsCreateWidgetFunc func, gpointer *user_data); void cb_utils_unbind_non_gobject_model (GtkWidget *listbox, GListModel *model); void cb_utils_linkify_user (const CbUserIdentity *user, GString *str); void cb_utils_write_reply_text (const CbMiniTweet *t, GString *str); char * cb_utils_escape_quotes (const char *in); char * cb_utils_escape_ampersands (const char *in); GDateTime * cb_utils_parse_date (const char *_in); char * cb_utils_get_file_type (const char *url); char * cb_utils_rest_proxy_call_to_string (RestProxyCall *call); void cb_utils_load_threaded_async (RestProxyCall *call, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); JsonNode *cb_utils_load_threaded_finish (GAsyncResult *result, GError **error); void cb_utils_query_users_async (RestProxy *proxy, const char *query, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); CbUserIdentity * cb_utils_query_users_finish (GAsyncResult *result, int *out_length, GError **error); static inline void cb_clear_source (guint *id) { if (*id == 0) return; g_source_remove (*id); *id = 0; } #endif corebird-1.7.4/src/Corebird.vala000066400000000000000000000503561324604713000165120ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ bool STRESSTEST = false; public class Corebird : Gtk.Application { public static Sql.Database db; public static Cb.SnippetManager snippet_manager; public signal void account_added (Account acc); public signal void account_removed (Account acc); public signal void account_window_changed (int64? old_id, int64 new_id); private SettingsDialog? settings_dialog = null; private GLib.GenericArray active_accounts; private bool started_as_service = false; const GLib.ActionEntry[] app_entries = { {"show-settings", show_settings_activated }, {"show-shortcuts", show_shortcuts_activated }, {"quit", quit_application }, {"show-about-dialog", about_activated }, {"show-dm-thread", show_dm_thread, "(xx)" }, {"show-window", show_window, "x" }, {"mark-read", mark_read_activated, "(xx)" }, {"reply-to-tweet", reply_to_tweet_activated, "(xx)" }, #if DEBUG {"post-json", post_json, "(ss)" }, #endif }; public Corebird () { GLib.Object(application_id: "org.baedert.corebird", flags: ApplicationFlags.HANDLES_COMMAND_LINE); //register_session: true); active_accounts = new GLib.GenericArray (); /* Create the directories here already since the database below needs it */ Dirs.create_dirs (); db = new Sql.Database (Dirs.config ("Corebird.db"), Sql.COREBIRD_INIT_FILE, Sql.COREBIRD_SQL_VERSION); snippet_manager = new Cb.SnippetManager (db.get_sqlite_db ()); } public override int command_line (ApplicationCommandLine cmd) { string? compose_screen_name = null; bool start_service = false; bool stop_service = false; bool print_startup_accounts = false; bool stresstest = false; string? account_name = null; OptionEntry[] options = new OptionEntry[7]; options[0] = {"tweet", 't', 0, OptionArg.STRING, ref compose_screen_name, "Shows only the 'compose tweet' window for the given account, nothing else.", "account name"}; options[1] = {"start-service", 's', 0, OptionArg.NONE, ref start_service, "Start service", null}; options[2] = {"stop-service", 'p', 0, OptionArg.NONE, ref stop_service, "Stop service, if it has been started as a service", null}; options[3] = {"print-startup-accounts", 'a', 0, OptionArg.NONE, ref print_startup_accounts, "Print configured startup accounts", null}; options[4] = {"account", 'c', 0, OptionArg.STRING, ref account_name, "Open the window for the given account", "account name"}; options[5] = {"stresstest", 'r', GLib.OptionFlags.HIDDEN, OptionArg.NONE, ref stresstest, "Debugging only.", null}; options[6] = {null}; string[] args = cmd.get_arguments (); string*[] _args = new string[args.length]; for (int i = 0; i < args.length; i++) { _args[i] = args[i]; } try { var opt_context = new OptionContext (""); opt_context.set_help_enabled (true); opt_context.add_main_entries (options, Config.GETTEXT_PACKAGE); opt_context.add_group (Gtk.get_option_group (false)); #if VIDEO opt_context.add_group (Gst.init_get_option_group ()); #endif unowned string[] tmp = _args; opt_context.parse (ref tmp); } catch (GLib.OptionError e) { cmd.print ("Use --help to see available options\n"); quit (); return -1; } if (stop_service && start_service) { error ("Can't stop and start service at the same time."); } if (stresstest) STRESSTEST = true; if (stop_service) { if (this.started_as_service) { debug ("Stopping service"); /* Starting as a service adds an extra hold() */ this.release (); } else { warning ("--stop-service passed, but corebird has not been started as a service"); } } else if (print_startup_accounts) { string[] startup_accounts = Settings.get ().get_strv ("startup-accounts"); foreach (unowned string acc in startup_accounts) { stdout.printf ("%s\n", acc); } } else if (start_service && !this.started_as_service) { this.started_as_service = true; this.activate (); } else { open_startup_windows (compose_screen_name, account_name); } return 0; } public override void activate () { if (started_as_service) { this.hold (); string[] startup_accounts = Settings.get ().get_strv ("startup-accounts"); if (startup_accounts.length == 1 && startup_accounts[0] == "") startup_accounts.resize (0); debug ("Configured startup accounts: %d", startup_accounts.length); uint n_accounts = Account.get_n (); debug ("Configured accounts: %u", n_accounts); foreach (unowned string screen_name in startup_accounts) { Account? acc = Account.query_account (screen_name); if (acc != null) { debug ("Service: Starting account %s...", screen_name); this.start_account (acc); } else { warning ("Invalid startup account: '%s'", screen_name); } } } else { open_startup_windows (null, null); } } private void show_settings_activated () { /* We don't set the settings dialog transient to any window because we already save its size */ if (this.settings_dialog != null) return; var dialog = new SettingsDialog (this); var action = (GLib.SimpleAction)this.lookup_action ("show-settings"); action.set_enabled (false); dialog.delete_event.connect (() => { action.set_enabled (true); this.settings_dialog = null; return Gdk.EVENT_PROPAGATE; }); dialog.show (); } private void about_activated () { var active_window = get_active_window (); var ad = new AboutDialog (); ad.modal = true; ad.set_transient_for (active_window); ad.show_all (); } private void show_shortcuts_activated () { var builder = new Gtk.Builder.from_resource ("/org/baedert/corebird/ui/shortcuts-window.ui"); var shortcuts_window = (Gtk.Window) builder.get_object ("shortcuts_window"); shortcuts_window.show (); } public override void startup () { base.startup (); this.set_resource_base_path ("/org/baedert/corebird"); typeof (LazyMenuButton).ensure (); typeof (FavImageView).ensure (); typeof (Cb.EmojiChooser).ensure (); #if DEBUG GLib.Environment.set_variable ("G_MESSAGES_DEBUG", "corebird", true); #endif debug ("startup"); // Setup gettext GLib.Intl.setlocale (GLib.LocaleCategory.ALL, Config.DATADIR + "/locale"); GLib.Intl.bindtextdomain (Config.GETTEXT_PACKAGE, null); GLib.Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8"); GLib.Intl.textdomain (Config.GETTEXT_PACKAGE); Utils.load_custom_css (); Utils.load_custom_icons (); Utils.init_soup_session (); Twitter.get ().init (); this.set_accels_for_action ("win.compose-tweet", {Settings.get_accel ("compose-tweet")}); this.set_accels_for_action ("win.toggle-topbar", {Settings.get_accel ("toggle-sidebar")}); this.set_accels_for_action ("win.switch-page(0)", {"1"}); this.set_accels_for_action ("win.switch-page(1)", {"2"}); this.set_accels_for_action ("win.switch-page(2)", {"3"}); this.set_accels_for_action ("win.switch-page(3)", {"4"}); this.set_accels_for_action ("win.switch-page(4)", {"5"}); this.set_accels_for_action ("win.switch-page(5)", {"6"}); this.set_accels_for_action ("win.switch-page(6)", {"7"}); this.set_accels_for_action ("app.show-settings", {Settings.get_accel ("show-settings")}); this.set_accels_for_action ("app.quit", {"Q"}); this.set_accels_for_action ("app.show-shortcuts", {"question", "F1"}); this.set_accels_for_action ("win.show-account-dialog", {Settings.get_accel ("show-account-dialog")}); this.set_accels_for_action ("win.show-account-list", {Settings.get_accel ("show-account-list")}); this.set_accels_for_action ("win.previous", {"Left", "Back"}); this.set_accels_for_action ("win.next", {"Right", "Forward"}); // TweetInfoPage this.set_accels_for_action ("tweet.reply", {"r"}); this.set_accels_for_action ("tweet.favorite", {"f"}); this.add_action_entries (app_entries, this); // If the user wants the dark theme, apply it var gtk_s = Gtk.Settings.get_default (); if (Settings.use_dark_theme ()) { gtk_s.gtk_application_prefer_dark_theme = true; } if (gtk_s.gtk_decoration_layout.contains ("menu")) { gtk_s.gtk_decoration_layout = gtk_s.gtk_decoration_layout.replace ("menu", ""); } } public override void shutdown () { Cb.MediaDownloader.get_default ().shutdown (); base.shutdown (); } /** * Open startup windows. * Semantics: Open a window for every account in the startup-accounts array. * If that array is empty, look at all the account and if there is one, open that one. * If there is none, open a MainWindow with a null account. */ private void open_startup_windows (string? compose_screen_name, string? account_name) { /* Explicitly prefer compose-name over account-name */ if (compose_screen_name != null && account_name != null) { account_name = null; } if (compose_screen_name != null) { Account? acc = Account.query_account (compose_screen_name); if (acc == null) { critical ("No account named `%s` is configured. Exiting.", account_name); return; } acc.init_proxy (); acc.query_user_info_by_screen_name.begin (); var cw = new ComposeTweetWindow (null, acc, null, ComposeTweetWindow.Mode.NORMAL); cw.show (); this.add_window (cw); return; } if (account_name != null) { Account? acc = Account.query_account (account_name); if (acc == null) { critical ("No account named `%s` is configured. Exiting.", account_name); return; } acc.init_proxy (); acc.query_user_info_by_screen_name.begin (); add_window_for_account (acc); return; } string[] startup_accounts = Settings.get ().get_strv ("startup-accounts"); /* Handle the stupid case where only one item is in the array but it's empty */ if (startup_accounts.length == 1 && startup_accounts[0] == "") startup_accounts.resize (0); uint n_accounts = Account.get_n (); if (startup_accounts.length == 0) { if (n_accounts == 1) { add_window_for_screen_name (Account.get_nth (0).screen_name); } else if (n_accounts == 0) { var window = new MainWindow (this, null); add_window (window); window.show_all (); } else { /* We have multiple configured accounts but still none in autostart. This should never happen but we handle the case anyway by just opening the first one. */ add_window_for_screen_name (Account.get_nth (0).screen_name); } } else { bool opened_window = false; foreach (unowned string account in startup_accounts) { if (!is_window_open_for_screen_name (account, null)) { if (add_window_for_screen_name (account)) { opened_window = true; } } } /* If we did not open any window at all since all windows for every account in the startups-account array were already open, just open a new window with a null account */ if (!opened_window) { if (n_accounts > 0) { /* Check if *any* of the configured accounts (not just startup-accounts) is not opened in a window */ for (uint i = 0; i < Account.get_n (); i ++) { var account = Account.get_nth (i); if (!is_window_open_for_user_id (account.id, null)) { add_window_for_account (account); return; } } } foreach (Gtk.Window w in this.get_windows ()) if (((MainWindow)w).account.screen_name == Account.DUMMY) { return; } var m = new MainWindow (this, null); add_window (m); m.show_all (); } } } /** * Adds a new MainWindow instance with the account that * has the given screen name. * Note that this only works if the account is already properly * set up and won't warn or fail if if isn't. * * @param screen_name The screen name of the account to add a * MainWindow for. * * @return true if a window has been opened, false otherwise */ public bool add_window_for_screen_name (string screen_name) { Account? acc = Account.query_account (screen_name); if (acc != null) { add_window_for_account (acc); return true; } warning ("Could not add window for account '%s'", screen_name); return false; } public void add_window_for_account (Account account) { var window = new MainWindow (this, account); this.add_window (window); window.show_all (); } /** * Checks if there's currently a MainWindow instance open that has a * reference to the account with the given screen name. * (This makes a linear search over all open windows, with a text comparison * in each iteration) * * @param screen_name The screen name to search for * @return TRUE if a window with the account associated to the given * screen name is open, FALSE otherwise. */ public bool is_window_open_for_screen_name (string screen_name, out MainWindow? window = null) { unowned GLib.List windows = this.get_windows (); foreach (Gtk.Window win in windows) { if (win is MainWindow) { if (((MainWindow)win).account.screen_name == screen_name) { window = (MainWindow)win; return true; } } } window = null; return false; } public bool is_window_open_for_user_id (int64 user_id, out MainWindow? window = null) { unowned GLib.List windows = this.get_windows (); foreach (Gtk.Window win in windows) { if (win is MainWindow) { if (((MainWindow)win).account.id == user_id) { window = (MainWindow)win; return true; } } } window = null; return false; } /** * Quits the application, saving all open windows and their geometries. */ private void quit_application () { unowned GLib.List windows = this.get_windows (); string[] startup_accounts = Settings.get ().get_strv ("startup-accounts"); if (startup_accounts.length == 1 && startup_accounts[0] == "") startup_accounts.resize (0); if (startup_accounts.length != 0) { base.quit (); return; } string[] account_names = new string[windows.length ()]; int index = 0; foreach (var win in windows) { if (!(win is MainWindow)) continue; var mw = (MainWindow)win; string screen_name = mw.account.screen_name; mw.save_geometry (); account_names[index] = screen_name; index ++; } account_names.resize (index + 1); Settings.get ().set_strv ("startup-accounts", account_names); base.quit (); } public void start_account (Account acc) { for (int i = 0; i < this.active_accounts.length; i ++) { var account = this.active_accounts.get (i); if (acc == account) { /* This can very well happen when we've been started as a service */ debug ("Account %s is already active", acc.screen_name); return; } } acc.init_proxy (); acc.user_stream.start (); acc.init_information.begin (); this.active_accounts.add (acc); } public void stop_account (Account acc) { bool found = false; for (int i = 0; i < this.active_accounts.length; i ++) { var account = this.active_accounts.get (i); if (account == acc) { found = true; break; } } if (!found) { warning ("Can't stop account %s since it's not in the list of active accounts", acc.screen_name); return; } /* If we got started as a service and the given account is in the * startup accounts, don't stop it here */ string[] startup_accounts = Settings.get ().get_strv ("startup-accounts"); if (this.started_as_service && acc.screen_name in startup_accounts) { // Don't stop account } else { acc.uninit (); this.active_accounts.remove (acc); } } /********************************************************/ private void show_dm_thread (GLib.SimpleAction a, GLib.Variant? value) { // Values: Account id, sender_id int64 account_id = value.get_child_value (0).get_int64 (); int64 sender_id = value.get_child_value (1).get_int64 (); MainWindow main_window; if (is_window_open_for_user_id (account_id, out main_window)) { var bundle = new Cb.Bundle (); bundle.put_int64 (DMPage.KEY_SENDER_ID, sender_id); main_window.main_widget.switch_page (Page.DM, bundle); main_window.present (); } else { var account = Account.query_account_by_id (account_id); if (account == null) { /* Security measure, should never happen. */ critical ("No account with id %s found", account_id.to_string ()); return; } main_window = new MainWindow (this, account); this.add_window (main_window); var bundle = new Cb.Bundle (); bundle.put_int64 (DMPage.KEY_SENDER_ID, sender_id); main_window.main_widget.switch_page (Page.DM, bundle); main_window.show_all (); } } private void show_window (GLib.SimpleAction a, GLib.Variant? value) { int64 user_id = value.get_int64 (); MainWindow main_window; if (is_window_open_for_user_id (user_id, out main_window)) { main_window.present (); } else { var account = Account.query_account_by_id (user_id); if (account == null) { /* Security measure, should never happen. */ critical ("No account with id %s found", user_id.to_string ()); return; } main_window = new MainWindow (this, account); this.add_window (main_window); main_window.show_all (); } } private void mark_read_activated (GLib.SimpleAction a, GLib.Variant? v) { int64 account_id = v.get_child_value (0).get_int64 (); int64 tweet_id = v.get_child_value (1).get_int64 (); MainWindow main_window; if (is_window_open_for_user_id (account_id, out main_window)) { main_window.mark_tweet_as_read (tweet_id); } } private void reply_to_tweet_activated (GLib.SimpleAction a, GLib.Variant? v) { int64 account_id = v.get_child_value (0).get_int64 (); int64 tweet_id = v.get_child_value (1).get_int64 (); MainWindow main_window; if (is_window_open_for_user_id (account_id, out main_window)) { main_window.reply_to_tweet (tweet_id); main_window.present (); } } #if DEBUG private void post_json (GLib.SimpleAction a, GLib.Variant? value) { string screen_name = value.get_child_value (0).get_string (); string json = value.get_child_value (1).get_string (); json += "\r\n"; for (int i = 0; i < this.active_accounts.length; i ++) { var acc = this.active_accounts.get (i); if (acc.screen_name == screen_name) { acc.user_stream.push_data (json); return; } } error ("Account @%s is not active.", screen_name); } #endif } corebird-1.7.4/src/DMManager.vala000066400000000000000000000256461324604713000165600ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public class DMManager : GLib.Object { private unowned Account account; private DMThreadsModel threads_model; public bool empty { get { return threads_model.get_n_items () == 0; } } public signal void message_received (DMThread thread, string text, bool initial); public signal void thread_changed (DMThread thread); public DMManager.for_account (Account account) { this.account = account; this.threads_model = new DMThreadsModel (); } public void load_cached_threads () { account.db.select ("dm_threads") .cols ("user_id", "screen_name", "name", "last_message", "last_message_id") .order ("last_message_id") .run ((vals) => { DMThread thread = new DMThread (); thread.user.id = int64.parse (vals[0]); thread.user.screen_name = vals[1]; thread.user.user_name = vals[2]; thread.last_message_id = int64.parse (vals[4]); thread.last_message = vals[3]; threads_model.add (thread); return true; }); } public GLib.ListModel get_threads_model () { return this.threads_model; } public bool has_thread (int64 user_id) { return this.threads_model.has_thread (user_id); } public int reset_unread_count (int64 user_id) { if (!threads_model.has_thread (user_id)) { debug ("No thread found for user id %s", user_id.to_string ()); return 0; } int prev_count = threads_model.reset_unread_count (user_id); this.thread_changed (threads_model.get_thread (user_id)); return prev_count; } public string? reset_notification_id (int64 user_id) { if (!threads_model.has_thread (user_id)) { debug ("No thread found for user id %s", user_id.to_string ()); return null; } return threads_model.reset_notification_id (user_id); } public async void load_newest_dms () { var collect_obj = new Collect (2); collect_obj.finished.connect (() => { load_newest_dms.callback (); }); int64 max_received_id = account.db.select ("dms").cols ("id") .where_eqi ("to_id", account.id) .order ("id DESC").limit (1).once_i64 (); int64 max_sent_id = account.db.select ("dms").cols ("id") .where_eqi ("from_id", account.id) .order ("id DESC").limit (1).once_i64 (); var call = account.proxy.new_call (); call.set_function ("1.1/direct_messages.json"); call.set_method ("GET"); call.add_param ("skip_status", "true"); call.add_param ("since_id", max_received_id.to_string ()); call.add_param ("count", "200"); call.add_param ("full_text", "true"); Cb.Utils.load_threaded_async.begin (call, null, (obj, res) => { try { Json.Node? root = Cb.Utils.load_threaded_async.end (res); on_dm_result (root, true); } catch (GLib.Error e) { warning (e.message); } collect_obj.emit (); }); var sent_call = account.proxy.new_call (); sent_call.set_function ("1.1/direct_messages/sent.json"); sent_call.add_param ("skip_status", "true"); sent_call.add_param ("since_id", max_sent_id.to_string ()); sent_call.add_param ("count", "200"); sent_call.add_param ("full_text", "true"); sent_call.set_method ("GET"); Cb.Utils.load_threaded_async.begin (sent_call, null, (obj, res) => { try { Json.Node? root = Cb.Utils.load_threaded_async.end (res); on_dm_result (root, false); } catch (GLib.Error e) { warning (e.message); } collect_obj.emit (); }); yield; } private void on_dm_result (Json.Node? root, bool received) { var root_arr = root.get_array (); debug ("sent: %u", root_arr.get_length ()); if (root_arr.get_length () > 0) { account.db.begin_transaction (); root_arr.foreach_element ((arr, pos, node) => { var dm_obj = node.get_object (); if (dm_obj.get_int_member ("sender_id") == account.id) { if (received) { update_thread (dm_obj, true); } else if (dm_obj.get_int_member ("recipient_id") != account.id) { save_message (dm_obj, true); } } else { update_thread (dm_obj, true); } }); account.db.end_transaction (); } } public void insert_message (Json.Object dm_obj) { update_thread (dm_obj, false); } private void update_thread (Json.Object dm_obj, bool initial) { int64 recipient_id = dm_obj.get_int_member ("recipient_id"); int64 sender_id = dm_obj.get_int_member ("sender_id"); int64 message_id = dm_obj.get_int_member ("id"); string source_text = dm_obj.get_string_member ("text"); var urls = dm_obj.get_object_member ("entities").get_array_member ("urls"); var url_list = new Cb.TextEntity[urls.get_length ()]; urls.foreach_element((arr, index, node) => { var url = node.get_object(); string expanded_url = url.get_string_member("expanded_url"); Json.Array indices = url.get_array_member ("indices"); url_list[index] = Cb.TextEntity() { from = (uint)indices.get_int_element (0), to = (uint)indices.get_int_element (1) , display_text = url.get_string_member ("display_url"), target = expanded_url.replace ("&", "&"), tooltip_text = expanded_url }; }); string text = Cb.TextTransform.text (source_text, url_list, Cb.TransformFlags.EXPAND_LINKS, 0, 0); int64 thread_user_id = 0; string? thread_screen_name = null; string? thread_user_name = null; /* User -> Other Other -> User User -> User */ if (sender_id == account.id) { thread_user_id = recipient_id; thread_screen_name = dm_obj.get_string_member ("recipient_screen_name"); thread_user_name = dm_obj.get_object_member ("recipient").get_string_member ("name") .strip ().replace ("&", "&"); } else { thread_user_id = sender_id; thread_screen_name = dm_obj.get_string_member ("sender_screen_name"); thread_user_name = dm_obj.get_object_member ("sender").get_string_member ("name") .strip ().replace ("&", "&"); } if (!threads_model.has_thread (thread_user_id)) { DMThread thread = new DMThread (); thread.user.id = thread_user_id; thread.user.screen_name = thread_screen_name; thread.user.user_name = thread_user_name; thread.last_message = text; thread.last_message_id = message_id; this.threads_model.add (thread); account.db.insert ("dm_threads") .vali64 ("user_id", thread_user_id) .val ("screen_name", thread_screen_name) .val ("name", thread_user_name) .val ("last_message", text) .vali64 ("last_message_id", message_id) .run (); } else if (sender_id != account.id || recipient_id == account.id) { DMThread thread = threads_model.get_thread (sender_id); if (message_id > thread.last_message_id) { this.threads_model.update_last_message (sender_id, message_id, text); account.db.update ("dm_threads").val ("last_message", text) .vali64 ("last_message_id", message_id) .where_eqi ("user_id", sender_id).run (); this.thread_changed (thread); } } account.user_counter.user_seen (thread_user_id, thread_screen_name, thread_user_name); /* This will exctract the json data again, etc. but it's still easier than * replacing entities here... */ save_message (dm_obj, initial); } private void save_message (Json.Object dm_obj, bool initial) { Json.Object sender = dm_obj.get_object_member ("sender"); Json.Object recipient = dm_obj.get_object_member ("recipient"); int64 sender_id = dm_obj.get_int_member ("sender_id"); int64 dm_id = dm_obj.get_int_member ("id"); string text = dm_obj.get_string_member ("text"); if (dm_obj.has_member ("entities")) { var urls = dm_obj.get_object_member ("entities").get_array_member ("urls"); var url_list = new Cb.TextEntity[urls.get_length ()]; urls.foreach_element((arr, index, node) => { var url = node.get_object(); string expanded_url = url.get_string_member("expanded_url"); Json.Array indices = url.get_array_member ("indices"); url_list[index] = Cb.TextEntity() { from = (uint)indices.get_int_element (0), to = (uint)indices.get_int_element (1) , target = expanded_url.replace ("&", "&"), tooltip_text = expanded_url, display_text = url.get_string_member ("display_url") }; }); text = Cb.TextTransform.text (text, url_list, 0, 0, 0); } account.db.insert ("dms").vali64 ("id", dm_id) .vali64 ("from_id", sender_id) .vali64 ("to_id", dm_obj.get_int_member ("recipient_id")) .val ("from_screen_name", dm_obj.get_string_member ("sender_screen_name")) .val ("to_screen_name", dm_obj.get_string_member ("recipient_screen_name")) .val ("from_name", sender.get_string_member ("name")) .val ("to_name", recipient.get_string_member ("name")) .vali64 ("timestamp", Cb.Utils.parse_date (dm_obj.get_string_member ("created_at")).to_unix ()) .val ("text", text) .run (); /* We do NOT update last_message of the maybe-existing thread here, since we are already doing that for received messages and don't need to do it for sent ones. */ /* Update unread count for the thread */ if (sender_id != account.id && threads_model.has_thread (sender_id)) { DMThread thread = threads_model.get_thread (sender_id); threads_model.increase_unread_count (sender_id); this.message_received (thread, text, initial); this.thread_changed (thread); } } } corebird-1.7.4/src/DMPage.vala000066400000000000000000000302061324604713000160460ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/dm-page.ui")] class DMPage : IPage, Cb.MessageReceiver, Gtk.Box { public const int KEY_SENDER_ID = 0; public const int KEY_SCREEN_NAME = 1; public const int KEY_USER_NAME = 2; public const int KEY_AVATAR_URL = 3; public int unread_count { get { return 0; } } private unowned MainWindow main_window; public unowned MainWindow window { set { main_window = value; } } public unowned Account account; public int id { get; set; } [GtkChild] private Gtk.Button send_button; [GtkChild] private CompletionTextView text_view; [GtkChild] private Gtk.ListBox messages_list; [GtkChild] private ScrollWidget scroll_widget; private DMPlaceholderBox placeholder_box = new DMPlaceholderBox (); public int64 user_id; private int64 lowest_id = int64.MAX; private bool was_scrolled_down = false; public DMPage (int id, Account account) { this.id = id; this.account = account; text_view.buffer.changed.connect (recalc_length); messages_list.set_sort_func (twitter_item_sort_func_inv); placeholder_box.show (); messages_list.set_placeholder(placeholder_box); scroll_widget.scrolled_to_start.connect (load_older); text_view.size_allocate.connect (() => { if (was_scrolled_down) scroll_widget.scroll_down_next (false, false); }); scroll_widget.vadjustment.value_changed.connect (() => { if (scroll_widget.scrolled_down) { this.was_scrolled_down = true; } else { this.was_scrolled_down = false; } }); } public void stream_message_received (Cb.StreamMessageType type, Json.Node root) { if (type == Cb.StreamMessageType.DIRECT_MESSAGE) { // Arriving new dms get already cached in the DMThreadsPage var obj = root.get_object ().get_object_member ("direct_message"); /* XXX Replace this with local entity parsing */ if (obj.get_int_member ("sender_id") == account.id && obj.has_member ("entities")) { var entries = messages_list.get_children (); int64 dm_id = obj.get_int_member ("id"); foreach (var entry in entries) { var e = (DMListEntry) entry; if (e.user_id == account.id && e.id == -1) { var text = obj.get_string_member ("text"); var urls = obj.get_object_member ("entities").get_array_member ("urls"); var url_list = new Cb.TextEntity[urls.get_length ()]; urls.foreach_element((arr, index, node) => { var url = node.get_object(); string expanded_url = url.get_string_member("expanded_url"); Json.Array indices = url.get_array_member ("indices"); expanded_url = expanded_url.replace("&", "&"); url_list[index] = Cb.TextEntity() { from = (int)indices.get_int_element (0), to = (int)indices.get_int_element (1) , target = expanded_url, display_text = url.get_string_member ("display_url") }; }); e.text = Cb.TextTransform.text (text, url_list, 0, 0, 0); e.id = dm_id; break; } } } /* Only handle DMs from the user we are currently chatting with */ if (obj.get_int_member ("sender_id") != this.user_id) return; /* Writing with ourselves, we have the message already */ if (this.user_id == this.account.id) return; var text = obj.get_string_member ("text"); if (obj.has_member ("entities")) { var urls = obj.get_object_member ("entities").get_array_member ("urls"); var url_list = new Cb.TextEntity[urls.get_length ()]; urls.foreach_element((arr, index, node) => { var url = node.get_object(); string expanded_url = url.get_string_member("expanded_url"); Json.Array indices = url.get_array_member ("indices"); url_list[index] = Cb.TextEntity() { from = (uint)indices.get_int_element (0), to = (uint)indices.get_int_element (1) , target = expanded_url.replace ("&", "&"), tooltip_text = expanded_url, display_text = url.get_string_member ("display_url") }; }); text = Cb.TextTransform.text (text, url_list, 0, 0, 0); } var sender = obj.get_object_member ("sender"); var new_msg = new DMListEntry (); new_msg.text = text; new_msg.name = sender.get_string_member ("name"); new_msg.screen_name = sender.get_string_member ("screen_name"); new_msg.timestamp = Cb.Utils.parse_date (obj.get_string_member ("created_at")).to_unix (); new_msg.main_window = main_window; new_msg.user_id = sender.get_int_member ("id"); new_msg.update_time_delta (); new_msg.load_avatar (sender.get_string_member ("profile_image_url")); messages_list.add (new_msg); if (scroll_widget.scrolled_down) scroll_widget.scroll_down_next (); } } private void load_older () { var now = new GLib.DateTime.now_local (); scroll_widget.balance_next_upper_change (TOP); // Load messages // TODO: Fix code duplication var query = account.db.select ("dms") .cols ("from_id", "to_id", "text", "from_name", "from_screen_name", "timestamp", "id"); if (user_id == account.id) query.where (@"`from_id`='$user_id' AND `to_id`='$user_id' AND `id` < '$lowest_id'"); else query.where (@"(`from_id`='$user_id' OR `to_id`='$user_id') AND `id` < '$lowest_id'"); query.order ("timestamp DESC") .limit (35) .run ((vals) => { int64 id = int64.parse (vals[6]); if (id < lowest_id) lowest_id = id; var entry = new DMListEntry (); entry.id = id; entry.user_id = int64.parse (vals[0]); entry.timestamp = int64.parse (vals[5]); entry.text = vals[2]; entry.name = vals[3]; entry.screen_name = vals[4]; entry.main_window = main_window; entry.update_time_delta (now); Twitter.get ().load_avatar_for_user_id.begin (account, entry.user_id, 48 * this.get_scale_factor (), (obj, res) => { Cairo.Surface? s = Twitter.get ().load_avatar_for_user_id.end (res); entry.avatar = s; }); messages_list.add (entry); return true; }); } public void on_join (int page_id, Cb.Bundle? args) { int64 user_id = args.get_int64 (KEY_SENDER_ID); if (user_id == 0) return; this.lowest_id = int64.MAX; this.user_id = user_id; string screen_name; string name = null; if ((screen_name = args.get_string (KEY_SCREEN_NAME)) != null) { name = args.get_string (KEY_USER_NAME); placeholder_box.user_id = user_id; placeholder_box.screen_name = screen_name; placeholder_box.name = name; placeholder_box.avatar_url = args.get_string (KEY_AVATAR_URL); placeholder_box.load_avatar (); } text_view.set_account (this.account); // Clear list messages_list.foreach ((w) => {messages_list.remove (w);}); // Update unread count DMThreadsPage threads_page = ((DMThreadsPage)main_window.get_page (Page.DM_THREADS)); threads_page.adjust_unread_count_for_user_id (user_id); var now = new GLib.DateTime.now_local (); // Load messages var query = account.db.select ("dms") .cols ("from_id", "to_id", "text", "from_name", "from_screen_name", "timestamp", "id"); if (user_id == account.id) query.where (@"`from_id`='$user_id' AND `to_id`='$user_id'"); else query.where (@"`from_id`='$user_id' OR `to_id`='$user_id'"); query.order ("timestamp DESC") .limit (35) .run ((vals) => { int64 id = int64.parse (vals[6]); if (id < lowest_id) lowest_id = id; var entry = new DMListEntry (); entry.id = id; entry.user_id = int64.parse (vals[0]); entry.timestamp = int64.parse (vals[5]); entry.text = vals[2]; entry.name = vals[3]; name = vals[3]; entry.screen_name = vals[4]; screen_name = vals[4]; entry.main_window = main_window; entry.update_time_delta (now); Twitter.get ().load_avatar_for_user_id.begin (account, entry.user_id, 48 * this.get_scale_factor (), (obj, res) => { Cairo.Surface? s = Twitter.get ().load_avatar_for_user_id.end (res); entry.avatar = s; }); messages_list.add (entry); return true; }); account.user_counter.user_seen (user_id, screen_name, name); scroll_widget.scroll_down_next (false, true); // Focus the text entry text_view.grab_focus (); } public void on_leave () {} [GtkCallback] private void send_button_clicked_cb () { if (text_view.buffer.text.length == 0) return; // Withdraw the notification if there is one DMThreadsPage threads_page = ((DMThreadsPage)main_window.get_page (Page.DM_THREADS)); string notification_id = threads_page.get_notification_id_for_user_id (this.user_id); if (notification_id != null) GLib.Application.get_default ().withdraw_notification (notification_id); // Just add the entry now DMListEntry entry = new DMListEntry (); entry.id = -1; entry.user_id = account.id; entry.screen_name = account.screen_name; entry.timestamp = new GLib.DateTime.now_local ().to_unix (); entry.text = GLib.Markup.escape_text (text_view.buffer.text); entry.main_window = main_window; entry.name = account.name; entry.avatar = account.avatar; entry.update_time_delta (); messages_list.add (entry); var call = account.proxy.new_call (); call.set_function ("1.1/direct_messages/new.json"); call.set_method ("POST"); call.add_param ("user_id", user_id.to_string ()); call.add_param ("text", text_view.buffer.text); call.invoke_async.begin (null, (obj, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), e.message, GLib.Log.LINE, GLib.Log.FILE, this.main_window); return; } }); // clear the text entry text_view.buffer.text = ""; // Scroll down if (scroll_widget.scrolled_down) scroll_widget.scroll_down_next (); } [GtkCallback] private bool text_view_key_press_cb (Gdk.EventKey evt) { if (evt.keyval == Gdk.Key.Return && (evt.state & Gdk.ModifierType.CONTROL_MASK) == Gdk.ModifierType.CONTROL_MASK) { send_button_clicked_cb (); return Gdk.EVENT_STOP; } return Gdk.EVENT_PROPAGATE; } private void recalc_length () { uint text_length = text_view.buffer.text.length; send_button.sensitive = text_length > 0; } public string get_title () { return _("Direct Conversation"); } public void create_radio_button (Gtk.RadioButton? group) {} public Gtk.RadioButton? get_radio_button() {return null;} } corebird-1.7.4/src/DMThreadsPage.vala000066400000000000000000000227151324604713000173670ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class DMThreadsPage : IPage, Cb.MessageReceiver, ScrollWidget { private bool initialized = false; private int _unread_count = 0; public int unread_count { get { return _unread_count; } set { //debug ("Changing unread_count from %d to %d", this._unread_count, value); this._unread_count = value; radio_button.show_badge = (this._unread_count > 0); } } private unowned MainWindow main_window; public unowned MainWindow window { set { main_window = value; } } public unowned Account account; public int id { get; set; } private BadgeRadioButton radio_button; private StartConversationEntry start_conversation_entry; private Gtk.ListBox thread_list; private Gtk.ListBox top_list; private Gtk.ListBoxRow? progress_row = null; private DMManager manager; public DMThreadsPage (int id, Account account) { this.id = id; this.account = account; this.manager = new DMManager.for_account (account); this.manager.message_received.connect (dm_received_cb); this.manager.thread_changed.connect (thread_changed_cb); /* Create UI */ this.hscrollbar_policy = Gtk.PolicyType.NEVER; var frame = new Gtk.Frame (null); frame.margin = 25; frame.set_valign (Gtk.Align.START); frame.set_shadow_type (Gtk.ShadowType.IN); frame.show (); var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); frame.add (box); box.show (); this.top_list = new Gtk.ListBox (); top_list.show (); top_list.set_selection_mode (Gtk.SelectionMode.NONE); top_list.keynav_failed.connect (top_list_keynav_failed_cb); box.add (top_list); var sep = new Gtk.Separator (Gtk.Orientation.HORIZONTAL); sep.show (); box.add (sep); thread_list = new Gtk.ListBox (); thread_list.set_valign (Gtk.Align.START); thread_list.set_selection_mode (Gtk.SelectionMode.NONE); thread_list.keynav_failed.connect (thread_list_keynav_failed_cb); thread_list.set_header_func (default_header_func); box.add (thread_list); this.add (frame); top_list.row_activated.connect ((row) => { if (row is StartConversationEntry) { ((StartConversationEntry)row).reveal (); } }); thread_list.row_activated.connect ((row) => { if (row is DMThreadEntry) { var entry = (DMThreadEntry) row; /* We can withdraw the notification here since activating the notification will dismiss it */ if (manager.has_thread (entry.user_id)) { string? notification_id = manager.reset_notification_id (entry.user_id); if (notification_id != null) GLib.Application.get_default ().withdraw_notification (notification_id); } var bundle = new Cb.Bundle (); bundle.put_int64 (DMPage.KEY_SENDER_ID, entry.user_id); main_window.main_widget.switch_page (Page.DM, bundle); } else warning ("activated row is not a DMThreadEntry"); }); start_conversation_entry = new StartConversationEntry (account); start_conversation_entry.start.connect((user_id, screen_name, name, avatar_url) => { if (manager.has_thread (user_id)) { this.unread_count -= manager.reset_unread_count (user_id); } var bundle = new Cb.Bundle (); bundle.put_int64 (DMPage.KEY_SENDER_ID, user_id); bundle.put_string (DMPage.KEY_SCREEN_NAME, screen_name); bundle.put_string (DMPage.KEY_USER_NAME, name); bundle.put_string (DMPage.KEY_AVATAR_URL, avatar_url); main_window.main_widget.switch_page (Page.DM, bundle); }); Cb.Utils.bind_model (thread_list, manager.get_threads_model (), thread_widget_func); top_list.add (start_conversation_entry); /* We need to do this here so we know which threads we already have cached */ manager.load_cached_threads (); } private Gtk.Widget thread_widget_func (GLib.Object item) { DMThread thread = (DMThread) item; var row = new DMThreadEntry (thread.user.id); row.screen_name = thread.user.screen_name; row.name = thread.user.user_name; row.last_message = thread.last_message; row.unread_count = thread.unread_count; thread.load_avatar.begin (this.account, this.get_scale_factor (), () => { row.avatar = thread.avatar_surface; }); return row; } void dm_received_cb (DMThread thread, string text, bool initial) { assert (thread.user.id != account.id); if (thread.user.id != account.id) { if (!user_id_visible (thread.user.id)) { this.unread_count ++; debug ("Increasing global unread count by 1"); } } if (!initial) { this.notify_new_dm (thread, text); } } void thread_changed_cb (DMThread thread) { foreach (Gtk.Widget w in this.thread_list.get_children ()) { if (w is DMThreadEntry) { var entry = (DMThreadEntry) w; if (entry.user_id == thread.user.id) { entry.last_message = thread.last_message; entry.unread_count = thread.unread_count; break; } } } } public void stream_message_received (Cb.StreamMessageType type, Json.Node root) { if (type == Cb.StreamMessageType.DIRECT_MESSAGE) { var obj = root.get_object ().get_object_member ("direct_message"); this.manager.insert_message (obj); } } public void on_join (int page_id, Cb.Bundle? args) { if (!GLib.NetworkMonitor.get_default ().get_network_available ()) return; if (!initialized) { bool was_empty = manager.empty; if (was_empty) { top_list.hide (); this.progress_row = new Gtk.ListBoxRow (); var spinner = new Gtk.Spinner (); spinner.set_size_request (16, 16); spinner.margin = 12; spinner.visible = true; spinner.start (); progress_row.add (spinner); progress_row.activatable = false; progress_row.visible = true; thread_list.add (progress_row); } this.manager.load_newest_dms.begin (() => { if (was_empty) { if (this.progress_row != null) { thread_list.remove (progress_row); this.progress_row = null; } top_list.show_all (); foreach (Gtk.Widget w in thread_list.get_children ()) { w.show (); } } }); this.initialized = true; } } public void on_leave () { start_conversation_entry.unreveal (); } private void notify_new_dm (DMThread thread, string msg_text) { if (!Settings.notify_new_dms ()) return; string sender_screen_name = thread.user.screen_name; int64 sender_id = thread.user.id; string summary; string text; if (thread.notification_id != null) { summary = ngettext ("%d new Message from %s", "%d new Messages from %s", thread.unread_count).printf (thread.unread_count, thread.user.user_name); text = ""; } else { summary = _("New direct message from %s").printf (sender_screen_name); text = msg_text; } thread.notification_id = account.notifications.send_dm (sender_id, thread.notification_id, summary, text); } public void create_radio_button(Gtk.RadioButton? group) { radio_button = new BadgeRadioButton(group, "corebird-dms-symbolic", _("Direct Messages")); } public Gtk.RadioButton? get_radio_button() { return radio_button; } private bool user_id_visible (int64 sender_id) { return (main_window.cur_page_id == Page.DM && ((DMPage)main_window.get_page (Page.DM)).user_id == sender_id); } public string get_title () { return _("Direct Messages"); } public void adjust_unread_count_for_user_id (int64 user_id) { int unread_count = manager.reset_unread_count (user_id); this.unread_count -= unread_count; debug ("unread_count -= %d", unread_count); } public string? get_notification_id_for_user_id (int64 user_id) { if (!manager.has_thread (user_id)) { warning ("No thread for user id %s", user_id.to_string ()); return null; } string? id = manager.reset_notification_id (user_id); return id; } private bool top_list_keynav_failed_cb (Gtk.DirectionType direction) { if (direction == Gtk.DirectionType.DOWN) { if (thread_list.visible) { thread_list.child_focus (direction); } return true; } return false; } private bool thread_list_keynav_failed_cb (Gtk.DirectionType direction) { if (direction == Gtk.DirectionType.UP) { top_list.child_focus (direction); return true; } return false; } } corebird-1.7.4/src/DefaultTimeline.vala000066400000000000000000000326201324604713000200260ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public abstract class DefaultTimeline : ScrollWidget, IPage { public const int REST = 25; protected bool initialized = false; public int id { get; set; } private int _unread_count = 0; public int unread_count { set { debug ("Unread count for %s from %d to %d", get_title (), _unread_count, value); _unread_count = int.max (value, 0); debug ("New unread count for %s: %d", this.get_title (), value); radio_button.show_badge = (_unread_count > 0); } get { return this._unread_count; } } protected unowned MainWindow main_window; public unowned MainWindow window { set { main_window = value; } } public TweetListBox tweet_list = new TweetListBox (); public unowned Account account; protected BadgeRadioButton radio_button; protected uint tweet_remove_timeout = 0; protected abstract string function { get; } protected bool loading = false; protected Gtk.Widget? last_focus_widget = null; private double last_value = 0.0; public DefaultTimeline (int id) { this.id = id; this.hscrollbar_policy = Gtk.PolicyType.NEVER; this.scrolled_to_start.connect (handle_scrolled_to_start); this.scrolled_to_end.connect (() => { if (!loading) { load_older (); } }); this.vadjustment.notify["value"].connect (() => { mark_seen_on_scroll (vadjustment.value); }); this.add (tweet_list); tweet_list.row_activated.connect ((row) => { if (row is TweetListEntry) { var bundle = new Cb.Bundle (); bundle.put_int (TweetInfoPage.KEY_MODE, TweetInfoPage.BY_INSTANCE); bundle.put_object (TweetInfoPage.KEY_TWEET, ((TweetListEntry)row).tweet); main_window.main_widget.switch_page (Page.TWEET_INFO, bundle); } last_focus_widget = row; }); tweet_list.retry_button_clicked.connect (() => { tweet_list.model.clear (); this.load_newest (); }); this.hexpand = true; } public virtual void on_join (int page_id, Cb.Bundle? args) { if (STRESSTEST) return; if (!initialized) { load_newest (); if (!Settings.auto_scroll_on_new_tweets ()) { /* we are technically not scrolling up, but due to missing content, we can't really not be scrolled up... */ mark_seen (-1); } account.user_stream.resumed.connect (stream_resumed_cb); initialized = true; } if (Settings.auto_scroll_on_new_tweets ()) { mark_seen (-1); } if (last_focus_widget != null) { /* We might have a reference to a row that's been removed from the listbox */ if (last_focus_widget.parent == tweet_list) last_focus_widget.grab_focus (); else last_focus_widget = null; } this.get_vadjustment ().value = this.last_value; } public virtual void on_leave () { this.last_focus_widget = main_window.get_focus (); if (tweet_list.action_entry != null && tweet_list.action_entry.shows_actions) tweet_list.action_entry.toggle_mode (); last_value = this.get_vadjustment ().value; } public bool handles_double_open () { return true; } public void double_open () { if (!loading) { this.scroll_up_next (true, true); tweet_list.get_row_at_index (0).grab_focus (); } } public void load_newest () { this.loading = true; this.load_newest_internal.begin (() => { this.loading = false; }); } public void load_older () { if (!initialized) return; this.balance_next_upper_change (BOTTOM); this.loading = true; this.load_older_internal.begin (() => { this.loading = false; }); } public abstract string get_title (); public override void destroy () { if (tweet_remove_timeout > 0) { GLib.Source.remove (tweet_remove_timeout); tweet_remove_timeout = 0; } base.destroy (); } public virtual void create_radio_button(Gtk.RadioButton? group){} public Gtk.RadioButton? get_radio_button() { return radio_button; } /** * Handle the case of the user scrolling to the start of the list, * i.e. remove all the items except a few ones after a timeout. */ protected void handle_scrolled_to_start() { if (tweet_remove_timeout != 0) return; if (tweet_list.model.get_n_items () > DefaultTimeline.REST) { tweet_remove_timeout = GLib.Timeout.add (500, () => { if (!scrolled_up) { tweet_remove_timeout = 0; return GLib.Source.REMOVE; } /* Check again in case this changed in the last 500ms */ if (tweet_list.model.get_n_items () > DefaultTimeline.REST) { tweet_list.model.remove_last_n_visible (tweet_list.model.get_n_items () - DefaultTimeline.REST); } tweet_remove_timeout = 0; return GLib.Source.REMOVE; }); } else if (tweet_remove_timeout != 0) { GLib.Source.remove (tweet_remove_timeout); tweet_remove_timeout = 0; } } public void delete_tweet (int64 tweet_id) { bool was_seen; bool removed = this.tweet_list.model.delete_id (tweet_id, out was_seen); if (removed && !was_seen) this.unread_count --; } public void toggle_favorite (int64 id, bool mode) { Cb.Tweet? t = this.tweet_list.model.get_for_id (id, 0); if (t != null) { if (mode) this.tweet_list.model.set_tweet_flag (t, Cb.TweetState.FAVORITED); else this.tweet_list.model.unset_tweet_flag (t, Cb.TweetState.FAVORITED); } } /** * So, we don't want to display a retweet in the following situations: * 1) If the original tweet was a tweet by the authenticated user * 2) In any case, if the user follows the author of the tweet * (not the author of the retweet!), we already get the source * tweet by other means, so don't display it again. * 3) It's a retweet from the authenticating user itself * 4) If the tweet was retweeted by a user that is on the list of * users the authenticating user disabled RTs for. * 5) If the retweet is already in the timeline. There's no other * way of checking the case where 2 independend users retweet * the same tweet. */ protected Cb.TweetState get_rt_flags (Cb.Tweet t) { uint flags = 0; /* First case */ if (t.get_user_id () == account.id) flags |= Cb.TweetState.HIDDEN_FORCE; /* Second case */ if (account.follows_id (t.get_user_id ())) flags |= Cb.TweetState.HIDDEN_RT_BY_FOLLOWEE; /* third case */ if (t.retweeted_tweet != null && t.retweeted_tweet.author.id == account.id) flags |= Cb.TweetState.HIDDEN_FORCE; /* Fourth case */ foreach (int64 id in account.disabled_rts) { if (id == t.source_tweet.author.id) { flags |= Cb.TweetState.HIDDEN_RTS_DISABLED; break; } } if (t.retweeted_tweet != null) { /* Fifth case */ foreach (Gtk.Widget w in tweet_list.get_children ()) { if (w is TweetListEntry) { var tt = ((TweetListEntry)w).tweet; if (tt.retweeted_tweet != null && tt.retweeted_tweet.id == t.retweeted_tweet.id) { flags |= Cb.TweetState.HIDDEN_FORCE; break; } } } } return (Cb.TweetState)flags; } protected void mark_seen (int64 id) { foreach (Gtk.Widget w in tweet_list.get_children ()) { if (w == null || !(w is TweetListEntry)) continue; var tle = (TweetListEntry) w; if (tle.tweet.id == id || id == -1) { if (!tle.tweet.get_seen ()) { this.unread_count--; } tle.tweet.set_seen (true); if (id != -1) break; } } } protected bool scroll_up (Cb.Tweet t) { bool auto_scroll = Settings.auto_scroll_on_new_tweets (); if (this.scrolled_up && (t.get_user_id () == account.id || auto_scroll)) { this.scroll_up_next (true, main_window.cur_page_id != this.id); return true; } return false; } private void stream_resumed_cb () { if (this.tweet_list.model.get_n_items () == 0) return; var call = account.proxy.new_call (); call.set_function (this.function); call.set_method ("GET"); call.add_param ("count", "1"); call.add_param ("since_id", (this.tweet_list.model.max_id + 1).to_string ()); call.add_param ("trim_user", "true"); call.add_param ("contributor_details", "false"); call.add_param ("include_entities", "false"); call.invoke_async.begin (null, (o, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { tweet_list.model.clear (); load_newest (); warning (e.message); return; } var parser = new Json.Parser (); try { parser.load_from_data (call.get_payload ()); } catch (GLib.Error e) { tweet_list.model.clear (); this.unread_count = 0; warning (e.message); load_newest (); return; } var root_arr = parser.get_root ().get_array (); if (root_arr.get_length () > 0) { this.tweet_list.model.clear (); this.unread_count = 0; this.load_newest (); } }); } /** * Default implementation for loading the newest tweets * from the given function of the twitter api. */ protected async void load_newest_internal () { int requested_tweet_count = 28; var call = account.proxy.new_call (); call.set_function (this.function); call.set_method("GET"); call.add_param ("count", requested_tweet_count.to_string ()); call.add_param ("contributor_details", "true"); call.add_param ("include_my_retweet", "true"); call.add_param ("tweet_mode", "extended"); call.add_param ("max_id", (tweet_list.model.min_id - 1).to_string ()); Json.Node? root_node = null; try { root_node = yield Cb.Utils.load_threaded_async (call, null); } catch (GLib.Error e) { message (e.message); tweet_list.set_error ("%s\n%s".printf (_("Could not load tweets"), e.message)); return; } var root = root_node.get_array(); if (root.get_length () == 0) { tweet_list.set_empty (); return; } TweetUtils.work_array (root, tweet_list, account); } /** * Default implementation to load older tweets. * */ protected async void load_older_internal () { int requested_tweet_count = 28; var call = account.proxy.new_call (); call.set_function (this.function); call.set_method ("GET"); call.add_param ("count", requested_tweet_count.to_string ()); call.add_param ("include_my_retweet", "true"); call.add_param ("tweet_mode", "extended"); call.add_param ("max_id", (tweet_list.model.min_id - 1).to_string ()); Json.Node? root_node = null; try { root_node = yield Cb.Utils.load_threaded_async (call, null); } catch (GLib.Error e) { warning (e.message); return; } var root = root_node.get_array (); if (root.get_length () == 0) { tweet_list.set_empty (); return; } TweetUtils.work_array (root, tweet_list, account); } /** * Mark the TweetListEntries the user has already seen. * * @param value The scrolling value as from Gtk.Adjustment */ protected void mark_seen_on_scroll (double value) { if (unread_count == 0) return; // We HAVE to use widgets here. tweet_list.forall_internal (false, (w) => { if (!(w is TweetListEntry)) return; var tle = (TweetListEntry)w; if (tle.tweet.get_seen ()) return; Gtk.Allocation alloc; tle.get_allocation (out alloc); if (alloc.y + (alloc.height / 2.0) >= value) { tle.tweet.set_seen (true); unread_count--; } }); } public void rerun_filters () { Cb.TweetModel tm = tweet_list.model; for (uint i = 0; i < tm.get_n_items (); i ++) { var tweet = (Cb.Tweet) tm.get_object (i); if (account.filter_matches (tweet)) { if (tm.set_tweet_flag (tweet, Cb.TweetState.HIDDEN_FILTERED)) i --; if (!tweet.get_seen ()) { this.unread_count --; tweet.set_seen (true); } } else { if (tm.unset_tweet_flag (tweet, Cb.TweetState.HIDDEN_FILTERED)) { i --; } } } // Same thing for invisible tweets... for (uint i = 0; i < tm.hidden_tweets.length; i ++) { var tweet = tm.hidden_tweets.get (i); if (tweet.is_flag_set (Cb.TweetState.HIDDEN_FILTERED)) { if (!account.filter_matches (tweet)) { tm.unset_tweet_flag (tweet, Cb.TweetState.HIDDEN_FILTERED); i --; } } } } } corebird-1.7.4/src/FavoritesTimeline.vala000066400000000000000000000054551324604713000204120ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class FavoritesTimeline : Cb.MessageReceiver, DefaultTimeline { protected override string function { get { return "1.1/favorites/list.json"; } } public FavoritesTimeline (int id, Account account) { base (id); this.account = account; this.tweet_list.account = account; } private void stream_message_received (Cb.StreamMessageType type, Json.Node root) { if (type == Cb.StreamMessageType.EVENT_FAVORITE) { Json.Node tweet_obj = root.get_object ().get_member ("target_object"); int64 tweet_id = tweet_obj.get_object ().get_int_member ("id"); // the source object is a user object indicating who made the favorite Json.Object source_obj = root.get_object ().get_object_member ("source"); if (source_obj.get_int_member ("id") != account.id) return; Cb.Tweet? existing_tweet = this.tweet_list.model.get_for_id (tweet_id, 0); if (existing_tweet != null) { /* This tweet is already in the model, so just mark it as favorited */ tweet_list.model.set_tweet_flag (existing_tweet, Cb.TweetState.FAVORITED); return; } var tweet = new Cb.Tweet (); tweet.load_from_json (tweet_obj, account.id, new GLib.DateTime.now_local ()); tweet.set_flag (Cb.TweetState.FAVORITED); this.tweet_list.model.add (tweet); } else if (type == Cb.StreamMessageType.EVENT_UNFAVORITE) { int64 id = root.get_object ().get_object_member ("target_object").get_int_member ("id"); toggle_favorite (id, false); } } public override void on_leave () { for (uint i = 0; i < tweet_list.model.get_n_items (); i ++) { var tweet = (Cb.Tweet) tweet_list.model.get_item (i); if (!tweet.is_flag_set (Cb.TweetState.FAVORITED)) { tweet_list.model.remove_tweet (tweet); i --; } } base.on_leave (); } public override string get_title () { return _("Favorites"); } public override void create_radio_button (Gtk.RadioButton? group) { radio_button = new BadgeRadioButton(group, "corebird-favorite-symbolic", _("Favorites")); } } corebird-1.7.4/src/FilterPage.vala000066400000000000000000000231061324604713000167740ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/filter-page.ui")] class FilterPage : Gtk.ScrolledWindow, IPage, Cb.MessageReceiver { public int id { get; set; } private unowned MainWindow main_window; public unowned MainWindow window { set { main_window = value; } } public unowned Account account; private BadgeRadioButton radio_button; [GtkChild] private Gtk.ListBox filter_list; [GtkChild] private Gtk.ListBox user_list; [GtkChild] private Gtk.Frame user_list_frame; [GtkChild] private Gtk.Revealer user_list_revealer; private bool filters_loaded = false; private bool users_loaded = false; /* TODO: We might want to use a custom model impl for the muted/blocked list... */ public FilterPage (int id, Account account) { this.id = id; this.account = account; filter_list.set_header_func (default_header_func); filter_list.add (new AddListEntry (_("Add new Filter"))); filter_list.row_activated.connect ((row) => { if (row is AddListEntry) { var dialog = new ModifyFilterDialog (main_window, account); dialog.filter_added.connect (filter_added_cb); dialog.show_all (); } else if (row is FilterListEntry) { var filter_row = (FilterListEntry) row; var dialog = new ModifyFilterDialog (main_window, account, filter_row.filter); dialog.filter_added.connect (filter_added_cb); dialog.show_all (); } }); user_list.set_header_func (default_header_func); } public void on_join (int page_id, Cb.Bundle? args) { if (!filters_loaded) { for (int i = 0; i < account.filters.length; i ++) { var f = account.filters.get (i); var entry = new FilterListEntry (f, account, main_window); filter_list.add (entry); } filters_loaded = true; } if (!GLib.NetworkMonitor.get_default ().get_network_available ()) return; if (users_loaded) return; users_loaded = true; var collect_obj = new Collect (2); collect_obj.finished.connect (() => { if (user_list.get_children ().length () > 0) { user_list_frame.show (); user_list_revealer.reveal_child = true; } }); var call = account.proxy.new_call (); call.set_function ("1.1/blocks/list.json"); call.set_method ("GET"); call.add_param ("include_entities", "false"); call.add_param ("skip_status", "true"); Cb.Utils.load_threaded_async.begin (call, null, (_, res) => { Json.Node? root = null; try { root = Cb.Utils.load_threaded_async.end (res); } catch (GLib.Error e) { warning (e.message); return; } if (root == null) { collect_obj.emit (); return; } Json.Array users = root.get_object ().get_array_member ("users"); users.foreach_element ((arr, index, node) => { var obj = node.get_object (); add_user (obj, false); }); collect_obj.emit (); }); var call2 = account.proxy.new_call (); call2.set_function ("1.1/mutes/users/list.json"); call2.set_method ("GET"); call2.add_param ("include_entities", "false"); call2.add_param ("skip_status", "true"); Cb.Utils.load_threaded_async.begin (call2, null, (_, res) => { Json.Node? root = null; try { root = Cb.Utils.load_threaded_async.end (res); } catch (GLib.Error e) { warning (e.message); return; } if (root == null) { collect_obj.emit (); return; } Json.Array users = root.get_object ().get_array_member ("users"); users.foreach_element ((arr, index, node) => { var obj = node.get_object (); add_user (obj, true); }); collect_obj.emit (); }); } /** * Called when the user adds a new Cb.Filter via the AddFilterDialog * **/ private void filter_added_cb (Cb.Filter f, bool created) { if (created) { var entry = new FilterListEntry (f, account, main_window); filter_list.add (entry); } else { var children = filter_list.get_children (); foreach (Gtk.Widget w in children) { if (!(w is FilterListEntry)) continue; var le = (FilterListEntry) w; if (le.filter.get_id () == f.get_id ()) { le.content = f.get_contents (); break; } } } } public void stream_message_received (Cb.StreamMessageType type, Json.Node root_node) { if (type == Cb.StreamMessageType.EVENT_BLOCK) { var obj = root_node.get_object ().get_object_member ("target"); add_user (obj, false); } else if (type == Cb.StreamMessageType.EVENT_UNBLOCK) { var obj = root_node.get_object ().get_object_member ("target"); int64 user_id = obj.get_int_member ("id"); remove_user (user_id, false); } else if (type == Cb.StreamMessageType.EVENT_MUTE) { var obj = root_node.get_object ().get_object_member ("target"); add_user (obj, true); } else if (type == Cb.StreamMessageType.EVENT_UNMUTE) { var obj = root_node.get_object ().get_object_member ("target"); int64 user_id = obj.get_int_member ("id"); remove_user (user_id, true); } } private void add_user (Json.Object user_obj, bool muted) { int64 id = user_obj.get_int_member ("id"); foreach (Gtk.Widget w in user_list.get_children ()) { if (!(w is UserFilterEntry)) continue; var ufe = (UserFilterEntry) w; if (ufe.user_id == id) { if (muted) ufe.muted = true; else ufe.blocked = true; return; } } string avatar_url = user_obj.get_string_member ("profile_image_url"); if (this.get_scale_factor () == 2) avatar_url = avatar_url.replace ("_normal", "_bigger"); var entry = new UserFilterEntry (); entry.user_id = id; entry.muted = muted; entry.blocked = !muted; entry.name = user_obj.get_string_member ("name"); entry.screen_name = user_obj.get_string_member ("screen_name"); entry.avatar_url = avatar_url; entry.deleted.connect ((id) => { if (entry.muted) unmute_user (id); if (entry.blocked) unblock_user (id); }); user_list.add (entry); user_list_frame.show (); } private void remove_user (int64 id, bool muted) { foreach (Gtk.Widget w in user_list.get_children ()) { if (!(w is UserFilterEntry)) continue; var ufe = (UserFilterEntry) w; if (ufe.user_id == id) { if (muted) ufe.muted = false; else ufe.blocked = false; if (!ufe.blocked && !ufe.muted) user_list.remove (w); break; } } if (user_list.get_children ().length () == 0) { user_list_frame.hide (); } } private void unblock_user (int64 id) { var call = account.proxy.new_call (); call.set_method ("POST"); call.set_function ("1.1/blocks/destroy.json"); call.add_param ("include_entities", "false"); call.add_param ("skip_status", "true"); call.add_param ("user_id", id.to_string ()); call.invoke_async.begin (null, (o, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), e.message, GLib.Log.LINE, GLib.Log.FILE, this.main_window); warning (e.message); return; } }); remove_user (id, false); } private void unmute_user (int64 id) { var call = account.proxy.new_call (); call.set_method ("POST"); call.set_function ("1.1/mutes/users/destroy.json"); call.add_param ("include_entities", "false"); call.add_param ("skip_status", "true"); call.add_param ("user_id", id.to_string ()); call.invoke_async.begin (null, (o, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), e.message, GLib.Log.LINE, GLib.Log.FILE, this.main_window); warning (e.message); return; } }); remove_user (id, true); } [GtkCallback] private bool filter_list_keynav_failed_cb (Gtk.DirectionType direction) { if (direction == Gtk.DirectionType.DOWN) { if (user_list.visible){ user_list.child_focus (direction); } return Gdk.EVENT_STOP; } return Gdk.EVENT_PROPAGATE; } [GtkCallback] private bool user_list_keynav_failed_cb (Gtk.DirectionType direction) { if (direction == Gtk.DirectionType.UP) { filter_list.child_focus (direction); return Gdk.EVENT_STOP; } return Gdk.EVENT_PROPAGATE; } public void on_leave () {} public void create_radio_button (Gtk.RadioButton? group) { radio_button = new BadgeRadioButton(group, "corebird-filter-symbolic", _("Filters")); } public Gtk.RadioButton? get_radio_button() { return radio_button; } public string get_title () { return _("Filters"); } } corebird-1.7.4/src/HomeTimeline.vala000066400000000000000000000170031324604713000173300ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public class HomeTimeline : Cb.MessageReceiver, DefaultTimeline { protected override string function { get { return "1.1/statuses/home_timeline.json"; } } public HomeTimeline(int id, Account account) { base (id); this.account = account; this.tweet_list.account = account; } public void stream_message_received (Cb.StreamMessageType type, Json.Node root) { if (type == Cb.StreamMessageType.TWEET) { add_tweet (root); } else if (type == Cb.StreamMessageType.DELETE) { int64 id = root.get_object ().get_object_member ("delete") .get_object_member ("status").get_int_member ("id"); delete_tweet (id); } else if (type == Cb.StreamMessageType.EVENT_FAVORITE) { int64 id = root.get_object ().get_object_member ("target_object").get_int_member ("id"); int64 source_id = root.get_object ().get_object_member ("source").get_int_member ("id"); if (source_id == account.id) toggle_favorite (id, true); } else if (type == Cb.StreamMessageType.EVENT_UNFAVORITE) { int64 id = root.get_object ().get_object_member ("target_object").get_int_member ("id"); int64 source_id = root.get_object ().get_object_member ("source").get_int_member ("id"); if (source_id == account.id) toggle_favorite (id, false); } else if (type == Cb.StreamMessageType.EVENT_BLOCK) { int64 user_id = root.get_object ().get_object_member ("target").get_int_member ("id"); hide_tweets_from (user_id, Cb.TweetState.HIDDEN_AUTHOR_BLOCKED); } else if (type == Cb.StreamMessageType.EVENT_UNBLOCK) { int64 user_id = root.get_object ().get_object_member ("target").get_int_member ("id"); show_tweets_from (user_id, Cb.TweetState.HIDDEN_AUTHOR_BLOCKED); } else if (type == Cb.StreamMessageType.EVENT_MUTE) { int64 user_id = root.get_object ().get_object_member ("target").get_int_member ("id"); hide_tweets_from (user_id, Cb.TweetState.HIDDEN_AUTHOR_MUTED); } else if (type == Cb.StreamMessageType.EVENT_UNMUTE) { int64 user_id = root.get_object ().get_object_member ("target").get_int_member ("id"); show_tweets_from (user_id, Cb.TweetState.HIDDEN_AUTHOR_MUTED); } } private void add_tweet (Json.Node obj) { GLib.DateTime now = new GLib.DateTime.now_local (); Cb.Tweet t = new Cb.Tweet (); t.load_from_json (obj, this.account.id, now); /* We don't use the set_state version from Cb.TweetModel here since we just decide the initial visibility of the tweet */ if (t.retweeted_tweet != null) { t.set_flag (get_rt_flags (t)); /* CbTweet#get_user_id () returns the retweeted user's id in case it's a retweet, so check both retweeted_tweet's and source_tweet's author id separately */ if (account.is_blocked (t.source_tweet.author.id)) t.set_flag (Cb.TweetState.HIDDEN_RETWEETER_BLOCKED); if (account.is_blocked (t.retweeted_tweet.author.id)) t.set_flag (Cb.TweetState.HIDDEN_AUTHOR_BLOCKED); if (account.is_muted (t.source_tweet.author.id)) t.set_flag (Cb.TweetState.HIDDEN_RETWEETER_MUTED); if (account.is_muted (t.retweeted_tweet.author.id)) t.set_flag (Cb.TweetState.HIDDEN_AUTHOR_MUTED); } else { if (account.is_blocked (t.source_tweet.author.id)) t.set_flag (Cb.TweetState.HIDDEN_AUTHOR_BLOCKED); if (account.is_muted (t.source_tweet.author.id)) t.set_flag (Cb.TweetState.HIDDEN_AUTHOR_MUTED); } if (account.filter_matches (t)) t.set_flag (Cb.TweetState.HIDDEN_FILTERED); bool auto_scroll = Settings.auto_scroll_on_new_tweets (); t.set_seen (t.source_tweet.author.id == account.id || (t.retweeted_tweet != null && t.retweeted_tweet.author.id == account.id) || (this.scrolled_up && main_window.cur_page_id == this.id && auto_scroll)); bool focused = tweet_list.get_first_visible_row () != null && tweet_list.get_first_visible_row ().is_focus; bool should_focus = (focused && this.scrolled_up); tweet_list.model.add (t); if (!t.is_hidden ()) { /* We need to balance even if we don't scroll up, in case auto-scroll-on-new-tweets is disabled */ this.balance_next_upper_change (TOP); if (auto_scroll) { base.scroll_up (t); } if (!t.get_seen ()) this.unread_count ++; } else { t.set_seen (true); } if (should_focus) { tweet_list.get_first_visible_row ().grab_focus (); } /* The rest of this function deals with notifications which we certainly don't want to show for invisible tweets */ if (t.is_hidden ()) return; // We never show any notifications if auto-scroll-on-new-tweet is enabled int stack_size = Settings.get_tweet_stack_count (); if (t.get_user_id () == account.id || auto_scroll) return; if (stack_size == 1 && !auto_scroll) { string summary = ""; if (t.retweeted_tweet != null){ summary = _("%s retweeted %s").printf (t.source_tweet.author.user_name, t.retweeted_tweet.author.user_name); } else { summary = _("%s tweeted").printf (t.source_tweet.author.user_name); } string id_suffix = "tweet-%s".printf (t.id.to_string ()); t.notification_id = account.notifications.send (summary, t.get_real_text (), id_suffix); } else if(stack_size != 0 && unread_count % stack_size == 0 && unread_count > 0) { string summary = ngettext("%d new Tweet!", "%d new Tweets!", unread_count).printf (unread_count); account.notifications.send (summary, ""); } } public void hide_tweets_from (int64 user_id, Cb.TweetState reason) { Cb.TweetModel tm = (Cb.TweetModel) tweet_list.model; tm.toggle_flag_on_user_tweets (user_id, reason, true); } public void show_tweets_from (int64 user_id, Cb.TweetState reason) { Cb.TweetModel tm = (Cb.TweetModel) tweet_list.model; tm.toggle_flag_on_user_tweets (user_id, reason, false); } public void hide_retweets_from (int64 user_id, Cb.TweetState reason) { Cb.TweetModel tm = (Cb.TweetModel) tweet_list.model; tm.toggle_flag_on_user_retweets (user_id, reason, true); } public void show_retweets_from (int64 user_id, Cb.TweetState reason) { Cb.TweetModel tm = (Cb.TweetModel) tweet_list.model; tm.toggle_flag_on_user_retweets (user_id, reason, false); } public override string get_title () { return "@" + account.screen_name; } public override void create_radio_button (Gtk.RadioButton? group) { radio_button = new BadgeRadioButton(group, "corebird-user-home-symbolic", _("Home")); } } corebird-1.7.4/src/IPage.vala000066400000000000000000000023471324604713000157430ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public interface IPage : Gtk.Widget { public abstract int id { get; set; } public abstract void on_join(int page_id, Cb.Bundle? args); public abstract void on_leave (); public abstract void create_radio_button(Gtk.RadioButton? group); public abstract Gtk.RadioButton? get_radio_button(); public abstract string get_title (); public abstract unowned MainWindow window { set; } public virtual bool handles_double_open () { return false; } public virtual void double_open () {} } corebird-1.7.4/src/ListStatusesPage.vala000066400000000000000000000277071324604713000202310ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/list-statuses-page.ui")] class ListStatusesPage : ScrollWidget, IPage { public const int KEY_USER_LIST = 0; public const int KEY_NAME = 1; public const int KEY_DESCRIPTION = 2; public const int KEY_CREATOR = 3; public const int KEY_N_SUBSCRIBERS = 4; public const int KEY_N_MEMBERS = 5; public const int KEY_CREATED_AT = 6; public const int KEY_MODE = 7; public const int KEY_LIST_ID = 8; public int id { get; set; } private unowned MainWindow main_window; public unowned MainWindow window { set { main_window = value; } } public unowned Account account; private int64 list_id; private uint tweet_remove_timeout = 0; [GtkChild] private TweetListBox tweet_list; [GtkChild] private MaxSizeContainer max_size_container; [GtkChild] private Gtk.MenuButton delete_button; [GtkChild] private Gtk.Button edit_button; [GtkChild] private Gtk.Label description_label; [GtkChild] private Gtk.Label name_label; [GtkChild] private Gtk.Label creator_label; [GtkChild] private Gtk.Label subscribers_label; [GtkChild] private Gtk.Label members_label; [GtkChild] private Gtk.Label created_at_label; [GtkChild] private Gtk.Stack name_stack; [GtkChild] private Gtk.Entry name_entry; [GtkChild] private Gtk.Stack description_stack; [GtkChild] private Gtk.Entry description_entry; [GtkChild] private Gtk.Stack delete_stack; [GtkChild] private Gtk.Button cancel_button; [GtkChild] private Gtk.Stack edit_stack; [GtkChild] private Gtk.Button save_button; [GtkChild] private Gtk.Stack mode_stack; [GtkChild] private Gtk.Label mode_label; [GtkChild] private Gtk.ComboBoxText mode_combo_box; [GtkChild] private Gtk.Button refresh_button; private bool loading = false; public ListStatusesPage (int id, Account account) { this.id = id; this.account = account; this.tweet_list.account = account; this.scroll_event.connect (scroll_event_cb); this.scrolled_to_end.connect (load_older); this.scrolled_to_start.connect (handle_scrolled_to_start); tweet_list.set_adjustment (this.get_vadjustment ()); } private bool scroll_event_cb (Gdk.EventScroll evt) { if (evt.delta_y < 0 && this.vadjustment.value == 0) { int inc = (int)(vadjustment.step_increment * (-evt.delta_y)); max_size_container.max_size += inc; return Gdk.EVENT_STOP; } return Gdk.EVENT_PROPAGATE; } /** * va_list params: * - int64 list_id - The id of the list to show * - string name - The lists's name * - bool user_list - true if the list belongs to the user, false otherwise * - string description - the lists's description * - string creator * - int subscribers_count * - int memebers_count * - int64 created_at * - string mode */ public void on_join (int page_id, Cb.Bundle? args) { int64 list_id = args.get_int64 (KEY_LIST_ID); if (list_id == 0) { list_id = this.list_id; return; // Continue } string? list_name = args.get_string (KEY_NAME); if (list_name != null) { bool user_list = args.get_bool (KEY_USER_LIST); string description = args.get_string (KEY_DESCRIPTION); string creator = args.get_string (KEY_CREATOR); int n_subscribers = args.get_int (KEY_N_SUBSCRIBERS); int n_members = args.get_int (KEY_N_MEMBERS); int64 created_at = args.get_int64 (KEY_CREATED_AT); string mode = args.get_string (KEY_MODE); delete_button.sensitive = user_list; edit_button.sensitive = user_list; name_label.label = list_name; description_label.label = description; creator_label.label = creator; members_label.label = "%'d".printf (n_members); subscribers_label.label = "%'d".printf (n_subscribers); created_at_label.label = new GLib.DateTime.from_unix_local (created_at).format ("%x, %X"); mode_label.label = Utils.capitalize (mode); } debug (@"Showing list with id $list_id"); if (list_id == this.list_id) { this.list_id = list_id; load_newer.begin (); } else { max_size_container.max_size = 0; this.list_id = list_id; tweet_list.model.clear (); load_newest.begin (); } } public void on_leave () {} private async void load_newest () { loading = true; tweet_list.set_unempty (); uint requested_tweet_count = 25; var call = account.proxy.new_call (); call.set_function ("1.1/lists/statuses.json"); call.add_param ("tweet_mode", "extended"); call.set_method ("GET"); debug ("USING LIST ID %s", list_id.to_string ()); call.add_param ("list_id", list_id.to_string ()); call.add_param ("count", requested_tweet_count.to_string ()); Json.Node? root = null; try { root = yield Cb.Utils.load_threaded_async (call, null); } catch (GLib.Error e) { if (e.message.down () == "not found") { tweet_list.set_empty (); } warning (e.message); loading = false; return; } var root_array = root.get_array (); if (root_array.get_length () == 0) { tweet_list.set_empty (); loading = false; return; } TweetUtils.work_array (root_array, tweet_list, account); loading = false; } private async void load_older () { if (loading) return; loading = true; uint requested_tweet_count = 25; var call = account.proxy.new_call (); call.set_function ("1.1/lists/statuses.json"); call.add_param ("tweet_mode", "extended"); call.set_method ("GET"); call.add_param ("list_id", list_id.to_string ()); call.add_param ("max_id", (tweet_list.model.min_id -1).to_string ()); call.add_param ("count", requested_tweet_count.to_string ()); Json.Node? root = null; try { root = yield Cb.Utils.load_threaded_async (call, null); } catch (GLib.Error e) { warning (e.message); return; } var root_array = root.get_array (); TweetUtils.work_array (root_array, tweet_list, account); loading = false; } [GtkCallback] private void edit_button_clicked_cb () { name_stack.visible_child = name_entry; description_stack.visible_child = description_entry; delete_stack.visible_child = cancel_button; edit_stack.visible_child = save_button; mode_stack.visible_child = mode_combo_box; name_entry.text = real_list_name (); description_entry.text = description_label.label; mode_combo_box.active_id = mode_label.label; } [GtkCallback] private void cancel_button_clicked_cb () { name_stack.visible_child = name_label; description_stack.visible_child = description_label; delete_stack.visible_child = delete_button; edit_stack.visible_child = edit_button; mode_stack.visible_child = mode_label; } [GtkCallback] private void save_button_clicked_cb () { // Make everything go back to normal name_label.label = "@%s/%s".printf(creator_label.label, name_entry.get_text ()); description_label.label = description_entry.text; mode_label.label = mode_combo_box.active_id; cancel_button_clicked_cb (); edit_button.sensitive = false; delete_button.sensitive = false; var call = account.proxy.new_call (); call.set_function ("1.1/lists/update.json"); call.set_method ("POST"); call.add_param ("list_id", list_id.to_string ()); call.add_param ("name", real_list_name ()); call.add_param ("mode", mode_label.label.down ()); call.add_param ("description", description_label.label); call.invoke_async.begin (null, (o, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), e.message, GLib.Log.LINE, GLib.Log.FILE, this.main_window); } edit_button.sensitive = true; delete_button.sensitive = true; }); } private string real_list_name () { string cur_name = name_label.label; int slash_index = cur_name.index_of ("/"); return cur_name.substring (slash_index + 1); } [GtkCallback] private void delete_confirmation_item_clicked_cb () { var call = account.proxy.new_call (); call.set_function("1.1/lists/destroy.json"); call.add_param ("list_id", list_id.to_string ()); call.set_method ("POST"); call.invoke_async.begin (null, (o, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), e.message, GLib.Log.LINE, GLib.Log.FILE, this.main_window); } }); // Go back to the ListsPage and tell it to remove this list var bundle = new Cb.Bundle (); bundle.put_int (ListsPage.KEY_MODE, ListsPage.MODE_DELETE); bundle.put_int64 (ListsPage.KEY_LIST_ID, list_id); main_window.main_widget.switch_page (Page.LISTS, bundle); } [GtkCallback] private void refresh_button_clicked_cb () { refresh_button.sensitive = false; load_newer.begin (() => { refresh_button.sensitive = true; }); } [GtkCallback] private void tweet_activated_cb (Gtk.ListBoxRow row) { if (row is TweetListEntry) { var bundle = new Cb.Bundle (); bundle.put_int (TweetInfoPage.KEY_MODE, TweetInfoPage.BY_INSTANCE); bundle.put_object (TweetInfoPage.KEY_TWEET, ((TweetListEntry)row).tweet); main_window.main_widget.switch_page (Page.TWEET_INFO, bundle); } else warning ("row is of unknown type"); } private async void load_newer () { var call = account.proxy.new_call (); call.set_function ("1.1/lists/statuses.json"); call.set_method ("GET"); call.add_param ("list_id", list_id.to_string ()); call.add_param ("count", "30"); int64 since_id = tweet_list.model.max_id; if (since_id < 0) since_id = 1; call.add_param ("since_id", since_id.to_string ()); debug ("Getting statuses since %s for list_id %s", since_id.to_string (), list_id.to_string ()); Json.Node? root = null; try { root = yield Cb.Utils.load_threaded_async (call, null); } catch (GLib.Error e) { warning (e.message); return; } var root_array = root.get_array (); if (root_array.get_length () > 0) { TweetUtils.work_array (root_array, tweet_list, account); } } protected void handle_scrolled_to_start() { if (tweet_remove_timeout != 0) return; if (tweet_list.model.get_n_items () > DefaultTimeline.REST) { tweet_remove_timeout = GLib.Timeout.add (500, () => { if (!scrolled_up) { tweet_remove_timeout = 0; return false; } tweet_list.model.remove_last_n_visible (tweet_list.model.get_n_items () - DefaultTimeline.REST); tweet_remove_timeout = 0; return GLib.Source.REMOVE; }); } else if (tweet_remove_timeout != 0) { GLib.Source.remove (tweet_remove_timeout); tweet_remove_timeout = 0; } } public string get_title () { return _("List"); } public void create_radio_button (Gtk.RadioButton? group) {} public Gtk.RadioButton? get_radio_button () {return null;} } corebird-1.7.4/src/ListsPage.vala000066400000000000000000000111441324604713000166440ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class ListsPage : IPage, ScrollWidget, Cb.MessageReceiver { public const int KEY_MODE = 0; public const int KEY_LIST_ID = 1; public const int MODE_DELETE = 1; private BadgeRadioButton radio_button; public int unread_count { get; set; } private unowned MainWindow main_window; public unowned MainWindow window { set { main_window = value; user_lists_widget.main_window = value; } } public unowned Account account { get; set; } public unowned Cb.DeltaUpdater delta_updater { get; set; } public int id { get; set; } private bool inited = false; private int64 user_id; private UserListsWidget user_lists_widget; public ListsPage (int id, Account account) { this.id = id; this.account = account; this.set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); this.user_lists_widget = new UserListsWidget (); this.user_lists_widget.account = account; this.add (user_lists_widget); } public void on_join (int page_id, Cb.Bundle? args) { int mode = 0; if (!GLib.NetworkMonitor.get_default ().get_network_available ()) return; if (args != null) mode = args.get_int (KEY_MODE); if (mode == 0 && !inited) { inited = true; this.user_id = account.id; load_newest.begin (); } else if (mode == MODE_DELETE) { int64 list_id = args.get_int64 (KEY_LIST_ID); message (@"Deleting list with id $list_id"); user_lists_widget.remove_list (list_id); } } public void on_leave () { user_lists_widget.unreveal (); } private async void load_newest () { yield user_lists_widget.load_lists (user_id); } private void stream_message_received (Cb.StreamMessageType type, Json.Node root) { // {{{ if (type == Cb.StreamMessageType.EVENT_LIST_CREATED || type == Cb.StreamMessageType.EVENT_LIST_SUBSCRIBED) { var obj = root.get_object ().get_object_member ("target_object"); var entry = new ListListEntry.from_json_data (obj, account); user_lists_widget.add_list (entry); } else if (type == Cb.StreamMessageType.EVENT_LIST_DESTROYED || type == Cb.StreamMessageType.EVENT_LIST_UNSUBSCRIBED) { var obj = root.get_object ().get_object_member ("target_object"); int64 list_id = obj.get_int_member ("id"); user_lists_widget.remove_list (list_id); } else if (type == Cb.StreamMessageType.EVENT_LIST_UPDATED) { var obj = root.get_object ().get_object_member ("target_object"); int64 list_id = obj.get_int_member ("id"); update_list (list_id, obj); } else if (type == Cb.StreamMessageType.EVENT_LIST_MEMBER_ADDED) { var obj = root.get_object ().get_object_member ("target_object"); int64 list_id = obj.get_int_member ("id"); user_lists_widget.update_member_count (list_id, 1); } else if (type == Cb.StreamMessageType.EVENT_LIST_MEMBER_REMOVED) { var obj = root.get_object ().get_object_member ("target_object"); int64 list_id = obj.get_int_member ("id"); user_lists_widget.update_member_count (list_id, -1); } } // }}} public async TwitterList[] get_user_lists () { if (!inited) { inited = true; yield user_lists_widget.load_lists (user_id); } return user_lists_widget.get_user_lists (); } private void update_list (int64 list_id, Json.Object obj) { string name = obj.get_string_member ("full_name"); string description = obj.get_string_member ("description"); string mode = obj.get_string_member ("mode"); user_lists_widget.update_list (list_id, name, description, mode); } public void create_radio_button (Gtk.RadioButton? group) { radio_button = new BadgeRadioButton (group, "view-list-symbolic", _("Lists")); } public string get_title () { return _("Lists"); } public Gtk.RadioButton? get_radio_button () { return radio_button; } } corebird-1.7.4/src/MainWidget.vala000066400000000000000000000150011324604713000167750ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public class MainWidget : Gtk.Box { private unowned Account account; private Gtk.RadioButton dummy_button = new Gtk.RadioButton (null); private IPage[] pages; private Cb.BundleHistory history = new Cb.BundleHistory (); private bool page_switch_lock = false; private ImpostorWidget stack_impostor = new ImpostorWidget (); private Gtk.Box top_box; private Gtk.Stack stack; private Gtk.Revealer topbar_revealer; public int cur_page_id { get { return history.get_current (); } } public MainWidget (Account account, MainWindow parent, Corebird app) { this.account = account; app.start_account (account); /* Create widgets */ this.set_orientation (Gtk.Orientation.VERTICAL); this.topbar_revealer = new Gtk.Revealer (); topbar_revealer.set_reveal_child (true); topbar_revealer.set_transition_type (Gtk.RevealerTransitionType.SLIDE_UP); this.top_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); top_box.set_hexpand (true); top_box.set_homogeneous (true); top_box.get_style_context ().add_class ("topbar"); topbar_revealer.add (top_box); this.add (topbar_revealer); this.stack = new Gtk.Stack (); stack.set_hexpand (true); stack.set_vexpand (true); this.add (stack); stack.add (stack_impostor); pages = new IPage[11]; pages[0] = new HomeTimeline (Page.STREAM, account); pages[1] = new MentionsTimeline (Page.MENTIONS, account); pages[2] = new FavoritesTimeline (Page.FAVORITES, account); pages[3] = new DMThreadsPage (Page.DM_THREADS, account); pages[4] = new ListsPage (Page.LISTS, account); pages[5] = new FilterPage (Page.FILTERS, account); pages[6] = new SearchPage (Page.SEARCH, account); pages[7] = new ProfilePage (Page.PROFILE, account); pages[8] = new TweetInfoPage (Page.TWEET_INFO, account); pages[9] = new DMPage (Page.DM, account); pages[10] = new ListStatusesPage (Page.LIST_STATUSES, account); /* Initialize all containers */ for (int i = 0; i < pages.length; i++) { IPage page = pages[i]; page.window = parent; if (page is Cb.MessageReceiver) account.user_stream.register ((Cb.MessageReceiver)page); page.create_radio_button (dummy_button); stack.add (page); if (page.get_radio_button () != null) { top_box.add (page.get_radio_button ()); page.get_radio_button ().clicked.connect (() => { if (page.get_radio_button ().active && !page_switch_lock) { switch_page (page.id); } }); } } Settings.get ().bind ("sidebar-visible", this.topbar_revealer, "reveal-child", SettingsBindFlags.DEFAULT); } /** * Switches the window's main notebook to the given page. * * @param page_id The id of the page to switch to. * See the Page.* constants. * */ public void switch_page (int page_id, Cb.Bundle? args = null) { if (page_id == history.get_current ()) { if (pages[page_id].handles_double_open ()) pages[page_id].double_open (); if ((history.get_current_bundle () != null && history.get_current_bundle ().equals (args)) || history.get_current_bundle () == args) return; } bool push = true; // Set the correct transition type if (page_id == Page.PREVIOUS || page_id < history.get_current ()) stack.transition_type = Gtk.StackTransitionType.SLIDE_RIGHT; else if (page_id == Page.NEXT || page_id > history.get_current ()) stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT; int current_page = history.get_current (); // If we go forward/back, we don't need to update the history. if (page_id == Page.PREVIOUS) { if (history.at_start ()) return; push = false; page_id = history.back (); args = history.get_current_bundle (); } else if (page_id == Page.NEXT) { if (history.at_end ()) return; push = false; page_id = history.forward (); args = history.get_current_bundle (); } if (page_id == current_page) { stack_impostor.clone (pages[page_id]); var transition_type = stack.transition_type; stack.transition_type = Gtk.StackTransitionType.NONE; stack.set_visible_child (stack_impostor); stack.transition_type = transition_type; } if (current_page != -1) pages[current_page].on_leave (); if (push) { history.push (page_id, args); } /* XXX The following will cause switch_page to be called twice because setting the active property of the button will cause the clicked event to be emitted, which will call switch_page. */ IPage page = pages[page_id]; Gtk.ToggleButton button = page.get_radio_button (); page_switch_lock = true; if (button != null) button.active = true; else dummy_button.active = true; /* on_join first, then set_visible_child so the new page is still !child-visible, so e.g. GtkStack transitions inside the page aren't animated */ page.on_join (page_id, args); stack.set_visible_child (pages[page_id]); ((MainWindow)this.parent).set_window_title (page.get_title (), stack.transition_type); page_switch_lock = false; ((MainWindow)this.parent).back_button.sensitive = !history.at_start (); } public void remove_current_page () { this.history.remove_current (); this.switch_page (Page.PREVIOUS); } public IPage get_page (int page_id) { return pages[page_id]; } public void stop () { for (int i = 0; i < pages.length; i++) { IPage page = pages[i]; if (page is Cb.MessageReceiver) account.user_stream.unregister ((Cb.MessageReceiver)page); } ((Corebird)GLib.Application.get_default ()).stop_account (this.account); } } corebird-1.7.4/src/MainWindow.vala000066400000000000000000000457711324604713000170420ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public class MainWindow : Gtk.ApplicationWindow { private const GLib.ActionEntry[] win_entries = { {"compose-tweet", show_hide_compose_window}, {"toggle-topbar", Settings.toggle_topbar_visible}, {"switch-page", simple_switch_page, "i"}, {"show-account-dialog", show_account_dialog}, {"show-account-list", show_account_list}, {"previous", previous}, {"next", next} }; private Gtk.HeaderBar headerbar; private AvatarWidget avatar_image; private Gtk.ListBox account_list; private Gtk.Popover account_popover; private Gtk.Box header_box; private Gtk.ToggleButton account_button; private Gtk.Label title_label; private Gtk.Label last_page_label; private Gtk.Stack title_stack; public Gtk.Button back_button; public Gtk.ToggleButton compose_tweet_button; private Gtk.MenuButton app_menu_button = null; public MainWidget main_widget; public unowned Account? account; private ComposeTweetWindow? compose_tweet_window = null; private Gtk.GestureMultiPress thumb_button_gesture; public int cur_page_id { get { return main_widget.cur_page_id; } } public MainWindow (Gtk.Application app, Account? account = null) { set_default_size (530, 700); var group = new Gtk.WindowGroup (); group.add_window (this); #if DEBUG this.set_focus.connect ((w) => { debug ("Focus widget now: %s %p", w != null ? __class_name (w) : "(null)", w); }); #endif /* Create widgets */ this.set_show_menubar (false); this.set_icon_name ("corebird"); this.delete_event.connect (window_delete_cb); this.headerbar = new Gtk.HeaderBar (); headerbar.set_title ("Corebird"); headerbar.set_show_close_button (true); this.title_stack = new Gtk.Stack (); this.title_label = new Gtk.Label (""); title_label.set_ellipsize (Pango.EllipsizeMode.MIDDLE); title_label.get_style_context ().add_class ("title"); title_stack.add (title_label); this.last_page_label = new Gtk.Label (""); last_page_label.set_ellipsize (Pango.EllipsizeMode.MIDDLE); last_page_label.get_style_context ().add_class ("title"); title_stack.add (last_page_label); headerbar.set_custom_title (title_stack); this.header_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6); header_box.set_no_show_all (true); this.account_button = new Gtk.ToggleButton (); account_button.set_tooltip_text (_("Show configured accounts")); account_button.clicked.connect (account_button_clicked_cb); account_button.get_style_context ().add_class ("account-button"); this.avatar_image = new AvatarWidget (); avatar_image.size = 24; avatar_image.set_valign (Gtk.Align.CENTER); account_button.add (avatar_image); account_button.show_all (); header_box.add (account_button); this.compose_tweet_button = new Gtk.ToggleButton (); compose_tweet_button.add (new Gtk.Image.from_icon_name ("corebird-compose-symbolic", Gtk.IconSize.BUTTON)); compose_tweet_button.set_tooltip_text (_("Compose Tweet")); compose_tweet_button.set_action_name ("win.compose-tweet"); compose_tweet_button.get_style_context ().add_class ("image-button"); compose_tweet_button.show_all (); header_box.add (compose_tweet_button); this.back_button = new Gtk.Button.from_icon_name ("go-previous-symbolic", Gtk.IconSize.BUTTON); back_button.clicked.connect (back_button_clicked_cb); back_button.show_all (); header_box.add (back_button); header_box.show_all (); headerbar.pack_start (header_box); headerbar.show_all (); this.set_titlebar (headerbar); this.account_popover = new Gtk.Popover (account_button); account_popover.closed.connect (account_popover_closed_cb); var frame = new Gtk.Frame (null); frame.set_margin_start (6); frame.set_margin_end (6); frame.set_margin_top (6); frame.set_margin_bottom (6); this.account_list = new Gtk.ListBox (); account_list.set_selection_mode (Gtk.SelectionMode.NONE); account_list.row_activated.connect (account_row_activated_cb); frame.add (account_list); account_popover.add (frame); account_popover.show_all (); change_account (account); account_list.set_sort_func (account_sort_func); account_list.set_header_func (default_header_func); var add_entry = new AddListEntry (_("Add new Account")); add_entry.show_all (); account_list.add (add_entry); for (uint i = 0; i < Account.get_n (); i ++) { var acc = Account.get_nth (i); if (acc.screen_name == Account.DUMMY) continue; var e = new UserListEntry.from_account (acc); e.show_settings = true; e.action_clicked.connect (() => { #if GTK322 account_popover.popdown (); #else account_popover.hide (); #endif }); account_list.add (e); } ((Corebird)app).account_added.connect ((new_acc) => { var entries = account_list.get_children (); foreach (Gtk.Widget ule in entries) if (ule is UserListEntry && new_acc.screen_name == ((UserListEntry)ule).screen_name) return; var ule = new UserListEntry.from_account (new_acc); ule.show_settings = true; ule.action_clicked.connect (() => { #if GTK322 account_popover.popdown (); #else account_popover.hide (); #endif }); account_list.add (ule); ule.show (); }); ((Corebird)app).account_removed.connect ((acc) => { var entries = account_list.get_children (); foreach (Gtk.Widget ule in entries) if (ule is UserListEntry && acc.screen_name == ((UserListEntry)ule).screen_name) { account_list.remove (ule); break; } }); this.add_action_entries (win_entries, this); headerbar.key_press_event.connect ((evt) => { if (evt.keyval == Gdk.Key.Down && main_widget != null) { main_widget.get_page (main_widget.cur_page_id).focus (Gtk.DirectionType.RIGHT); return true; } return false; }); this.thumb_button_gesture = new Gtk.GestureMultiPress (this); thumb_button_gesture.set_button (0); thumb_button_gesture.set_propagation_phase (Gtk.PropagationPhase.CAPTURE); thumb_button_gesture.pressed.connect (thumb_button_pressed_cb); load_geometry (); } private void back_button_clicked_cb () { main_widget.switch_page (Page.PREVIOUS); } public void change_account (Account? account) { int64? old_user_id = null; if (this.account != null) { old_user_id = this.account.id; this.account.info_changed.disconnect (account_info_changed); } this.account = account; if (main_widget != null) { main_widget.stop (); } if (get_child () != null) { remove (get_child ()); } Corebird cb = (Corebird) GLib.Application.get_default (); if (account != null && account.screen_name != Account.DUMMY) { header_box.show (); main_widget = new MainWidget (account, this, cb); main_widget.show_all (); this.add (main_widget); main_widget.switch_page (0); this.set_window_title (main_widget.get_page (0).get_title ()); avatar_image.surface = account.avatar_small; account.notify["avatar-small"].connect(() => { avatar_image.surface = this.account.avatar_small; }); account.info_changed.connect (account_info_changed); this.set_title ("Corebird - @%s".printf (account.screen_name)); cb.account_window_changed (old_user_id, account.id); if (!Gtk.Settings.get_default ().gtk_shell_shows_app_menu) { if (app_menu_button == null) { app_menu_button = new Gtk.MenuButton (); app_menu_button.image = new Gtk.Image.from_icon_name ("emblem-system-symbolic", Gtk.IconSize.MENU); app_menu_button.get_style_context ().add_class ("image-button"); app_menu_button.menu_model = cb.app_menu; app_menu_button.show_all (); headerbar.pack_end (app_menu_button); } else app_menu_button.show (); } } else { /* "Special case" when creating a new account */ header_box.hide (); if (app_menu_button != null) app_menu_button.hide (); Account acc_; if (account == null) acc_ = new Account (0, Account.DUMMY, "name"); else acc_ = account; this.account = acc_; this.title_label.label = "Corebird"; this.set_title ("Corebird"); Account.add_account (acc_); var create_widget = new AccountCreateWidget (acc_, cb, this); create_widget.result_received.connect ((result, acc) => { if (result) { change_account (acc); } else { //Account.remove ("screen_name"); } }); this.add (create_widget); } } private void account_row_activated_cb (Gtk.ListBoxRow row) { if (row is AddListEntry) { #if GTK322 account_popover.popdown (); #else account_popover.hide (); #endif Account dummy_acc = new Account (0, Account.DUMMY, "name"); var window = new MainWindow (application, dummy_acc); get_application ().add_window (window); window.show_all (); return; } var e = (UserListEntry)row; int64 user_id = e.user_id; Corebird cb = (Corebird)this.get_application (); MainWindow? account_window = null; if (user_id == this.account.id || cb.is_window_open_for_user_id (user_id, out account_window)) { #if GTK322 account_popover.popdown (); #else account_popover.hide (); #endif if (account_window != null) account_window.present (); return; } Account? acc = Account.query_account_by_id (user_id); if (acc != null) { change_account (acc); #if GTK322 account_popover.popdown (); #else account_popover.hide (); #endif } else warning ("account == null"); } private void thumb_button_pressed_cb (Gtk.GestureMultiPress gesture, int n_press, double x, double y) { uint button = gesture.get_current_button (); if (button == 9) { // Forward thumb button main_widget.switch_page (Page.NEXT); gesture.set_state (Gtk.EventSequenceState.CLAIMED); } else if (button == 8) { // backward thumb button main_widget.switch_page (Page.PREVIOUS); gesture.set_state (Gtk.EventSequenceState.CLAIMED); } } private void show_hide_compose_window () { if (this.account == null || this.account.screen_name == Account.DUMMY) return; if (compose_tweet_window == null) { compose_tweet_window = new ComposeTweetWindow (this, account, null, ComposeTweetWindow.Mode.NORMAL); compose_tweet_window.show (); compose_tweet_window.hide.connect (() => { compose_tweet_button.active = false; }); compose_tweet_window.destroy.connect (() => { compose_tweet_window = null; }); } else { compose_tweet_window.hide (); compose_tweet_window.destroy (); } } /** * GSimpleActionActivateCallback version of switch_page, used * for keyboard accelerators. */ private void simple_switch_page (GLib.SimpleAction a, GLib.Variant? param) { main_widget.switch_page (param.get_int32 ()); } private void previous (GLib.SimpleAction a, GLib.Variant? param) { if (this.account == null || this.account.screen_name == Account.DUMMY) return; main_widget.switch_page (Page.PREVIOUS); } private void next (GLib.SimpleAction a, GLib.Variant? param) { if (this.account == null || this.account.screen_name == Account.DUMMY) return; main_widget.switch_page (Page.NEXT); } /* result of the show-account-dialog GAction */ private void show_account_dialog () { if (this.account == null || this.account.screen_name == Account.DUMMY) return; var dialog = new AccountDialog (this.account); dialog.set_transient_for (this); dialog.modal = true; dialog.show (); } /* for show-account-list GAction */ private void show_account_list () { if (this.account != null && this.account.screen_name != Account.DUMMY) { #if GTK322 this.account_popover.popup (); #else this.account_popover.show (); #endif } } public IPage get_page (int page_id) { return main_widget.get_page (page_id); } private void account_button_clicked_cb () { #if GTK322 if (account_popover.visible) { account_popover.popdown (); } else { account_popover.popup (); } #else account_popover.visible = !account_popover.visible; #endif } private void account_popover_closed_cb () { account_button.active = false; } private bool window_delete_cb (Gdk.EventAny evt) { if (main_widget != null) main_widget.stop (); if (account == null) return Gdk.EVENT_PROPAGATE; unowned GLib.List ws = this.application.get_windows (); debug("Windows: %u", ws.length ()); string[] startup_accounts = Settings.get ().get_strv ("startup-accounts"); if (startup_accounts.length == 1 && startup_accounts[0] == "") startup_accounts.resize (0); save_geometry (); if (startup_accounts.length > 0) return Gdk.EVENT_PROPAGATE; int n_main_windows = 0; foreach (Gtk.Window win in ws) if (win is MainWindow && ((MainWindow) win).account != null && ((MainWindow) win).account.screen_name != Account.DUMMY) n_main_windows ++; if (n_main_windows == 1) { // This is the last window so we save this one anyways... string[] new_startup_accounts = new string[1]; new_startup_accounts[0] = ((MainWindow)ws.nth_data (0)).account.screen_name; Settings.get ().set_strv ("startup-accounts", new_startup_accounts); debug ("Saving the account %s", ((MainWindow)ws.nth_data (0)).account.screen_name); } return Gdk.EVENT_PROPAGATE; } private void account_info_changed (string screen_name, string name, Cairo.Surface small_avatar, Cairo.Surface avatar) { this.set_window_title (main_widget.get_page (main_widget.cur_page_id).get_title ()); this.set_title ("Corebird - @%s".printf (screen_name)); } /** * */ private void load_geometry () { if (account == null || account.screen_name == Account.DUMMY) { debug ("Could not load geometry, account == null"); return; } GLib.Variant win_geom = Settings.get ().get_value ("window-geometry"); int x = 0, y = 0, w = 0, h = 0; if (!win_geom.lookup (account.screen_name, "(iiii)", &x, &y, &w, &h)) { warning ("Couldn't load window geometry for screen_name `%s'", account.screen_name); return; } if (w == 0 || h == 0) return; move (x, y); this.set_default_size (w, h); } /** * Saves this window's geometry in the window-geometry gsettings key. */ public void save_geometry () { if (account == null || account.screen_name == Account.DUMMY) return; GLib.Variant win_geom = Settings.get ().get_value ("window-geometry"); GLib.Variant new_geom; GLib.VariantBuilder builder = new GLib.VariantBuilder (new GLib.VariantType("a{s(iiii)}")); var iter = win_geom.iterator (); string? key = null; int x = 0, y = 0, w = 0, h = 0; while (iter.next ("{s(iiii)}", &key, &x, &y, &w, &h)) { if (key != account.screen_name) { builder.add ("{s(iiii)}", key, x, y, w, h); } key = null; // Otherwise we leak key } /* Finally, add this window */ this.get_position (out x, out y); this.get_size (out w, out h); builder.add ("{s(iiii)}", account.screen_name, x, y, w, h); new_geom = builder.end (); debug ("Saving geomentry for %s: %d,%d,%d,%d", account.screen_name, x, y, w, h); Settings.get ().set_value ("window-geometry", new_geom); } private int account_sort_func (Gtk.ListBoxRow a, Gtk.ListBoxRow b) { if (a is AddListEntry) return 1; return ((UserListEntry)a).screen_name.ascii_casecmp (((UserListEntry)b).screen_name); } public void rerun_filters () { /* We only do this for stream + mentions at the moment */ ((DefaultTimeline)get_page (Page.STREAM)).rerun_filters (); ((DefaultTimeline)get_page (Page.MENTIONS)).rerun_filters (); } public void set_window_title (string title, Gtk.StackTransitionType transition_type = Gtk.StackTransitionType.NONE) { this.last_page_label.label = this.title_label.label; this.title_stack.transition_type = Gtk.StackTransitionType.NONE; this.title_stack.visible_child = last_page_label; this.title_stack.transition_type = transition_type; this.title_label.label = title; this.title_stack.visible_child = title_label; } public void reply_to_tweet (int64 tweet_id) { Cb.Tweet? tweet = null; tweet = ((DefaultTimeline)this.main_widget.get_page(Page.STREAM)).tweet_list.model.get_for_id (tweet_id, 0); if (tweet == null) { warning ("tweet with id %s could not be found", tweet_id.to_string ()); return; } var ctw = new ComposeTweetWindow (this, this.account, tweet, ComposeTweetWindow.Mode.REPLY); ctw.show_all (); } public void mark_tweet_as_read (int64 tweet_id) { DefaultTimeline home_timeline = ((DefaultTimeline)this.main_widget.get_page(Page.STREAM)); DefaultTimeline mentions_timeline = ((DefaultTimeline)this.main_widget.get_page(Page.MENTIONS)); Cb.Tweet? tweet = null; tweet = home_timeline.tweet_list.model.get_for_id (tweet_id, 0); if (tweet != null) { tweet.set_seen (true); home_timeline.unread_count --; } // and now with the MentionsTimeline tweet = mentions_timeline.tweet_list.model.get_for_id (tweet_id, 0); if (tweet != null) { tweet.set_seen (true); mentions_timeline.unread_count --; } } } corebird-1.7.4/src/Makefile.am000066400000000000000000000135431324604713000161450ustar00rootroot00000000000000AM_CPPFLAGS = \ $(CB_CFLAGS) \ -include $(CONFIG_HEADER) \ -DDATADIR=\"$(datadir)\" \ -DPKGDATADIR=\"$(pkgdatadir)\" \ -DG_LOG_DOMAIN=\"corebird\" \ -I$(top_srcdir)/src/rest/ AM_VALAFLAGS = \ --thread \ --enable-deprecated \ --enable-checking \ --vapidir $(builddir) \ --vapidir $(top_srcdir)/vapi \ --basedir $(srcdir) \ --directory $(builddir) \ --gresources $(top_srcdir)/corebird.gresource.xml \ -D G_LOG_DOMAIN=\"corebird\" \ $(top_srcdir)/vapi/config.vapi \ $(top_srcdir)/vapi/corebird-internal.vapi \ $(top_srcdir)/vapi/rest-0.7.vapi \ $(top_srcdir)/vapi/libtl.vapi \ -C \ $(NULL) BUILT_SOURCES = \ corebird.h \ corebird-resources.c bin_PROGRAMS = corebird noinst_LTLIBRARIES = libcorebird.la libcorebird_la_VALASOURCES = \ Corebird.vala \ MainWindow.vala \ MainWidget.vala \ Account.vala \ HomeTimeline.vala \ MentionsTimeline.vala \ SearchPage.vala \ DMPage.vala \ IPage.vala \ DefaultTimeline.vala \ DMThreadsPage.vala \ Settings.vala \ NotificationManager.vala \ Twitter.vala \ ProfilePage.vala \ TweetInfoPage.vala \ ListStatusesPage.vala \ ListsPage.vala \ FavoritesTimeline.vala \ FilterPage.vala \ UserEventReceiver.vala \ DMManager.vala \ widgets/TweetListBox.vala \ widgets/MaxSizeContainer.vala \ widgets/ScrollWidget.vala \ widgets/TextButton.vala \ widgets/DoubleTapButton.vala \ widgets/ReplyEntry.vala \ widgets/PixbufButton.vala \ widgets/BadgeRadioButton.vala \ widgets/DMPlaceholderBox.vala \ widgets/AccountCreateWidget.vala \ widgets/AspectImage.vala \ widgets/UserListsWidget.vala \ widgets/ReplyIndicator.vala \ widgets/MultiMediaWidget.vala \ widgets/AvatarWidget.vala \ widgets/AvatarBannerWidget.vala \ widgets/CropWidget.vala \ widgets/AddImageButton.vala \ widgets/CompletionTextView.vala \ widgets/LazyMenuButton.vala \ widgets/ImpostorWidget.vala \ widgets/FollowButton.vala \ widgets/MediaButton.vala \ widgets/ComposeImageManager.vala \ widgets/FavImageView.vala \ util/TweetUtils.vala \ util/Utils.vala \ util/UserCompletion.vala \ util/Dirs.vala \ util/UserUtils.vala \ list/TweetListEntry.vala \ list/DMListEntry.vala \ list/UserListEntry.vala \ list/DMThreadEntry.vala \ list/StartConversationEntry.vala \ list/ListListEntry.vala \ list/NewListEntry.vala \ list/FilterListEntry.vala \ list/UserFilterEntry.vala \ list/AddListEntry.vala \ list/SnippetListEntry.vala \ list/FavImageRow.vala \ util/Benchmark.vala \ window/ComposeTweetWindow.vala \ window/SettingsDialog.vala \ window/UserListDialog.vala \ window/ModifyFilterDialog.vala \ window/AboutDialog.vala \ window/AccountDialog.vala \ window/MediaDialog.vala \ window/ModifySnippetDialog.vala \ sql/Database.vala \ sql/InsertStatement.vala \ sql/UpdateStatement.vala \ sql/SelectStatement.vala \ async/Collect.vala \ model/DMThreadsModel.vala nodist_libcorebird_la_SOURCES = \ libcorebird_la_vala.stamp \ $(libcorebird_la_VALASOURCES:.vala=.c) corebird.h corebird.vapi: libcorebird_la_vala.stamp $(libcorebird_la_VALASOURCES:.vala=.c): libcorebird_la_vala.stamp libcorebird_la_SOURCES = \ CbMedia.c \ CbMedia.h \ CbMediaDownloader.c \ CbMediaDownloader.h \ CbTypes.c \ CbTypes.h \ CbTextTransform.c \ CbTextTransform.h \ CbTweet.c \ CbTweet.h \ CbFilter.c \ CbFilter.h \ CbAvatarCache.c \ CbAvatarCache.h \ CbUserCounter.c \ CbUserCounter.h \ CbMediaImageWidget.h \ CbMediaImageWidget.c \ CbTweetModel.h \ CbTweetModel.c \ CbTwitterItem.h \ CbTwitterItem.c \ CbDeltaUpdater.h \ CbDeltaUpdater.c \ CbUtils.h \ CbUtils.c \ CbBundle.h \ CbBundle.c \ CbBundleHistory.h \ CbBundleHistory.c \ CbSnippetManager.h \ CbSnippetManager.c \ CbMediaVideoWidget.h \ CbMediaVideoWidget.c \ CbSurfaceProgress.h \ CbSurfaceProgress.c \ CbMessageReceiver.h \ CbMessageReceiver.c \ CbUserStream.h \ CbUserStream.c \ CbComposeJob.h \ CbComposeJob.c \ CbGtkCompat.h \ CbUserCompletionModel.c \ CbUserCompletionModel.h \ CbEmojiChooser.c \ CbEmojiChooser.h \ rest/rest/rest-param.c \ rest/rest/rest-params.c \ rest/rest/rest-proxy.c \ rest/rest/rest-proxy-call.c \ rest/rest/rest-proxy-call-private.h \ rest/rest/rest-main.c \ rest/rest/rest-private.h \ rest/rest/oauth-proxy.c \ rest/rest/oauth-proxy-call.c \ rest/rest/oauth-proxy-private.h \ rest/rest/sha1.c \ rest/rest/sha1.h \ rest/rest/rest-param.h \ rest/rest/rest-params.h \ rest/rest/rest-proxy.h \ rest/rest/rest-proxy-call.h \ rest/rest/oauth-proxy.h \ rest/rest/oauth-proxy-call.h \ libtl/libtweetlength.h \ libtl/libtweetlength.c \ libtl/data.h libcorebird_la_vala.stamp: $(libcorebird_la_VALASOURCES) Makefile @$(VALAC) \ $(AM_VALAFLAGS) $(CB_VALA_FLAGS) \ -H corebird.h \ --library corebird \ --vapi corebird.vapi \ $(filter %.vala %.c,$^) touch $@ libcorebird_la_LIBADD = $(CB_LIBS) corebird_VALASOURCES = \ main.vala nodist_corebird_SOURCES = \ corebird_vala.stamp \ corebird-resources.c \ $(corebird_VALASOURCES:.vala=.c) corebird_LDADD = \ $(CB_LIBS) \ $(builddir)/libcorebird.la corebird_VALAFLAGS = \ $(CB_VALA_FLAGS) \ corebird.vapi $(corebird_VALASOURCES:.vala=.c): corebird_vala.stamp corebird_vala.stamp: $(builddir)/libcorebird.la $(builddir)/corebird.vapi $(corebird_VALASOURCES) Makefile @$(VALAC) \ $(AM_VALAFLAGS) $(CB_VALA_FLAGS) \ --pkg corebird \ $(filter %.vala %.c,$^) touch $@ resource_deps = $(shell $(GLIB_COMPILE_RESOURCES) --generate-dependencies --sourcedir=$(top_srcdir) $(top_srcdir)/corebird.gresource.xml) corebird-resources.c: $(top_srcdir)/corebird.gresource.xml $(resource_deps) Makefile XMLLINT=$(XMLLINT) $(GLIB_COMPILE_RESOURCES) --target $@ --generate --sourcedir=$(top_srcdir) --c-name corebird $< CLEANFILES = \ libcorebird_la_vala.stamp \ corebird_vala.stamp \ $(libcorebird_la_VALASOURCES:.vala=.c) \ $(nodist_corebird_SOURCES) \ corebird.vapi \ corebird.h EXTRA_DIST = \ $(libcorebird_la_VALASOURCES) \ $(corebird_VALASOURCES) \ $(corebird_SOURCES) \ $(resource_deps) corebird-1.7.4/src/MentionsTimeline.vala000066400000000000000000000103541324604713000202360ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class MentionsTimeline : Cb.MessageReceiver, DefaultTimeline { protected override string function { get { return "1.1/statuses/mentions_timeline.json"; } } public MentionsTimeline(int id, Account account) { base (id); this.account = account; this.tweet_list.account= account; } private void stream_message_received (Cb.StreamMessageType type, Json.Node root) { if (type == Cb.StreamMessageType.TWEET) { add_tweet (root); } else if (type == Cb.StreamMessageType.DELETE) { int64 id = root.get_object ().get_object_member ("delete") .get_object_member ("status").get_int_member ("id"); delete_tweet (id); } else if (type == Cb.StreamMessageType.EVENT_FAVORITE) { int64 id = root.get_object ().get_object_member ("target_object").get_int_member ("id"); toggle_favorite (id, true); } else if (type == Cb.StreamMessageType.EVENT_UNFAVORITE) { int64 id = root.get_object ().get_object_member ("target_object").get_int_member ("id"); toggle_favorite (id, false); } } private void add_tweet (Json.Node root_node) { /* Mark tweets as seen the user has already replied to */ var root = root_node.get_object (); var author = root.get_object_member ("user"); if (author.get_int_member ("id") == account.id && !root.get_null_member ("in_reply_to_status_id")) { mark_seen (root.get_int_member ("in_reply_to_status_id")); return; } if (root.get_string_member ("text").contains ("@" + account.screen_name)) { GLib.DateTime now = new GLib.DateTime.now_local (); var t = new Cb.Tweet (); t.load_from_json (root_node, account.id, now); if (t.get_user_id () == account.id) return; if (t.retweeted_tweet != null && get_rt_flags (t) > 0) return; if (account.filter_matches (t)) return; if (account.blocked_or_muted (t.get_user_id ())) return; this.balance_next_upper_change (TOP); t.set_seen (false); tweet_list.model.add (t); base.scroll_up (t); this.unread_count ++; if (Settings.notify_new_mentions ()) { string text; if (t.retweeted_tweet != null) text = Utils.unescape_html (t.retweeted_tweet.text); else text = Utils.unescape_html (t.source_tweet.text); /* Ignore the mention if both accounts are configured */ if (Account.query_account_by_id (t.get_user_id ()) == null) { string summary = _("%s mentioned %s").printf (Utils.unescape_html (t.get_user_name ()), account.name); string id = "%s-%s".printf (account.id.to_string (), "mention"); var tuple = new GLib.Variant.tuple ({account.id, t.id}); var notification = new GLib.Notification (summary); notification.set_body (text); notification.set_default_action_and_target_value ("app.show-window", account.id); notification.add_button_with_target_value ("Mark read", "app.mark-read", tuple); notification.add_button_with_target_value ("Reply", "app.reply-to-tweet", tuple); t.notification_id = id; GLib.Application.get_default ().send_notification (id, notification); } } } } public override string get_title () { return _("Mentions"); } public override void create_radio_button (Gtk.RadioButton? group) { radio_button = new BadgeRadioButton (group, "corebird-mentions-symbolic", _("Mentions")); } } corebird-1.7.4/src/NotificationManager.vala000066400000000000000000000042001324604713000206650ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public class NotificationManager : GLib.Object { private unowned Account account; public NotificationManager (Account account) { this.account = account; } public void withdraw (string id) { GLib.Application.get_default ().withdraw_notification (id); } public string send (string summary, string body, string? id_suffix = null) { var n = new GLib.Notification (summary); n.set_body (body); string id = "%s-%s".printf (account.id.to_string (), id_suffix ?? ""); /* Default action: Bring the account window to the front */ n.set_default_action_and_target_value ("app.show-window", account.id); GLib.Application.get_default ().send_notification (id, n); return id; } public string send_dm (int64 sender_id, string? existing_id, string summary, string text) { if (existing_id != null) { this.withdraw (existing_id); } string new_id = "new-dm-%s".printf (sender_id.to_string ()); var n = new GLib.Notification (summary); var value = new GLib.Variant.tuple ({new GLib.Variant.int64 (account.id), new GLib.Variant.int64 (sender_id)}); n.set_default_action_and_target_value ("app.show-dm-thread", value); n.set_body (text); GLib.Application.get_default ().send_notification (new_id, n); return new_id; } } corebird-1.7.4/src/ProfilePage.vala000066400000000000000000000762541324604713000171630ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/profile-page.ui")] class ProfilePage : ScrollWidget, IPage, Cb.MessageReceiver { private const GLib.ActionEntry[] action_entries = { {"write-dm", write_dm_activated}, {"tweet-to", tweet_to_activated}, {"add-remove-list", add_remove_list_activated}, }; public const int KEY_SCREEN_NAME = 0; public const int KEY_USER_ID = 1; public int unread_count { get { return 0; } } private unowned MainWindow main_window; public unowned MainWindow window { set { main_window = value; user_lists.main_window = value; } } public unowned Account account; public int id { get; set; } [GtkChild] private AspectImage banner_image; [GtkChild] private AvatarWidget avatar_image; [GtkChild] private Gtk.Label name_label; [GtkChild] private Gtk.Label screen_name_label; [GtkChild] private Gtk.Label description_label; [GtkChild] private Gtk.Label url_label; [GtkChild] private Gtk.Label tweets_label; [GtkChild] private Gtk.Label following_label; [GtkChild] private Gtk.Label followers_label; [GtkChild] private Gtk.Label location_label; [GtkChild] private FollowButton follow_button; [GtkChild] private TweetListBox tweet_list; [GtkChild] private TweetListBox followers_list; [GtkChild] private TweetListBox following_list; [GtkChild] private Gtk.Spinner progress_spinner; [GtkChild] private Gtk.Label follows_you_label; [GtkChild] private UserListsWidget user_lists; [GtkChild] private Gtk.Stack user_stack; [GtkChild] private Gtk.MenuButton more_button; [GtkChild] private Gtk.Stack loading_stack; [GtkChild] private Gtk.RadioButton tweets_button; [GtkChild] private Gtk.Label loading_error_label; private int64 user_id; private new string name; private string screen_name; private string avatar_url; private int follower_count = -1; private GLib.Cancellable data_cancellable; private bool lists_page_inited = false; private bool block_item_blocked = false; private bool retweet_item_blocked = false; private bool mute_item_blocked = false; private bool tweets_loading = false; private bool followers_loading = false; private Cursor? followers_cursor = null; private bool following_loading = false; private Cursor? following_cursor = null; private GLib.SimpleActionGroup actions; public ProfilePage (int id, Account account) { this.id = id; this.account = account; this.user_lists.account = account; this.tweet_list.account = account; this.scrolled_to_end.connect (() => { if (user_stack.visible_child == tweet_list) { this.load_older_tweets.begin (); } else if (user_stack.visible_child == followers_list) { this.load_followers.begin (); } else if (user_stack.visible_child == following_list) { this.load_following.begin (); } }); tweet_list.row_activated.connect ((row) => { var bundle = new Cb.Bundle (); bundle.put_int (TweetInfoPage.KEY_MODE, TweetInfoPage.BY_INSTANCE); bundle.put_object (TweetInfoPage.KEY_TWEET, ((TweetListEntry)row).tweet); main_window.main_widget.switch_page (Page.TWEET_INFO, bundle); }); followers_list.row_activated.connect ((row) => { var bundle = new Cb.Bundle (); bundle.put_int64 (ProfilePage.KEY_USER_ID, ((UserListEntry)row).user_id); bundle.put_string (ProfilePage.KEY_SCREEN_NAME, ((UserListEntry)row).screen_name); main_window.main_widget.switch_page (Page.PROFILE, bundle); }); following_list.row_activated.connect ((row) => { var bundle = new Cb.Bundle (); bundle.put_int64 (ProfilePage.KEY_USER_ID, ((UserListEntry)row).user_id); bundle.put_string (ProfilePage.KEY_SCREEN_NAME, ((UserListEntry)row).screen_name); main_window.main_widget.switch_page (Page.PROFILE, bundle); }); user_lists.hide_user_list_entry (); actions = new GLib.SimpleActionGroup (); actions.add_action_entries (action_entries, this); GLib.SimpleAction block_action = new GLib.SimpleAction.stateful ("toggle-blocked", null, new GLib.Variant.boolean (false)); block_action.activate.connect (toggle_blocked_activated); actions.add_action (block_action); GLib.SimpleAction mute_action = new GLib.SimpleAction.stateful ("toggle-muted", null, new GLib.Variant.boolean (false)); mute_action.activate.connect (toggle_muted_activated); actions.add_action (mute_action); GLib.SimpleAction rt_action = new GLib.SimpleAction.stateful ("toggle-retweets", null, new GLib.Variant.boolean (false)); rt_action.activate.connect (retweet_action_activated); actions.add_action (rt_action); this.insert_action_group ("user", actions); } private void set_user_id (int64 user_id) { this.user_id = user_id; follow_button.sensitive = (user_id != account.id); loading_stack.visible_child_name = "progress"; progress_spinner.start (); set_banner (null); load_friendship.begin (); load_profile_data.begin (user_id); } private async void load_friendship () { /* Set muted and blocked status now, let the friendship update it */ set_user_blocked (account.is_blocked (user_id)); set_user_muted (account.is_muted (user_id)); /* We (maybe) re-enable this later when the friendship object has arrived */ ((SimpleAction)actions.lookup_action ("toggle-retweets")).set_enabled (false); ((SimpleAction)actions.lookup_action ("add-remove-list")).set_enabled (user_id != account.id); ((SimpleAction)actions.lookup_action ("write-dm")).set_enabled (user_id != account.id); ((SimpleAction)actions.lookup_action ("toggle-blocked")).set_enabled (user_id != account.id); ((SimpleAction)actions.lookup_action ("toggle-muted")).set_enabled (user_id != account.id); uint fr = yield UserUtils.load_friendship (account, this.user_id, this.screen_name); follows_you_label.visible = (fr & FRIENDSHIP_FOLLOWED_BY) > 0; set_user_blocked ((fr & FRIENDSHIP_BLOCKING) > 0); set_retweets_disabled ((fr & FRIENDSHIP_FOLLOWING) > 0 && (fr & FRIENDSHIP_WANT_RETWEETS) == 0); if ((fr & FRIENDSHIP_CAN_DM) == 0) ((SimpleAction)actions.lookup_action ("write-dm")).set_enabled (false); ((SimpleAction)actions.lookup_action ("toggle-retweets")).set_enabled ((fr & FRIENDSHIP_FOLLOWING) > 0); } private async void load_profile_data (int64 user_id) { follow_button.sensitive = false; var call = account.proxy.new_call (); call.set_method ("GET"); call.set_function ("1.1/users/show.json"); if (user_id != 0) call.add_param ("user_id", user_id.to_string ()); else call.add_param ("screen_name", this.screen_name); call.add_param ("include_entities", "false"); Json.Node? root_node = null; try { root_node = yield Cb.Utils.load_threaded_async (call, data_cancellable); } catch (GLib.Error e) { if (e.message == "Forbidden") { loading_error_label.label = _("Suspended Account"); loading_stack.visible_child = loading_error_label; } else { warning (e.message); } return; } if (root_node == null) return; var root = root_node.get_object(); int64 id = root.get_int_member ("id"); this.user_id = id; string avatar_url = root.get_string_member("profile_image_url"); int scale = this.get_scale_factor (); /* Always load the 200x200 px version even in loDPI since there's no 100x100px version */ avatar_url = avatar_url.replace ("_normal", "_200x200"); // We don't use our AvatarCache here because this (100×100) avatar is only // ever loaded here. TweetUtils.download_avatar.begin (avatar_url, 100 * scale, data_cancellable, (obj, res) => { Cairo.Surface surface; try { var pixbuf = TweetUtils.download_avatar.end (res); if (pixbuf == null) { surface = scale_surface ((Cairo.ImageSurface)Twitter.no_avatar, 100, 100); } else { surface = Gdk.cairo_surface_create_from_pixbuf (pixbuf, scale, null); } } catch (GLib.Error e) { warning (e.message); surface = Twitter.no_avatar; } avatar_image.surface = surface; progress_spinner.stop (); loading_stack.visible_child_name = "data"; }); string name = root.get_string_member("name").replace ("&", "&").strip (); string screen_name = root.get_string_member("screen_name"); string description = root.get_string_member("description"); int followers = (int)root.get_int_member("followers_count"); int following = (int)root.get_int_member("friends_count"); int tweets = (int)root.get_int_member("statuses_count"); bool is_following = false; if (Utils.usable_json_value (root, "following")) is_following = root.get_boolean_member("following"); bool has_url = root.get_object_member("entities").has_member("url"); bool verified = root.get_boolean_member ("verified"); bool protected_user = root.get_boolean_member ("protected"); if (protected_user) { tweet_list.set_placeholder_text (_("Protected profile")); } string color = root.get_string_member ("profile_background_color"); banner_image.color_string = "#" + color; if (root.has_member ("profile_banner_url")) { string banner_base_url = root.get_string_member ("profile_banner_url"); load_profile_banner (banner_base_url); } string display_url = ""; Json.Object entities = root.get_object_member ("entities"); if (has_url) { var urls_object = entities.get_object_member("url").get_array_member("urls"). get_element(0).get_object(); var url = urls_object.get_string_member("expanded_url"); if (urls_object.has_member ("display_url")) { display_url = urls_object.get_string_member("expanded_url"); } else { url = urls_object.get_string_member("url"); display_url = url; } } string location = null; if (root.has_member("location")) { location = root.get_string_member("location"); } Cb.TextEntity[]? text_urls = null; if (root.has_member ("description")) { int n_tl_entities = 0; Tl.Entity[]? tl_entities = Tl.extract_entities (description, null); // We just add hashtags and mentions ourselves and leave links to Twitter foreach (Tl.Entity e in tl_entities) { if (e.type == Tl.EntityType.HASHTAG || e.type == Tl.EntityType.MENTION) n_tl_entities ++; } Json.Array urls = entities.get_object_member ("description").get_array_member ("urls"); text_urls = new Cb.TextEntity[urls.get_length () + n_tl_entities]; urls.foreach_element ((arr, i, node) => { var ent = node.get_object (); string expanded_url = ent.get_string_member ("expanded_url"); /* We do *not* escape ampersands as & here, since we will later do that on the entire description when setting the text of our description_label. Contrary to normal tweets, profile descriptions don't come with pre-escaped ampersands */ Json.Array indices = ent.get_array_member ("indices"); text_urls[i] = Cb.TextEntity(){ from = (uint)indices.get_int_element (0), to = (uint)indices.get_int_element (1), target = expanded_url, display_text = ent.get_string_member ("display_url") }; }); // Adding them now is fine since we will sort them later int i = (int)urls.get_length (); foreach (Tl.Entity e in tl_entities) { if (e.type != Tl.EntityType.HASHTAG && e.type != Tl.EntityType.MENTION) continue; if (e.type == Tl.EntityType.HASHTAG) { text_urls[i] = Cb.TextEntity () { from = (uint)e.start_character_index, to = (uint)(e.start_character_index + e.length_in_characters), target = e.start->ndup(e.length_in_bytes), display_text = e.start->ndup(e.length_in_bytes), tooltip_text = e.start->ndup(e.length_in_bytes) }; } else if (e.type == Tl.EntityType.MENTION) { text_urls[i] = Cb.TextEntity () { from = (uint)e.start_character_index, to = (uint)(e.start_character_index + e.length_in_characters), target = "@0/%.*s".printf (e.length_in_bytes, e.start), display_text = e.start->ndup(e.length_in_bytes), tooltip_text = e.start->ndup(e.length_in_bytes) }; } i ++; } } account.user_counter.user_seen (id, screen_name, name); this.follow_button.following = is_following; this.follow_button.sensitive = (this.user_id != this.account.id); var section = (GLib.Menu)more_button.menu_model.get_item_link (0, GLib.Menu.LINK_SECTION); var user_item = new GLib.MenuItem (_("Tweet to @%s").printf (screen_name.replace ("_", "__")), "user.tweet-to"); section.remove (1); section.insert_item (1, user_item); name_label.set_markup (name.strip ()); screen_name_label.set_label ("@" + screen_name); string desc = description; if (text_urls != null) { TweetUtils.sort_entities (ref text_urls); desc = Cb.TextTransform.text (description, text_urls, 0, 0, 0); } this.follower_count = followers; description_label.label = "%s".printf (desc.replace ("&", "&")); tweets_label.label = "%'d".printf(tweets); following_label.label = "%'d".printf(following); update_follower_label (); if (location != null && location != "") { location_label.visible = true; location_label.label = location; } else location_label.visible = false; avatar_image.verified = verified; if (display_url.length > 0) { url_label.visible = true; url_label.set_markup ("%s" .printf (display_url, display_url)); description_label.margin_bottom = 6; } else { url_label.visible = false; description_label.margin_bottom = 12; } this.name = name; this.screen_name = screen_name; this.avatar_url = avatar_url; } private async void load_tweets () { tweet_list.set_unempty (); tweets_loading = true; int requested_tweet_count = 10; var call = account.proxy.new_call (); call.set_function ("1.1/statuses/user_timeline.json"); call.set_method ("GET"); if (user_id != 0) call.add_param ("user_id", this.user_id.to_string ()); else call.add_param ("screen_name", this.screen_name); call.add_param ("count", requested_tweet_count.to_string ()); call.add_param ("contributor_details", "true"); call.add_param ("tweet_mode", "extended"); call.add_param ("include_my_retweet", "true"); Json.Node? root = null; try { root = yield Cb.Utils.load_threaded_async (call, data_cancellable); } catch (GLib.Error e) { if (e.message != "Authorization Required") { warning (e.message); } tweet_list.set_empty (); return; } if (root == null) return; var root_array = root.get_array (); if (root_array.get_length () == 0) { tweet_list.set_empty (); return; } TweetUtils.work_array (root_array, tweet_list, account); tweets_loading = false; } private async void load_older_tweets () { if (tweets_loading) return; if (user_stack.visible_child != tweet_list) return; tweets_loading = true; int requested_tweet_count = 15; var call = account.proxy.new_call (); call.set_function ("1.1/statuses/user_timeline.json"); call.set_method ("GET"); call.add_param ("user_id", this.user_id.to_string ()); call.add_param ("count", requested_tweet_count.to_string ()); call.add_param ("contributor_details", "true"); call.add_param ("include_my_retweet", "true"); call.add_param ("tweet_mode", "extended"); call.add_param ("max_id", (tweet_list.model.min_id - 1).to_string ()); Json.Node? root = null; try { root = yield Cb.Utils.load_threaded_async (call, data_cancellable); } catch (GLib.Error e) { warning (e.message); return; } if (root == null) return; var root_arr = root.get_array (); TweetUtils.work_array (root_arr, tweet_list, account); tweets_loading = false; } private async void load_followers () { if (this.followers_cursor != null && this.followers_cursor.full) return; if (this.followers_loading) return; this.followers_loading = true; this.followers_cursor = yield UserUtils.load_followers (this.account, this.user_id, this.followers_cursor); if (this.followers_cursor == null) { this.followers_list.set_placeholder_text (_("Protected Profile")); this.followers_list.set_empty (); return; } var users_array = this.followers_cursor.json_object.get_array (); users_array.foreach_element ((array, index, node) => { var user_obj = node.get_object (); string avatar_url = user_obj.get_string_member ("profile_image_url"); if (this.get_scale_factor () == 2) avatar_url = avatar_url.replace ("_normal", "_bigger"); var entry = new UserListEntry (); entry.show_settings = false; entry.user_id = user_obj.get_int_member ("id"); entry.set_screen_name ("@" + user_obj.get_string_member ("screen_name")); entry.name = user_obj.get_string_member ("name"); entry.avatar_url = avatar_url; entry.get_style_context ().add_class ("tweet"); entry.show (); this.followers_list.add (entry); }); this.followers_loading = false; } private async void load_following () { if (this.following_cursor != null && this.following_cursor.full) return; if (this.following_loading) return; this.following_loading = true; this.following_cursor = yield UserUtils.load_following (this.account, this.user_id, this.following_cursor); if (this.following_cursor == null) { message ("null cursor"); this.following_list.set_placeholder_text (_("Protected Profile")); this.following_list.set_empty (); return; } var users_array = this.following_cursor.json_object.get_array (); users_array.foreach_element ((array, index, node) => { var user_obj = node.get_object (); string avatar_url = user_obj.get_string_member ("profile_image_url"); if (this.get_scale_factor () == 2) avatar_url = avatar_url.replace ("_normal", "_bigger"); var entry = new UserListEntry (); entry.show_settings = false; entry.user_id = user_obj.get_int_member ("id"); entry.set_screen_name ("@" + user_obj.get_string_member ("screen_name")); entry.name = user_obj.get_string_member ("name"); entry.avatar_url = avatar_url; entry.get_style_context ().add_class ("tweet"); entry.show (); this.following_list.add (entry); }); this.following_loading = false; } private void load_profile_banner (string base_url) { string banner_url = base_url + "/mobile_retina"; Utils.download_pixbuf.begin (banner_url, null, (obj, res) => { Gdk.Pixbuf? banner = Utils.download_pixbuf.end (res); set_banner (banner); }); } [GtkCallback] private void follow_button_clicked_cb () { var call = account.proxy.new_call(); HomeTimeline ht = (HomeTimeline) main_window.get_page (Page.STREAM); if (follow_button.following) { call.set_function( "1.1/friendships/destroy.json"); ht.hide_tweets_from (this.user_id, Cb.TweetState.HIDDEN_UNFOLLOWED); ht.hide_retweets_from (this.user_id, Cb.TweetState.HIDDEN_UNFOLLOWED); follower_count --; account.unfollow_id (this.user_id); ((SimpleAction)actions.lookup_action ("toggle-retweets")).set_enabled (false); set_retweets_disabled (false); } else { call.set_function ("1.1/friendships/create.json"); call.add_param ("follow", "false"); ht.show_tweets_from (this.user_id, Cb.TweetState.HIDDEN_UNFOLLOWED); if (!((SimpleAction)actions.lookup_action ("toggle-retweets")).get_state ().get_boolean ()) { ht.show_retweets_from (this.user_id, Cb.TweetState.HIDDEN_UNFOLLOWED); } set_user_blocked (false); follower_count ++; account.follow_id (this.user_id); ((SimpleAction)actions.lookup_action ("toggle-retweets")).set_enabled (true); } update_follower_label (); follow_button.sensitive = false; call.set_method ("POST"); call.add_param ("id", user_id.to_string ()); call.invoke_async.begin (null, (obj, res) => { try { this.follow_button.following = !this.follow_button.following; this.follow_button.sensitive = (this.user_id != this.account.id); call.invoke_async.end (res); } catch (GLib.Error e) { critical (e.message); critical (call.get_payload ()); } follow_button.sensitive = true; }); } [GtkCallback] private bool activate_link (string uri) { return TweetUtils.activate_link (uri, main_window); } private inline void set_banner (Gdk.Pixbuf? banner) { if (banner == null) banner_image.pixbuf = Twitter.no_banner; else banner_image.pixbuf = banner; } /** * see IPage#onJoin */ public void on_join (int page_id, Cb.Bundle? args) { int64 user_id = args.get_int64 (KEY_USER_ID); if (user_id == -1) return; string? screen_name = args.get_string (KEY_SCREEN_NAME); if (screen_name != null) { this.screen_name = screen_name; } data_cancellable = new GLib.Cancellable (); if (user_id != this.user_id) { reset_data (); followers_cursor = null; followers_list.remove_all (); following_cursor = null; following_list.remove_all (); set_user_id (user_id); if (account.follows_id (user_id)) { this.follow_button.following = true; this.follow_button.sensitive = true; } tweet_list.model.clear (); user_lists.clear_lists (); lists_page_inited = false; load_tweets.begin (); } else { /* Still load the friendship since muted/blocked/etc. may have changed */ load_friendship.begin (); } tweet_list.reset_placeholder_text (); followers_list.reset_placeholder_text (); following_list.reset_placeholder_text (); tweets_button.active = true; //user_stack.visible_child = tweet_list; } public void on_leave () { // We might otherwise overwrite the new user's data with that from the old one. data_cancellable.cancel (); more_button.get_popover ().hide (); } private void reset_data () { name_label.label = " "; screen_name_label.label = " "; description_label.label = " "; url_label.label = " "; location_label.label = " "; tweets_label.label = " "; following_label.label = " "; followers_label.label = " "; avatar_image.surface = null; } public void create_radio_button (Gtk.RadioButton? group) {} public string get_title () { return "@" + screen_name; } public Gtk.RadioButton? get_radio_button(){ return null; } private void write_dm_activated (GLib.SimpleAction a, GLib.Variant? v) { var bundle = new Cb.Bundle (); bundle.put_int64 (DMPage.KEY_SENDER_ID, user_id); bundle.put_string (DMPage.KEY_SCREEN_NAME, screen_name); bundle.put_string (DMPage.KEY_USER_NAME, name); bundle.put_string (DMPage.KEY_AVATAR_URL, avatar_url.replace ("_bigger", "_normal")); main_window.main_widget.switch_page (Page.DM, bundle); } private void tweet_to_activated (GLib.SimpleAction a, GLib.Variant? v) { var cw = new ComposeTweetWindow (main_window, account, null); cw.set_text ("@" + screen_name + " "); cw.show_all (); } private void add_remove_list_activated (GLib.SimpleAction a, GLib.Variant? v) { var uld = new UserListDialog (main_window, account, user_id); uld.load_lists (); uld.show_all (); } private void toggle_blocked_activated (GLib.SimpleAction a, GLib.Variant? v) { if (block_item_blocked) return; block_item_blocked = true; bool current_state = get_user_blocked (); HomeTimeline ht = (HomeTimeline) main_window.get_page (Page.STREAM); var call = account.proxy.new_call (); call.set_method ("POST"); if (current_state) { call.set_function ("1.1/blocks/destroy.json"); ht.show_tweets_from (this.user_id, Cb.TweetState.HIDDEN_AUTHOR_BLOCKED); } else { call.set_function ("1.1/blocks/create.json"); this.follow_button.following = false; this.follow_button.sensitive = (this.user_id != this.account.id); ht.hide_tweets_from (this.user_id, Cb.TweetState.HIDDEN_AUTHOR_BLOCKED); } set_user_blocked (!current_state); call.add_param ("user_id", this.user_id.to_string ()); call.invoke_async.begin (null, (obj, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), e.message, GLib.Log.LINE, GLib.Log.FILE, this.main_window); /* Reset the state if the blocking failed */ a.set_state (new GLib.Variant.boolean (current_state)); } block_item_blocked = false; }); } private void toggle_muted_activated (GLib.SimpleAction a, GLib.Variant? v) { bool setting = get_user_muted (); mute_item_blocked = true; a.set_state (!setting); UserUtils.mute_user.begin (account,this.user_id, !setting, (obj, res) => { UserUtils.mute_user.end (res); mute_item_blocked = false; HomeTimeline ht = (HomeTimeline) main_window.get_page (Page.STREAM); if (setting) { ht.show_tweets_from (this.user_id, Cb.TweetState.HIDDEN_AUTHOR_MUTED); ht.show_retweets_from (this.user_id, Cb.TweetState.HIDDEN_RETWEETER_MUTED); } else { ht.hide_tweets_from (this.user_id, Cb.TweetState.HIDDEN_AUTHOR_MUTED); ht.hide_retweets_from (this.user_id, Cb.TweetState.HIDDEN_RETWEETER_MUTED); } }); } private void retweet_action_activated (GLib.SimpleAction a, GLib.Variant? v) { if (retweet_item_blocked) return; retweet_item_blocked = true; bool current_state = a.get_state ().get_boolean (); a.set_state (new GLib.Variant.boolean (!current_state)); var call = account.proxy.new_call (); call.set_function ("1.1/friendships/update.json"); call.set_method ("POST"); call.add_param ("user_id", this.user_id.to_string ()); call.add_param ("retweets", current_state.to_string ()); HomeTimeline ht = (HomeTimeline) main_window.get_page (Page.STREAM); if (current_state) { ht.show_retweets_from (this.user_id, Cb.TweetState.HIDDEN_RTS_DISABLED); account.remove_disabled_rts_id (this.user_id); } else { ht.hide_retweets_from (this.user_id, Cb.TweetState.HIDDEN_RTS_DISABLED); account.add_disabled_rts_id (this.user_id); } call.invoke_async.begin (null, (obj, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), e.message, GLib.Log.LINE, GLib.Log.FILE, this.main_window); /* Reset the state if the retweeting failed */ a.set_state (new GLib.Variant.boolean (current_state)); } retweet_item_blocked = false; }); } private void set_user_blocked (bool blocked) { ((SimpleAction)actions.lookup_action ("toggle-blocked")).set_state (new GLib.Variant.boolean (blocked)); } private bool get_user_blocked () { return ((SimpleAction)actions.lookup_action ("toggle-blocked")).get_state ().get_boolean (); } private void set_user_muted (bool muted) { ((SimpleAction)actions.lookup_action ("toggle-muted")).set_state (new GLib.Variant.boolean (muted)); } private bool get_user_muted () { return ((SimpleAction)actions.lookup_action ("toggle-muted")).get_state ().get_boolean (); } private void set_retweets_disabled (bool disabled) { ((SimpleAction)actions.lookup_action ("toggle-retweets")).set_state (new GLib.Variant.boolean (disabled)); } private void update_follower_label () { followers_label.label = "%'d".printf(follower_count); } public void stream_message_received (Cb.StreamMessageType type, Json.Node root_node) { if (type == Cb.StreamMessageType.TWEET) { var obj = root_node.get_object (); var user = obj.get_object_member ("user"); if (user.get_int_member ("id") != this.user_id) return; // Correct user! var tweet = new Cb.Tweet (); tweet.load_from_json (root_node, account.id, new GLib.DateTime.now_local ()); this.tweet_list.model.add (tweet); } } [GtkCallback] private void tweets_button_toggled_cb (GLib.Object source) { if (((Gtk.RadioButton)source).active) { this.balance_next_upper_change (BOTTOM); user_stack.visible_child = tweet_list; } } [GtkCallback] private void followers_button_toggled_cb (GLib.Object source) { if (((Gtk.RadioButton)source).active) { if (this.followers_cursor == null) { this.load_followers.begin (); } this.balance_next_upper_change (BOTTOM); user_stack.visible_child = followers_list; } } [GtkCallback] private void following_button_toggled_cb (GLib.Object source) { if (((Gtk.RadioButton)source).active) { if (this.following_cursor == null) { this.load_following.begin (); } this.balance_next_upper_change (BOTTOM); user_stack.visible_child = following_list; } } [GtkCallback] private void lists_button_toggled_cb (GLib.Object source) { if (((Gtk.RadioButton)source).active) { if (!lists_page_inited) { user_lists.load_lists.begin (user_id); lists_page_inited = true; } this.balance_next_upper_change (BOTTOM); user_stack.visible_child = user_lists; } } } corebird-1.7.4/src/SearchPage.vala000066400000000000000000000303161324604713000167550ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/search-page.ui")] class SearchPage : IPage, Gtk.Box { public const int KEY_QUERY = 0; private const int USER_COUNT = 3; /** The unread count here is always zero */ public int unread_count { get { return 0; } } public unowned Account account; public int id { get; set; } private unowned MainWindow main_window; public unowned MainWindow window { set { main_window = value; } } [GtkChild] private Gtk.SearchEntry search_entry; [GtkChild] private Gtk.Button search_button; [GtkChild] private TweetListBox tweet_list; [GtkChild] private Gtk.Label users_header; [GtkChild] private Gtk.Label tweets_header; [GtkChild] private ScrollWidget scroll_widget; private Gtk.RadioButton radio_button; private GLib.Cancellable? cancellable = null; private LoadMoreEntry load_more_entry = new LoadMoreEntry (); private string search_query; private int user_page = 1; private int64 lowest_tweet_id = int64.MAX-1; private Gtk.Widget last_focus_widget; private int n_results = 0; private Collect collect_obj; private uint remove_content_timeout = 0; private string last_search_query; private bool loading_tweets = false; private bool loading_users = false; public SearchPage (int id, Account account) { this.id = id; this.account = account; /* We are slightly abusing the TweetListBox here */ tweet_list.bind_model (null, null); tweet_list.set_header_func (header_func); tweet_list.set_sort_func (twitter_item_sort_func); tweet_list.row_activated.connect (row_activated_cb); tweet_list.retry_button_clicked.connect (retry_button_clicked_cb); search_button.clicked.connect (() => { search_for (search_entry.get_text()); }); load_more_entry.get_button ().clicked.connect (() => { user_page++; load_users (); }); scroll_widget.scrolled_to_end.connect (load_tweets); tweet_list.get_placeholder ().hide (); tweet_list.set_adjustment (scroll_widget.get_vadjustment ()); } [GtkCallback] private void search_entry_activate_cb () { search_for (search_entry.get_text ()); } private void retry_button_clicked_cb () { search_for (last_search_query); } /** * see IPage#onJoin */ public void on_join (int page_id, Cb.Bundle? args) { string? term = args != null ? args.get_string (KEY_QUERY) : null; if (this.remove_content_timeout != 0) { GLib.Source.remove (this.remove_content_timeout); this.remove_content_timeout = 0; } if (term == null) { if (last_focus_widget != null && last_focus_widget.parent != null) last_focus_widget.grab_focus (); else search_entry.grab_focus (); return; } search_for (term, true); } public override void dispose () { if (this.remove_content_timeout != 0) { GLib.Source.remove (this.remove_content_timeout); this.remove_content_timeout = 0; } base.dispose (); } public void on_leave () { this.remove_content_timeout = GLib.Timeout.add (3 * 1000 * 60, () => { tweet_list.remove_all (); tweet_list.get_placeholder ().hide (); this.last_focus_widget = null; this.remove_content_timeout = 0; return GLib.Source.REMOVE; }); } public void search_for (string search_term, bool set_text = false) { if (search_term.length == 0) return; this.last_search_query = search_term; if (this.cancellable != null) { debug ("Cancelling earlier search..."); this.cancellable.cancel (); } this.cancellable = new GLib.Cancellable (); n_results = 0; string q = this.last_search_query;//search_term.copy (); // clear the list tweet_list.remove_all (); tweet_list.set_unempty (); tweet_list.get_placeholder ().show (); if (set_text) search_entry.set_text(q); q += " -rt"; this.search_query = GLib.Uri.escape_string (q); this.user_page = 1; this.lowest_tweet_id = int64.MAX-1; collect_obj = new Collect (2); collect_obj.finished.connect (show_entries); load_tweets (); load_users (); } private void row_activated_cb (Gtk.ListBoxRow row) { this.last_focus_widget = row; var bundle = new Cb.Bundle (); if (row is UserListEntry) { bundle.put_int64 (ProfilePage.KEY_USER_ID, ((UserListEntry)row).user_id); bundle.put_string (ProfilePage.KEY_SCREEN_NAME, ((UserListEntry)row).screen_name); main_window.main_widget.switch_page (Page.PROFILE, bundle); } else if (row is TweetListEntry) { bundle.put_int (TweetInfoPage.KEY_MODE, TweetInfoPage.BY_INSTANCE); bundle.put_object (TweetInfoPage.KEY_TWEET, ((TweetListEntry)row).tweet); main_window.main_widget.switch_page (Page.TWEET_INFO, bundle); } } private void header_func (Gtk.ListBoxRow row, Gtk.ListBoxRow? before) { //{{{ Gtk.Widget header = row.get_header (); if (header != null) return; if (before == null && row is UserListEntry) { row.set_header (users_header); } else if ((before is UserListEntry || before is LoadMoreEntry) && row is TweetListEntry) { row.set_header (tweets_header); } } //}}} private void load_users () { if (this.loading_users) return; this.loading_users = true; var user_call = account.proxy.new_call (); user_call.set_method ("GET"); user_call.set_function ("1.1/users/search.json"); user_call.add_param ("q", this.search_query); user_call.add_param ("count", (USER_COUNT + 1).to_string ()); user_call.add_param ("include_entities", "false"); user_call.add_param ("page", user_page.to_string ()); Cb.Utils.load_threaded_async.begin (user_call, cancellable, (_, res) => { Json.Node? root = null; try { root = Cb.Utils.load_threaded_async.end (res); } catch (GLib.Error e) { warning (e.message); tweet_list.set_error (e.message); if (!collect_obj.done) collect_obj.emit (); this.loading_users = false; return; } if (root == null) { this.loading_users = false; debug ("load_users: root is null"); if (!collect_obj.done) collect_obj.emit (); return; } var users = root.get_array (); if (users.get_length () == 0 && n_results <= 0) n_results = -1; else n_results += (int)users.get_length (); if (n_results <= 0) { tweet_list.set_empty (); } users.foreach_element ((array, index, node) => { if (index > USER_COUNT - 1) return; var user_obj = node.get_object (); var entry = new UserListEntry (); string avatar_url = user_obj.get_string_member ("profile_image_url"); if (this.get_scale_factor () == 2) avatar_url = avatar_url.replace ("_normal", "_bigger"); entry.user_id = user_obj.get_int_member ("id"); entry.set_screen_name ("@" + user_obj.get_string_member ("screen_name")); entry.name = user_obj.get_string_member ("name").strip (); entry.avatar_url = avatar_url; entry.verified = user_obj.get_boolean_member ("verified"); entry.show_settings = false; if (!collect_obj.done) entry.visible = false; tweet_list.add (entry); }); if (users.get_length () > USER_COUNT) { if (load_more_entry.parent == null) { load_more_entry.visible = false; tweet_list.add (load_more_entry); } } else { load_more_entry.hide (); } if (!collect_obj.done) collect_obj.emit (); this.loading_users = false; }); } private void load_tweets () { if (loading_tweets) return; this.loading_tweets = true; var call = account.proxy.new_call (); call.set_function ("1.1/search/tweets.json"); call.set_method ("GET"); call.add_param ("q", this.search_query); call.add_param ("tweet_mode", "extended"); call.add_param ("max_id", (lowest_tweet_id - 1).to_string ()); call.add_param ("count", "35"); Cb.Utils.load_threaded_async.begin (call, cancellable, (_, res) => { Json.Node? root = null; try { root = Cb.Utils.load_threaded_async.end (res); } catch (GLib.Error e) { warning (e.message); tweet_list.set_error (e.message); if (!collect_obj.done) collect_obj.emit (); this.loading_tweets = false; return; } if (root == null) { debug ("load tweets: root is null"); this.loading_tweets = false; if (!collect_obj.done) collect_obj.emit (); return; } var now = new GLib.DateTime.now_local (); var statuses = root.get_object().get_array_member("statuses"); if (statuses.get_length () == 0 && n_results <= 0) n_results = -1; else n_results += (int)statuses.get_length (); if (n_results <= 0) tweet_list.set_empty (); statuses.foreach_element ((array, index, node) => { var tweet = new Cb.Tweet (); tweet.load_from_json (node, account.id, now); if (tweet.id < lowest_tweet_id) lowest_tweet_id = tweet.id; var entry = new TweetListEntry (tweet, main_window, account); if (!collect_obj.done) entry.visible = false; else entry.show (); tweet_list.add (entry); }); if (!collect_obj.done) collect_obj.emit (); this.loading_tweets = false; }); } private void show_entries (GLib.Error? e) { if (e != null) { tweet_list.set_error (e.message); tweet_list.set_empty (); this.loading_tweets = false; this.loading_users = false; return; } tweet_list.@foreach ((w) => w.show()); this.loading_tweets = false; this.loading_users = false; /* Work around a problem with GtkListBox where the entries are not redrawn for some reason. This happened whenever we remove_all'd all the rows from the list while it was not mapped */ tweet_list.queue_draw (); } public void create_radio_button (Gtk.RadioButton? group){ radio_button = new BadgeRadioButton (group, "corebird-edit-find-symbolic", _("Search")); } public Gtk.RadioButton? get_radio_button() { return radio_button; } public string get_title () { return _("Search"); } public bool handles_double_open () { return true; } } class LoadMoreEntry : Gtk.ListBoxRow, Cb.TwitterItem { private GLib.TimeSpan last_timediff; public bool seen { get { return true; } set {} } private Gtk.Button load_more_button; public LoadMoreEntry () { this.activatable = false; var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12); box.show (); this.load_more_button = new Gtk.Button.with_label (_("Load More")); load_more_button.get_style_context ().add_class ("dim-label"); load_more_button.set_halign (Gtk.Align.CENTER); load_more_button.set_hexpand (true); load_more_button.set_relief (Gtk.ReliefStyle.NONE); load_more_button.show (); box.add (load_more_button); this.add (box); } public Gtk.Button get_button () { return load_more_button; } public int update_time_delta (GLib.DateTime? now = null) {return 0;} public int64 get_sort_factor () { return int64.MAX - 2; } public int64 get_timestamp () { return 0; } public GLib.TimeSpan get_last_set_timediff () { return this.last_timediff; } public void set_last_set_timediff (GLib.TimeSpan span) { this.last_timediff = span; } } corebird-1.7.4/src/Settings.vala000066400000000000000000000061771324604713000165630ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public enum MediaVisibility{ SHOW = 1, HIDE = 2, HIDE_IN_TIMELINES = 3 } public class Settings : GLib.Object { private static GLib.Settings settings; public static void init(){ settings = new GLib.Settings("org.baedert.corebird"); } public static new GLib.Settings get () { return settings; } /** * Returns how many tweets should be stacked before a * notification should be created. */ public static int get_tweet_stack_count() { int setting_val = settings.get_enum("new-tweets-notify"); return setting_val; } /** * Check whether the user wants Corebird to always use the dark gtk theme variant. */ public static bool use_dark_theme(){ return settings.get_boolean("use-dark-theme"); } public static bool notify_new_mentions(){ return settings.get_boolean("new-mentions-notify"); } public static bool notify_new_dms(){ return settings.get_boolean("new-dms-notify"); } public static bool auto_scroll_on_new_tweets () { return settings.get_boolean ("auto-scroll-on-new-tweets"); } public static string get_accel (string accel_name) { return settings.get_string ("accel-" + accel_name); } public static double max_media_size () { return settings.get_double ("max-media-size"); } public static void toggle_topbar_visible () { settings.set_boolean ("sidebar-visible", !settings.get_boolean ("sidebar-visible")); } public static string get_consumer_key () { return settings.get_string ("consumer-key"); } public static string get_consumer_secret () { return settings.get_string ("consumer-secret"); } public static void add_text_transform_flag (Cb.TransformFlags flag) { settings.set_uint ("text-transform-flags", settings.get_uint ("text-transform-flags") | flag); } public static void remove_text_transform_flag (Cb.TransformFlags flag) { settings.set_uint ("text-transform-flags", settings.get_uint ("text-transform-flags") & ~flag); } public static Cb.TransformFlags get_text_transform_flags () { return (Cb.TransformFlags) settings.get_uint ("text-transform-flags"); } public static bool hide_nsfw_content () { return settings.get_boolean ("hide-nsfw-content"); } public static MediaVisibility get_media_visiblity () { return (MediaVisibility)settings.get_enum ("media-visibility"); } } corebird-1.7.4/src/TweetInfoPage.vala000066400000000000000000000552561324604713000174660ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/tweet-info-page.ui")] class TweetInfoPage : IPage, ScrollWidget, Cb.MessageReceiver { public const int KEY_MODE = 0; public const int KEY_TWEET = 1; public const int KEY_EXISTING = 2; public const int KEY_TWEET_ID = 3; public const int KEY_SCREEN_NAME = 4; public const int BY_INSTANCE = 1; public const int BY_ID = 2; private const GLib.ActionEntry[] action_entries = { {"quote", quote_activated }, {"reply", reply_activated }, {"favorite", favorite_activated}, {"delete", delete_activated } }; public int unread_count { get {return 0;} } public int id { get; set; } public unowned MainWindow window { set { main_window = value; } } public unowned Account account; private int64 tweet_id; private string screen_name; private bool values_set = false; private Cb.Tweet tweet; private GLib.SimpleActionGroup actions; private unowned MainWindow main_window; private GLib.Cancellable? cancellable = null; [GtkChild] private Gtk.Grid grid; [GtkChild] private Gtk.Box main_box; [GtkChild] private MultiMediaWidget mm_widget; [GtkChild] private Gtk.Label text_label; [GtkChild] private TextButton name_button; [GtkChild] private Gtk.Label screen_name_label; [GtkChild] private AvatarWidget avatar_image; [GtkChild] private Gtk.Label rt_label; [GtkChild] private Gtk.Label fav_label; [GtkChild] private TweetListBox bottom_list_box; [GtkChild] private TweetListBox top_list_box; [GtkChild] private Gtk.ToggleButton favorite_button; [GtkChild] private Gtk.ToggleButton retweet_button; [GtkChild] private Gtk.Label time_label; [GtkChild] private Gtk.Label source_label; [GtkChild] private MaxSizeContainer max_size_container; [GtkChild] private ReplyIndicator reply_indicator; [GtkChild] private Gtk.Stack main_stack; [GtkChild] private Gtk.Label error_label; [GtkChild] private Gtk.Label reply_label; [GtkChild] private Gtk.Box reply_box; public TweetInfoPage (int id, Account account) { this.id = id; this.account = account; this.top_list_box.account = account; this.bottom_list_box.account = account; grid.set_redraw_on_allocate (true); mm_widget.media_clicked.connect ((m, i) => TweetUtils.handle_media_click (tweet, main_window, i)); this.scroll_event.connect ((evt) => { if (evt.delta_y < 0 && this.vadjustment.value == 0 && reply_indicator.replies_available) { int inc = (int)(vadjustment.step_increment * (-evt.delta_y)); max_size_container.max_size += inc; return true; } return false; }); bottom_list_box.row_activated.connect ((row) => { var bundle = new Cb.Bundle (); bundle.put_int (KEY_MODE, TweetInfoPage.BY_INSTANCE); bundle.put_object (KEY_TWEET, ((TweetListEntry)row).tweet); bundle.put_bool (KEY_EXISTING, true); main_window.main_widget.switch_page (Page.TWEET_INFO, bundle); }); top_list_box.row_activated.connect ((row) => { var bundle = new Cb.Bundle (); bundle.put_int (KEY_MODE, TweetInfoPage.BY_INSTANCE); bundle.put_object (KEY_TWEET, ((TweetListEntry)row).tweet); bundle.put_bool (KEY_EXISTING, true); main_window.main_widget.switch_page (Page.TWEET_INFO, bundle); }); this.actions = new GLib.SimpleActionGroup (); this.actions.add_action_entries (action_entries, this); this.insert_action_group ("tweet", this.actions); Settings.get ().changed["media-visibility"].connect (media_visiblity_changed_cb); this.mm_widget.visible = (Settings.get_media_visiblity () != MediaVisibility.HIDE); } private void media_visiblity_changed_cb () { if (Settings.get_media_visiblity () == MediaVisibility.HIDE) this.mm_widget.hide (); else this.mm_widget.show (); } public void on_join (int page_id, Cb.Bundle? args) { int mode = args.get_int (KEY_MODE); if (mode == 0) return; values_set = false; bool existing = args.get_bool (KEY_EXISTING); reply_indicator.replies_available = false; max_size_container.max_size = 0; main_stack.visible_child = main_box; /* If we have a tweet instance here already, we set the avatar now instead of in * set_tweet_data, since the rearrange_tweets() or list.model.clear() calls * might cause the avatar to get removed from the cache. */ if (existing) { // Only possible BY_INSTANCE var tweet = (Cb.Tweet) args.get_object (KEY_TWEET); if (Twitter.get ().has_avatar (tweet.get_user_id ())) avatar_image.surface = Twitter.get ().get_cached_avatar (tweet.get_user_id ()); rearrange_tweets (tweet.id); } else { bottom_list_box.model.clear (); bottom_list_box.hide (); top_list_box.model.clear (); top_list_box.hide (); } if (mode == BY_INSTANCE) { Cb.Tweet tweet = (Cb.Tweet)args.get_object (KEY_TWEET); if (Twitter.get ().has_avatar (tweet.get_user_id ())) avatar_image.surface = Twitter.get ().get_cached_avatar (tweet.get_user_id ()); if (tweet.retweeted_tweet != null) this.tweet_id = tweet.retweeted_tweet.id; else this.tweet_id = tweet.id; this.screen_name = tweet.get_screen_name (); this.tweet = tweet; set_tweet_data (tweet); } else if (mode == BY_ID) { this.tweet = null; this.tweet_id = args.get_int64 (KEY_TWEET_ID); this.screen_name = args.get_string (KEY_SCREEN_NAME); } query_tweet_info (existing); } private void load_user_avatar (string url) { string avatar_url; int scale = this.get_scale_factor (); if (scale == 1) avatar_url = url.replace ("_normal", "_bigger"); else avatar_url = url.replace ("_normal", "_200x200"); TweetUtils.download_avatar.begin (avatar_url, 73 * scale, cancellable, (obj, res) => { Cairo.Surface surface; try { var pixbuf = TweetUtils.download_avatar.end (res); if (pixbuf == null) { surface = scale_surface ((Cairo.ImageSurface)Twitter.no_avatar, 73, 73); } else { surface = Gdk.cairo_surface_create_from_pixbuf (pixbuf, scale, null); } } catch (GLib.Error e) { warning (e.message); surface = Twitter.no_avatar; } avatar_image.surface = surface; }); } private void rearrange_tweets (int64 new_id) { //assert (new_id != this.tweet_id); if (top_list_box.model.contains_id (new_id)) { // Move the current tweet down into bottom_list_box bottom_list_box.model.add (this.tweet); bottom_list_box.show (); top_list_box.model.clear (); top_list_box.hide (); } else if (bottom_list_box.model.contains_id (new_id)) { // Remove all tweets above the new one from the bottom list box, // add the direct successor to the top_list top_list_box.model.clear (); top_list_box.show (); var t = bottom_list_box.model.get_for_id (new_id, -1); if (t != null) { top_list_box.model.add (t); } else { top_list_box.model.add (this.tweet); } reply_indicator.replies_available = true; bottom_list_box.model.remove_tweets_above (new_id); if (bottom_list_box.model.get_n_items () == 0) bottom_list_box.hide (); } //else //error ("wtf"); } public void on_leave () { if (cancellable != null) { cancellable.cancel (); cancellable = null; } } [GtkCallback] private void favorite_button_toggled_cb () { if (!values_set) return; favorite_button.sensitive = false; this.update_rt_fav_labels (); TweetUtils.set_favorite_status.begin (account, tweet, favorite_button.active, () => { favorite_button.sensitive = true; }); } [GtkCallback] private void retweet_button_toggled_cb () { if (!values_set) return; retweet_button.sensitive = false; if (retweet_button.active) this.tweet.retweet_count ++; else this.tweet.retweet_count --; this.update_rt_fav_labels (); TweetUtils.set_retweet_status.begin (account, tweet, retweet_button.active, () => { retweet_button.sensitive = true; }); } [GtkCallback] private void reply_button_clicked_cb () { ComposeTweetWindow ctw = new ComposeTweetWindow(main_window, this.account, this.tweet, ComposeTweetWindow.Mode.REPLY); ctw.show (); } [GtkCallback] private bool link_activated_cb (string uri) { return TweetUtils.activate_link (uri, main_window); } [GtkCallback] private void name_button_clicked_cb () { int64 id; string screen_name; if (this.tweet.retweeted_tweet != null) { id = this.tweet.retweeted_tweet.author.id; screen_name = this.tweet.retweeted_tweet.author.screen_name; } else { id = this.tweet.source_tweet.author.id; screen_name = this.tweet.source_tweet.author.screen_name; } var bundle = new Cb.Bundle (); bundle.put_int64 (ProfilePage.KEY_USER_ID, id); bundle.put_string (ProfilePage.KEY_SCREEN_NAME, screen_name); main_window.main_widget.switch_page (Page.PROFILE, bundle); } private void query_tweet_info (bool existing) { if (this.cancellable != null) { this.cancellable.cancel (); } this.cancellable = new Cancellable (); var now = new GLib.DateTime.now_local (); var call = account.proxy.new_call (); call.set_method ("GET"); call.set_function ("1.1/statuses/show.json"); call.add_param ("id", tweet_id.to_string ()); call.add_param ("include_my_retweet", "true"); call.add_param ("tweet_mode", "extended"); Cb.Utils.load_threaded_async.begin (call, cancellable, (__, res) => { Json.Node? root = null; try { root = Cb.Utils.load_threaded_async.end (res); } catch (GLib.Error e) { error_label.label = "%s: %s".printf (_("Could not show tweet"), e.message); main_stack.visible_child = error_label; return; } if (root == null) return; Json.Object root_object = root.get_object (); if (this.tweet != null) { int n_retweets = (int)root_object.get_int_member ("retweet_count"); int n_favorites = (int)root_object.get_int_member ("favorite_count"); this.tweet.retweet_count = n_retweets; this.tweet.favorite_count = n_favorites; } else { this.tweet = new Cb.Tweet (); tweet.load_from_json (root, account.id, now); } string with = root_object.get_string_member ("source"); with = "" + extract_source (with) + ""; set_tweet_data (tweet, with); if (!existing) { if (tweet.retweeted_tweet == null) load_replied_to_tweet (tweet.source_tweet.reply_id); else load_replied_to_tweet (tweet.retweeted_tweet.reply_id); } values_set = true; }); var reply_call = account.proxy.new_call (); reply_call.set_method ("GET"); reply_call.set_function ("1.1/search/tweets.json"); reply_call.add_param ("q", "to:" + this.screen_name); reply_call.add_param ("since_id", tweet_id.to_string ()); reply_call.add_param ("count", "200"); reply_call.add_param ("tweet_mode", "extended"); Cb.Utils.load_threaded_async.begin (reply_call, cancellable, (_, res) => { Json.Node? root = null; try { root = Cb.Utils.load_threaded_async.end (res); } catch (GLib.Error e) { if (!(e is GLib.IOError.CANCELLED)) warning (e.message); return; } if (root == null) return; var statuses_node = root.get_object ().get_array_member ("statuses"); int64 previous_tweet_id = -1; if (top_list_box.model.get_n_items () > 0) { //assert (top_list_box.model.get_n_items () == 1); previous_tweet_id = ((Cb.Tweet)(top_list_box.model.get_item (0))).id; } int n_replies = 0; statuses_node.foreach_element ((arr, index, node) => { if (n_replies >= 5) return; var obj = node.get_object (); if (!obj.has_member ("in_reply_to_status_id") || obj.get_null_member ("in_reply_to_status_id")) return; int64 reply_id = obj.get_int_member ("in_reply_to_status_id"); if (reply_id != tweet_id) { return; } var t = new Cb.Tweet (); t.load_from_json (node, account.id, now); if (t.id != previous_tweet_id) { top_list_box.model.add (t); n_replies ++; } }); if (n_replies > 0) { top_list_box.show (); reply_indicator.replies_available = true; } else { //top_list_box.hide (); //reply_indicator.replies_available = false; } }); } /** * Loads the tweet this tweet is a reply to. * This will recursively call itself until the end of the chain is reached. * * @param reply_id The id of the tweet the previous tweet was a reply to. */ private void load_replied_to_tweet (int64 reply_id) { if (reply_id == 0) { return; } bottom_list_box.show (); var call = account.proxy.new_call (); call.set_function ("1.1/statuses/show.json"); call.set_method ("GET"); call.add_param ("id", reply_id.to_string ()); call.add_param ("tweet_mode", "extended"); call.invoke_async.begin (cancellable, (obj, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { if (e.message.strip () != "Forbidden" && e.message.strip ().down () != "not found" && !(e is GLib.IOError.CANCELLED)) { critical (e.message); Utils.show_error_object (call.get_payload (), e.message, GLib.Log.LINE, GLib.Log.FILE, this.main_window); } bottom_list_box.visible = (bottom_list_box.get_children ().length () > 0); return; } var parser = new Json.Parser (); try { parser.load_from_data (call.get_payload ()); } catch (GLib.Error e) { critical (e.message); return; } /* If we get here, the tweet is not protected so we can just use it */ var tweet = new Cb.Tweet (); tweet.load_from_json (parser.get_root (), account.id, new GLib.DateTime.now_local ()); bottom_list_box.model.add (tweet); if (tweet.retweeted_tweet == null) load_replied_to_tweet (tweet.source_tweet.reply_id); else load_replied_to_tweet (tweet.retweeted_tweet.reply_id); }); } /** * */ private void set_tweet_data (Cb.Tweet tweet, string? with = null) { account.user_counter.user_seen (tweet.get_user_id (), tweet.get_screen_name (), tweet.get_user_name ()); GLib.DateTime created_at = new GLib.DateTime.from_unix_local ( tweet.retweeted_tweet != null ? tweet.retweeted_tweet.created_at : tweet.source_tweet.created_at); string time_format = created_at.format ("%x, %X"); if (with != null) { time_format += " via " + with; } text_label.label = tweet.get_formatted_text (); name_button.set_markup (tweet.get_user_name ()); screen_name_label.label = "@" + tweet.get_screen_name (); load_user_avatar (tweet.avatar_url); update_rt_fav_labels (); time_label.label = time_format; retweet_button.active = tweet.is_flag_set (Cb.TweetState.RETWEETED); favorite_button.active = tweet.is_flag_set (Cb.TweetState.FAVORITED); avatar_image.verified = tweet.is_flag_set (Cb.TweetState.VERIFIED); set_source_link (tweet.id, tweet.get_screen_name ()); if ((tweet.retweeted_tweet != null && tweet.retweeted_tweet.reply_id != 0) || tweet.source_tweet.reply_id != 0) { var reply_users = tweet.get_reply_users (); reply_box.show (); var buff = new StringBuilder (); buff.append (_("Replying to")); buff.append_c (' '); Cb.Utils.linkify_user (ref reply_users[0], buff); for (int i = 1; i < reply_users.length - 1; i ++) { buff.append (", "); Cb.Utils.linkify_user (ref reply_users[i], buff); } if (reply_users.length > 1) { /* Last one */ buff.append_c (' ') .append (_("and")) .append_c (' '); Cb.Utils.linkify_user (ref reply_users[reply_users.length - 1], buff); } reply_label.label = buff.str; } else { reply_box.hide (); } if (tweet.has_inline_media ()) { this.mm_widget.visible = (Settings.get_media_visiblity () != MediaVisibility.HIDE); mm_widget.set_all_media (tweet.get_medias ()); } else { mm_widget.hide (); } ((GLib.SimpleAction)actions.lookup_action ("delete")).set_enabled (tweet.get_user_id () == account.id); if (tweet.is_flag_set (Cb.TweetState.PROTECTED)) { retweet_button.hide (); ((GLib.SimpleAction)actions.lookup_action ("quote")).set_enabled (false); } else { retweet_button.show (); ((GLib.SimpleAction)actions.lookup_action ("quote")).set_enabled (true); } } private void update_rt_fav_labels () { rt_label.label = "%'d %s".printf (tweet.retweet_count, _("Retweets")); fav_label.label = "%'d %s".printf (tweet.favorite_count, _("Favorites")); } private void set_source_link (int64 id, string screen_name) { var link = "https://twitter.com/%s/status/%s".printf (screen_name, id.to_string ()); source_label.label = "%s" .printf (link, _("Open in Browser"), _("Source")); } private void quote_activated () { ComposeTweetWindow ctw = new ComposeTweetWindow(main_window, this.account, this.tweet, ComposeTweetWindow.Mode.QUOTE); ctw.show (); } private void reply_activated () { ComposeTweetWindow ctw = new ComposeTweetWindow(main_window, this.account, this.tweet, ComposeTweetWindow.Mode.REPLY); ctw.show (); } private void favorite_activated () { if (!values_set || !favorite_button.sensitive) return; bool favoriting = !favorite_button.active; favorite_button.sensitive = false; this.update_rt_fav_labels (); TweetUtils.set_favorite_status.begin (account, tweet, favoriting, () => { favorite_button.sensitive = true; values_set = false; favorite_button.active = favoriting; values_set = true; }); } private void delete_activated () { if (this.tweet == null || this.tweet.get_user_id () != account.id) { return; } this.main_window.main_widget.remove_current_page (); TweetUtils.delete_tweet.begin (account, tweet, () => { }); } public string get_title () { return _("Tweet Details"); } /** * Twitter's source parameter of tweets includes a 'rel' parameter * that doesn't work as pango markup, so we just remove it here. * * Example string: * TweetDeck * * @param source_str The source string from twitter * * @return The #source_string without the rel parameter */ private string extract_source (string source_str) { int from, to; int tmp = 0; tmp = source_str.index_of_char ('"'); tmp = source_str.index_of_char ('"', tmp + 1); from = source_str.index_of_char ('"', tmp + 1); to = source_str.index_of_char ('"', from + 1); if (to == -1 || from == -1) return source_str; return source_str.substring (0, from-5) + source_str.substring(to + 1); } public void create_radio_button (Gtk.RadioButton? group) {} public Gtk.RadioButton? get_radio_button () { return null; } public void stream_message_received (Cb.StreamMessageType type, Json.Node root) { if (type == Cb.StreamMessageType.TWEET) { Json.Object root_obj = root.get_object (); if (Utils.usable_json_value (root_obj, "in_reply_to_status_id")) { int64 reply_id = root_obj.get_int_member ("in_reply_to_status_id"); if (reply_id == this.tweet_id) { var t = new Cb.Tweet (); t.load_from_json (root, account.id, new GLib.DateTime.now_local ()); top_list_box.model.add (t); top_list_box.show (); this.reply_indicator.replies_available = true; } } } else if (type == Cb.StreamMessageType.DELETE) { int64 tweet_id = root.get_object ().get_object_member ("delete") .get_object_member ("status") .get_int_member ("id"); if (tweet_id == this.tweet_id && main_window.cur_page_id == this.id) { /* TODO: We should probably remove this page with this bundle form the history, even if it's not the currently visible page */ debug ("Current tweet with id %s deleted!", tweet_id.to_string ()); this.main_window.main_widget.remove_current_page (); } } else if (type == Cb.StreamMessageType.EVENT_FAVORITE) { int64 id = root.get_object ().get_object_member ("target_object").get_int_member ("id"); int64 source_id = root.get_object ().get_object_member ("source").get_int_member ("id"); if (source_id == account.id && id == this.tweet_id) { this.values_set = false; this.favorite_button.active = true; this.tweet.favorite_count ++; this.update_rt_fav_labels (); this.values_set = true; } } else if (type == Cb.StreamMessageType.EVENT_UNFAVORITE) { int64 id = root.get_object ().get_object_member ("target_object").get_int_member ("id"); int64 source_id = root.get_object ().get_object_member ("source").get_int_member ("id"); if (source_id == account.id && id == this.tweet_id) { this.values_set = false; this.favorite_button.active = false; this.tweet.favorite_count --; this.update_rt_fav_labels (); this.values_set = true; } } } } corebird-1.7.4/src/Twitter.vala000066400000000000000000000150441324604713000164160ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public class Twitter : GLib.Object { private static Twitter twitter; private Twitter () {} public static new Twitter get () { if (twitter == null) twitter = new Twitter (); return twitter; } [Signal (detailed = true)] private signal void avatar_downloaded (Cairo.Surface avatar); public const int MAX_BYTES_PER_IMAGE = 1024 * 1024 * 3; public const int short_url_length = 23; public const int max_media_per_upload = 4; public static Cairo.Surface no_avatar; public static Gdk.Pixbuf no_banner; private Cb.AvatarCache avatar_cache; public void init () { try { Twitter.no_avatar = Gdk.cairo_surface_create_from_pixbuf ( new Gdk.Pixbuf.from_resource ("/org/baedert/corebird/data/no_avatar.png"), 1, null); Twitter.no_banner = new Gdk.Pixbuf.from_resource ("/org/baedert/corebird/data/no_banner.png"); } catch (GLib.Error e) { error ("Error while loading assets: %s", e.message); } this.avatar_cache = new Cb.AvatarCache (); } public void ref_avatar (Cairo.Surface surface) { this.avatar_cache.increase_refcount_for_surface (surface); } public void unref_avatar (Cairo.Surface surface) { this.avatar_cache.decrease_refcount_for_surface (surface); } public bool has_avatar (int64 user_id) { return (get_cached_avatar (user_id) != Twitter.no_avatar); } public Cairo.Surface get_cached_avatar (int64 user_id) { bool found; Cairo.Surface? surface = this.avatar_cache.get_surface_for_id (user_id, out found); if (surface == null) return Twitter.no_avatar; else return surface; } /* This is a get_avatar version for times where we don't have an at least relatively recent avatar_url for the given account. This will first query the account details of the given account, then use the avatar_url to download the avatar and insert it into the avatar cache */ public async Cairo.Surface? load_avatar_for_user_id (Account account, int64 user_id, int size) { Cairo.Surface? s; bool found = false; s = avatar_cache.get_surface_for_id (user_id, out found); if (s != null) { assert (found); return s; } if (s == null && found) { ulong handler_id = 0; handler_id = this.avatar_downloaded[user_id.to_string ()].connect ((ava) => { s = ava; this.disconnect (handler_id); this.load_avatar_for_user_id.callback (); }); yield; assert (s != null); return s; } this.avatar_cache.add (user_id, null, null); // We first need to get the avatar url for the given user id... var call = account.proxy.new_call (); call.set_function ("1.1/users/show.json"); call.set_method ("GET"); call.add_param ("user_id", user_id.to_string ()); call.add_param ("include_entities", "false"); Json.Node? root = null; try { root = yield Cb.Utils.load_threaded_async (call, null); } catch (GLib.Error e) { warning (e.message); return null; } if (root == null) return null; var root_obj = root.get_object (); string avatar_url = root_obj.get_string_member ("profile_image_url"); this.avatar_cache.set_url (user_id, avatar_url); s = yield this.get_surface(user_id, avatar_url, size, true); if (s != null) return s; else yield; return s; } /** * Get the avatar with the given url. If the avatar exists on the * hard drive already, it is loaded and returned immediately. If * the avatar is in memory already, that version is returned. * If the avatar is neither on disk nor in memory, it will be downladed * first and set via the supplied `func`. */ public async void get_avatar (int64 user_id, string url, AvatarWidget dest_widget, int size = 48, bool force_download = false) { dest_widget.surface = yield this.get_surface (user_id, url, size, force_download); } private async Cairo.Surface? get_surface (int64 user_id, string url, int size = 48, bool force_download = false) { assert (user_id > 0); bool has_key = false; Cairo.Surface? a = this.avatar_cache.get_surface_for_id (user_id, out has_key); bool new_url = a == Twitter.no_avatar && url != this.avatar_cache.get_url_for_id (user_id); if (a != null && !new_url) { return a; } if (has_key && !new_url && !force_download) { // wait until the avatar has finished downloading ulong handler_id = 0; handler_id = this.avatar_downloaded[user_id.to_string ()].connect ((ava) => { this.disconnect (handler_id); a = ava; get_surface.callback (); }); yield; return a; } else { // download the avatar this.avatar_cache.add (user_id, null, url); Gdk.Pixbuf? avatar = null; try { avatar = yield TweetUtils.download_avatar (url, size); } catch (GLib.Error e) { warning ("%s for %s", e.message, url); } Cairo.Surface s; // E.g. in the 404 case... if (avatar == null) s = Twitter.no_avatar; else s = Gdk.cairo_surface_create_from_pixbuf (avatar, 1, null); // a NULL surface is already in the cache this.avatar_cache.set_avatar (user_id, s, url); // signal all the other waiters in the queue avatar_downloaded[user_id.to_string ()](s); return s; } } } corebird-1.7.4/src/UserEventReceiver.vala000066400000000000000000000121041324604713000203530ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class UserEventReceiver : GLib.Object, Cb.MessageReceiver { private unowned Account account; public UserEventReceiver (Account account) { this.account = account; } public void stream_message_received (Cb.StreamMessageType type, Json.Node root_node) { switch (type) { case Cb.StreamMessageType.FRIENDS: account.set_friends (root_node.get_object ().get_array_member ("friends")); break; case Cb.StreamMessageType.EVENT_FOLLOW: int64 user_id = root_node.get_object ().get_object_member ("target") .get_int_member ("id"); account.follow_id (user_id); break; case Cb.StreamMessageType.EVENT_UNFOLLOW: int64 user_id = root_node.get_object ().get_object_member ("target") .get_int_member ("id"); account.unfollow_id (user_id); break; case Cb.StreamMessageType.EVENT_MUTE: int64 user_id = root_node.get_object ().get_object_member ("target") .get_int_member ("id"); account.mute_id (user_id); break; case Cb.StreamMessageType.EVENT_UNMUTE: int64 user_id = root_node.get_object ().get_object_member ("target") .get_int_member ("id"); account.unmute_id (user_id); break; case Cb.StreamMessageType.EVENT_BLOCK: int64 user_id = root_node.get_object ().get_object_member ("target") .get_int_member ("id"); account.block_id (user_id); break; case Cb.StreamMessageType.EVENT_UNBLOCK: int64 user_id = root_node.get_object ().get_object_member ("target") .get_int_member ("id"); account.unblock_id (user_id); break; case Cb.StreamMessageType.EVENT_USER_UPDATE: var user_obj = root_node.get_object ().get_object_member ("target"); if (user_obj.get_int_member ("id") == account.id) { string old_screen_name = account.screen_name; account.name = user_obj.get_string_member ("name"); account.description = user_obj.get_string_member ("description"); account.screen_name = user_obj.get_string_member ("screen_name"); account.info_changed (account.screen_name, account.name, account.avatar_small, account.avatar); account.save_info (); Utils.update_startup_account (old_screen_name, account.screen_name); } else { warning ("USER_UPDATE: ids don't match"); } break; case Cb.StreamMessageType.DIRECT_MESSAGE: var cb = (Corebird) GLib.Application.get_default (); if (!cb.is_window_open_for_user_id (account.id) && Settings.notify_new_dms ()) { var dm_obj = root_node.get_object ().get_object_member ("direct_message"); var sender_obj = dm_obj.get_object_member ("sender"); int64 sender_id = sender_obj.get_int_member ("id"); string sender_name = sender_obj.get_string_member ("name"); string dm_text = dm_obj.get_string_member ("text"); account.notifications.send_dm (sender_id, null, _("New direct message from %s").printf (sender_name), Utils.unescape_html (dm_text)); } break; case Cb.StreamMessageType.TWEET: var cb = (Corebird) GLib.Application.get_default (); if (!cb.is_window_open_for_user_id (account.id) && Settings.notify_new_mentions ()) { var tweet_obj = root_node.get_object (); string text = tweet_obj.get_string_member ("text"); if (text.contains ("@" + account.screen_name)) { var author_obj = tweet_obj.get_object_member ("user"); // TODO: Care about retweets/quotes! // XXX : And media? string author_name = author_obj.get_string_member ("name"); string summary = _("%s mentioned %s").printf (author_name, account.name); account.notifications.send (summary, text); } } break; } } } corebird-1.7.4/src/async/000077500000000000000000000000001324604713000152205ustar00rootroot00000000000000corebird-1.7.4/src/async/Collect.vala000066400000000000000000000030501324604713000174500ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public class Collect : GLib.Object { private int cur = 0; private int max; private GLib.Error? error = null; public bool done { get { return this.cur == this.max; } } public signal void finished (GLib.Error? error); public Collect (int max) requires (max >= 0) { this.max = max; } public void emit (GLib.Error? error = null) requires (cur < max) { /* If our global error is set, something previously went wrong and we ignore this call to emit(); */ if (this.error != null) return; /* If error is set, we call finished() with that error and ignore all following calls to emit() */ if (error != null) { finished (error); this.error = error; return; } cur++; if (cur == max) { finished (null); } } } corebird-1.7.4/src/libtl/000077500000000000000000000000001324604713000152115ustar00rootroot00000000000000corebird-1.7.4/src/libtl/data.h000066400000000000000000000273421324604713000163030ustar00rootroot00000000000000/* This file is part of libtweetlength * Copyright (C) 2017 Timm Bäder * * libtweetlength is free software: you can 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. * * libtweetlength is distributed in the hope that it will be useful, * but WITHOUT 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 libtweetlength. If not, see . */ #ifndef __TL_DATA_H__ #define __TL_DATA_H__ #define PUNCTUATION "!'#%&\"()*+,\\-./:;<=>?@[]^_{|}~$`" #define SPACES " \0x0020\0085\00A0" #define INVALID_CHARS "\0xFFFE\0xFEFF\0xFFFF" #define INVALID_URL_CHARS (PUNCTUATION SPACES INVALID_CHARS) #define INVALID_AFTER_URL_CHARS "?,!`~&*^%\\|" #define INVALID_BEFORE_NON_PROTOCOL_URL_CHARS (".@_-)/") #define INVALID_BEFORE_URL_CHARS ("$") #define VALID_BEFORE_HASHTAG_CHARS "{}!<>()[]\\/?`~:;.,%*" #define INVALID_BEFORE_HASHTAG_CHARS "_&" #define INVALID_HASHTAG_CHARS "!'#%&\"()*+,\\-./:;<=>?@[]^{|}~$`" #define INVALID_BEFORE_MENTION_CHARS "!_$&#*" #define VALID_BEFORE_MENTION_CHARS ";,`=+" #define INVALID_MENTION_CHARS "!'#%&\"()*+,\\-./:;<=>?@[]^{|}~$`" // List from twitter-text static const struct { size_t length; const char *str; } SPECIAL_CCTLDS[] = { {2, "co"}, {2, "tv"} }; static const struct { size_t length; const char *str; } GTLDS[] = { {2, "삼성"}, {2, "集团"}, {2, "网络"}, {2, "网址"}, {2, "移动"}, {2, "游戏"}, {2, "机构"}, {2, "政务"}, {2, "世界"}, {2, "中信"}, {2, "公司"}, {2, "公益"}, {2, "在线"}, {2, "商标"}, {2, "商城"}, {3, "eus"}, {3, "vet"}, {3, "foo"}, {3, "uno"}, {3, "edu"}, {3, "wed"}, {3, "dnp"}, {3, "gal"}, {3, "gmo"}, {3, "wtc"}, {3, "gop"}, {3, "wtf"}, {3, "gov"}, {3, "xxx"}, {3, "xyz"}, {3, "hiv"}, {3, "ink"}, {3, "tel"}, {3, "int"}, {3, "tax"}, {3, "орг"}, {3, "kim"}, {3, "com"}, {3, "みんな"}, {3, "mil"}, {3, "ceo"}, {3, "cat"}, {3, "moe"}, {3, "中文网"}, {3, "net"}, {3, "cab"}, {3, "bzh"}, {3, "nhk"}, {3, "我爱你"}, {3, "nyc"}, {3, "bmw"}, {3, "soy"}, {3, "onl"}, {3, "biz"}, {3, "bio"}, {3, "org"}, {3, "bid"}, {3, "ovh"}, {3, "bar"}, {3, "axa"}, {3, "pro"}, {3, "pub"}, {3, "red"}, {3, "ren"}, {3, "rio"}, {4, "moda"}, {4, "navy"}, {4, "mobi"}, {4, "mini"}, {4, "voto"}, {4, "menu"}, {4, "meet"}, {4, "surf"}, {4, "luxe"}, {4, "link"}, {4, "limo"}, {4, "life"}, {4, "lgbt"}, {4, "land"}, {4, "kred"}, {4, "kiwi"}, {4, "jobs"}, {4, "info"}, {4, "tips"}, {4, "host"}, {4, "sohu"}, {4, "pics"}, {4, "haus"}, {4, "guru"}, {4, "town"}, {4, "pink"}, {4, "post"}, {4, "sexy"}, {4, "ruhr"}, {4, "toys"}, {4, "qpon"}, {4, "scot"}, {4, "fund"}, {4, "rest"}, {4, "rich"}, {4, "fish"}, {4, "vote"}, {4, "farm"}, {4, "fail"}, {4, "name"}, {4, "gift"}, {4, "дети"}, {4, "care"}, {4, "cash"}, {4, "wang"}, {4, "camp"}, {4, "aero"}, {4, "buzz"}, {4, "blue"}, {4, "wien"}, {4, "wiki"}, {4, "bike"}, {4, "club"}, {4, "best"}, {4, "desi"}, {4, "army"}, {4, "موقع"}, {4, "شبكة"}, {4, "arpa"}, {4, "asia"}, {4, "组织机构"}, {4, "beer"}, {4, "сайт"}, {4, "zone"}, {4, "coop"}, {4, "cool"}, {5, "cards"}, {5, "cheap"}, {5, "miami"}, {5, "citic"}, {5, "media"}, {5, "space"}, {5, "mango"}, {5, "ninja"}, {5, "build"}, {5, "black"}, {5, "lotto"}, {5, "solar"}, {5, "codes"}, {5, "paris"}, {5, "संगठन"}, {5, "lease"}, {5, "parts"}, {5, "koeln"}, {5, "بازار"}, {5, "photo"}, {5, "jetzt"}, {5, "place"}, {5, "shoes"}, {5, "autos"}, {5, "loans"}, {5, "press"}, {5, "audio"}, {5, "house"}, {5, "horse"}, {5, "homes"}, {5, "tirol"}, {5, "today"}, {5, "dance"}, {5, "tokyo"}, {5, "tools"}, {5, "guide"}, {5, "gripe"}, {5, "green"}, {5, "archi"}, {5, "works"}, {5, "globo"}, {5, "rehab"}, {5, "glass"}, {5, "gives"}, {5, "reise"}, {5, "trade"}, {5, "email"}, {5, "vegas"}, {5, "watch"}, {5, "actor"}, {5, "vodka"}, {5, "rocks"}, {5, "rodeo"}, {6, "bayern"}, {6, "physio"}, {6, "photos"}, {6, "social"}, {6, "quebec"}, {6, "berlin"}, {6, "reisen"}, {6, "agency"}, {6, "schule"}, {6, "repair"}, {6, "report"}, {6, "ryukyu"}, {6, "camera"}, {6, "active"}, {6, "nagoya"}, {6, "museum"}, {6, "expert"}, {6, "supply"}, {6, "monash"}, {6, "career"}, {6, "center"}, {6, "church"}, {6, "market"}, {6, "claims"}, {6, "maison"}, {6, "luxury"}, {6, "clinic"}, {6, "london"}, {6, "events"}, {6, "suzuki"}, {6, "coffee"}, {6, "lawyer"}, {6, "condos"}, {6, "kaufen"}, {6, "juegos"}, {6, "joburg"}, {6, "tattoo"}, {6, "онлайн"}, {6, "москва"}, {6, "insure"}, {6, "tienda"}, {6, "credit"}, {6, "yachts"}, {6, "hiphop"}, {6, "dating"}, {6, "gratis"}, {6, "degree"}, {6, "dental"}, {6, "global"}, {6, "direct"}, {6, "futbol"}, {6, "durban"}, {6, "travel"}, {6, "webcam"}, {6, "viajes"}, {6, "villas"}, {6, "vision"}, {6, "voyage"}, {6, "estate"}, {6, "voting"}, {6, "moscow"}, {7, "spiegel"}, {7, "support"}, {7, "surgery"}, {7, "systems"}, {7, "singles"}, {7, "shiksha"}, {7, "website"}, {7, "schmidt"}, {7, "limited"}, {7, "reviews"}, {7, "rentals"}, {7, "recipes"}, {7, "capital"}, {7, "careers"}, {7, "organic"}, {7, "okinawa"}, {7, "neustar"}, {7, "college"}, {7, "cologne"}, {7, "company"}, {7, "academy"}, {7, "kitchen"}, {7, "cooking"}, {7, "country"}, {7, "cruises"}, {7, "dentist"}, {7, "holiday"}, {7, "hamburg"}, {7, "guitars"}, {7, "digital"}, {7, "gallery"}, {7, "domains"}, {7, "frogans"}, {7, "exposed"}, {7, "florist"}, {7, "flights"}, {7, "fitness"}, {7, "finance"}, {7, "fishing"}, {8, "airforce"}, {8, "attorney"}, {8, "bargains"}, {8, "boutique"}, {8, "brussels"}, {8, "builders"}, {8, "capetown"}, {8, "catering"}, {8, "cleaning"}, {8, "clothing"}, {8, "computer"}, {8, "yokohama"}, {8, "democrat"}, {8, "diamonds"}, {8, "discount"}, {8, "engineer"}, {8, "exchange"}, {8, "feedback"}, {8, "saarland"}, {8, "ventures"}, {8, "training"}, {8, "graphics"}, {8, "holdings"}, {8, "lighting"}, {8, "mortgage"}, {8, "supplies"}, {8, "partners"}, {8, "software"}, {8, "pictures"}, {8, "plumbing"}, {8, "services"}, {9, "vacations"}, {9, "equipment"}, {9, "education"}, {9, "furniture"}, {9, "directory"}, {9, "institute"}, {9, "community"}, {9, "marketing"}, {9, "christmas"}, {9, "solutions"}, {9, "financial"}, {10, "vlaanderen"}, {10, "foundation"}, {10, "university"}, {10, "immobilien"}, {10, "industries"}, {10, "cuisinella"}, {10, "technology"}, {10, "creditcard"}, {10, "consulting"}, {10, "management"}, {10, "properties"}, {10, "republican"}, {10, "associates"}, {11, "enterprises"}, {11, "engineering"}, {11, "investments"}, {11, "contractors"}, {11, "motorcycles"}, {11, "photography"}, {11, "productions"}, {11, "blackfriday"}, {11, "accountants"}, {12, "versicherung"}, {12, "construction"}, {13, "international"}, {14, "cancerresearch"} }; static const struct { size_t length; const char *str; } CCTLDS[] = { {2, "sa"}, {2, "rw"}, {2, "ru"}, {2, "rs"}, {2, "ro"}, {2, "re"}, {2, "qa"}, {2, "py"}, {2, "pw"}, {2, "pt"}, {2, "ps"}, {2, "pr"}, {2, "pn"}, {2, "pm"}, {2, "pl"}, {2, "pk"}, {2, "ph"}, {2, "pg"}, {2, "pf"}, {2, "pe"}, {2, "pa"}, {2, "om"}, {2, "nz"}, {2, "nu"}, {2, "nr"}, {2, "np"}, {2, "no"}, {2, "nl"}, {2, "ni"}, {2, "ng"}, {2, "nf"}, {2, "li"}, {2, "ne"}, {2, "na"}, {2, "mz"}, {2, "my"}, {2, "mx"}, {2, "mw"}, {2, "mv"}, {2, "mu"}, {2, "mt"}, {2, "ms"}, {2, "mr"}, {2, "mq"}, {2, "mp"}, {2, "mo"}, {2, "mn"}, {2, "한국"}, {2, "ml"}, {2, "mk"}, {2, "mh"}, {2, "mg"}, {2, "mf"}, {2, "me"}, {2, "md"}, {2, "mc"}, {2, "ma"}, {2, "ly"}, {2, "lv"}, {2, "lu"}, {2, "lt"}, {2, "ls"}, {2, "lr"}, {2, "lk"}, {2, "nc"}, {2, "sb"}, {2, "香港"}, {2, "台灣"}, {2, "台湾"}, {2, "中國"}, {2, "中国"}, {2, "გე"}, {2, "рф"}, {2, "zw"}, {2, "zm"}, {2, "za"}, {2, "yt"}, {2, "ye"}, {2, "ws"}, {2, "wf"}, {2, "vu"}, {2, "vn"}, {2, "vi"}, {2, "vg"}, {2, "ve"}, {2, "vc"}, {2, "va"}, {2, "uz"}, {2, "uy"}, {2, "us"}, {2, "um"}, {2, "uk"}, {2, "ug"}, {2, "ua"}, {2, "tz"}, {2, "tw"}, {2, "tv"}, {2, "sc"}, {2, "tt"}, {2, "tp"}, {2, "to"}, {2, "tn"}, {2, "tm"}, {2, "tl"}, {2, "tk"}, {2, "tj"}, {2, "th"}, {2, "tg"}, {2, "tf"}, {2, "td"}, {2, "tc"}, {2, "sz"}, {2, "sy"}, {2, "sx"}, {2, "sv"}, {2, "su"}, {2, "st"}, {2, "ss"}, {2, "sr"}, {2, "so"}, {2, "sn"}, {2, "sm"}, {2, "sl"}, {2, "sk"}, {2, "sj"}, {2, "si"}, {2, "sh"}, {2, "sg"}, {2, "se"}, {2, "sd"}, {2, "tr"}, {2, "mm"}, {2, "dz"}, {2, "do"}, {2, "dm"}, {2, "dk"}, {2, "dj"}, {2, "de"}, {2, "cz"}, {2, "cy"}, {2, "cx"}, {2, "cw"}, {2, "cv"}, {2, "cu"}, {2, "cr"}, {2, "co"}, {2, "cn"}, {2, "cm"}, {2, "lc"}, {2, "ck"}, {2, "ci"}, {2, "ch"}, {2, "cg"}, {2, "cf"}, {2, "cd"}, {2, "cc"}, {2, "ca"}, {2, "bz"}, {2, "by"}, {2, "bw"}, {2, "bv"}, {2, "bt"}, {2, "bs"}, {2, "ec"}, {2, "br"}, {2, "bo"}, {2, "bn"}, {2, "bm"}, {2, "bl"}, {2, "bj"}, {2, "bi"}, {2, "bh"}, {2, "bg"}, {2, "bf"}, {2, "be"}, {2, "bd"}, {2, "bb"}, {2, "ba"}, {2, "az"}, {2, "ax"}, {2, "aw"}, {2, "au"}, {2, "at"}, {2, "as"}, {2, "ar"}, {2, "aq"}, {2, "ao"}, {2, "an"}, {2, "am"}, {2, "al"}, {2, "ai"}, {2, "ag"}, {2, "af"}, {2, "ae"}, {2, "ad"}, {2, "ac"}, {2, "bq"}, {2, "cl"}, {2, "lb"}, {2, "la"}, {2, "kz"}, {2, "ky"}, {2, "kw"}, {2, "kr"}, {2, "kp"}, {2, "kn"}, {2, "km"}, {2, "ki"}, {2, "kh"}, {2, "kg"}, {2, "ke"}, {2, "jp"}, {2, "ee"}, {2, "jm"}, {2, "je"}, {2, "it"}, {2, "is"}, {2, "ir"}, {2, "iq"}, {2, "io"}, {2, "in"}, {2, "im"}, {2, "il"}, {2, "ie"}, {2, "id"}, {2, "hu"}, {2, "ht"}, {2, "hr"}, {2, "hn"}, {2, "hm"}, {2, "jo"}, {2, "eg"}, {2, "eh"}, {2, "er"}, {2, "es"}, {2, "et"}, {2, "eu"}, {2, "fi"}, {2, "fj"}, {2, "fk"}, {2, "fm"}, {2, "fo"}, {2, "fr"}, {2, "ga"}, {2, "gb"}, {2, "gd"}, {2, "hk"}, {2, "gf"}, {2, "gg"}, {2, "gh"}, {2, "gi"}, {2, "gl"}, {2, "gm"}, {2, "gn"}, {2, "gp"}, {2, "gq"}, {2, "gr"}, {2, "gs"}, {2, "gt"}, {2, "gu"}, {2, "gw"}, {2, "gy"}, {2, "ge"}, {3, "мкд"}, {3, "мон"}, {3, "срб"}, {3, "укр"}, {3, "қаз"}, {3, "قطر"}, {3, "مصر"}, {3, "ไทย"}, {3, "新加坡"}, {4, "تونس"}, {4, "عمان"}, {4, "भारत"}, {4, "ভারত"}, {4, "ਭਾਰਤ"}, {4, "ભારત"}, {4, "ලංකා"}, {5, "ایران"}, {5, "بھارت"}, {5, "سودان"}, {5, "سورية"}, {5, "বাংলা"}, {5, "భారత్"}, {6, "الاردن"}, {6, "المغرب"}, {6, "امارات"}, {6, "فلسطين"}, {6, "مليسيا"}, {6, "இலங்கை"}, {7, "الجزائر"}, {7, "پاکستان"}, {7, "இந்தியா"}, {8, "السعودية"}, {11, "சிங்கப்பூர்"}, }; #endif corebird-1.7.4/src/libtl/libtweetlength.c000066400000000000000000000643271324604713000204120ustar00rootroot00000000000000/* This file is part of libtweetlength * Copyright (C) 2017 Timm Bäder * * libtweetlength is free software: you can 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. * * libtweetlength is distributed in the hope that it will be useful, * but WITHOUT 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 libtweetlength. If not, see . */ #include "libtweetlength.h" #include "data.h" #include #define LINK_LENGTH 23 typedef struct { guint type; const char *start; gsize start_character_index; gsize length_in_bytes; gsize length_in_characters; } Token; #ifdef LIBTL_DEBUG static char * G_GNUC_UNUSED token_str (const Token *t) { return g_strdup_printf ("Type: %u, Text: '%.*s'", t->type, (int)t->length_in_bytes, t->start); } static char * G_GNUC_UNUSED entity_str (const TlEntity *e) { return g_strdup_printf ("Type: %u, Text: '%.*s'", e->type, (int)e->length_in_bytes, e->start); } #endif enum { TOK_TEXT = 1, TOK_NUMBER, TOK_WHITESPACE, TOK_COLON, TOK_SLASH, TOK_OPEN_PAREN, TOK_CLOSE_PAREN, TOK_QUESTIONMARK, TOK_DOT, TOK_HASH, TOK_AT, TOK_EQUALS, TOK_DASH, TOK_UNDERSCORE, TOK_APOSTROPHE, TOK_QUOTE, TOK_DOLLAR, TOK_AMPERSAND, TOK_EXCLAMATION, TOK_TILDE }; static inline guint token_type_from_char (gunichar c) { switch (c) { case '@': return TOK_AT; case '#': return TOK_HASH; case ':': return TOK_COLON; case '/': return TOK_SLASH; case '(': return TOK_OPEN_PAREN; case ')': return TOK_CLOSE_PAREN; case '.': return TOK_DOT; case '?': return TOK_QUESTIONMARK; case '=': return TOK_EQUALS; case '-': return TOK_DASH; case '_': return TOK_UNDERSCORE; case '\'': return TOK_APOSTROPHE; case '"': return TOK_QUOTE; case '$': return TOK_DOLLAR; case '&': return TOK_AMPERSAND; case '!': return TOK_EXCLAMATION; case '~': return TOK_TILDE; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': return TOK_NUMBER; case ' ': case '\n': case '\t': return TOK_WHITESPACE; default: return TOK_TEXT; } } static inline gboolean token_ends_in_accented (const Token *t) { const char *p = t->start; gunichar c; gsize i; if (t->length_in_bytes == 1 || t->type != TOK_TEXT) { return FALSE; } // The rules here aren't exactly clear... // We read the last character of the text pointed to by the given token. // If that's not an ascii character, we return TRUE. for (i = 0; i < t->length_in_characters - 1; i ++) { p = g_utf8_next_char (p); } c = g_utf8_get_char (p); if (c > 127) return TRUE; return FALSE; } static inline gboolean token_in (const Token *t, const char *haystack) { const int haystack_len = strlen (haystack); int i; if (t->length_in_bytes > 1) { return FALSE; } for (i = 0; i < haystack_len; i ++) { if (haystack[i] == t->start[0]) { return TRUE; } } return FALSE; } static inline void emplace_token (GArray *array, const char *token_start, gsize token_length, gsize start_character_index, gsize length_in_characters) { Token *t; g_array_set_size (array, array->len + 1); t = &g_array_index (array, Token, array->len - 1); t->type = token_type_from_char (token_start[0]); t->start = token_start; t->length_in_bytes = token_length; t->start_character_index = start_character_index; t->length_in_characters = length_in_characters; } static inline void emplace_entity_for_tokens (GArray *array, const Token *tokens, guint entity_type, guint start_token_index, guint end_token_index) { TlEntity *e; guint i; g_array_set_size (array, array->len + 1); e = &g_array_index (array, TlEntity, array->len - 1); e->type = entity_type; e->start = tokens[start_token_index].start; e->length_in_bytes = 0; e->length_in_characters = 0; e->start_character_index = tokens[start_token_index].start_character_index; for (i = start_token_index; i <= end_token_index; i ++) { e->length_in_bytes += tokens[i].length_in_bytes; e->length_in_characters += tokens[i].length_in_characters; } } static inline gboolean is_valid_mention_char (gunichar c) { // Just ASCII if (c > 127) return FALSE; return TRUE; } static inline gboolean token_is_tld (const Token *t, gboolean has_protocol) { guint i; if (t->length_in_characters > GTLDS[G_N_ELEMENTS (GTLDS) - 1].length) { return FALSE; } for (i = 0; i < G_N_ELEMENTS (GTLDS); i ++) { if (t->length_in_characters == GTLDS[i].length && strncasecmp (t->start, GTLDS[i].str, t->length_in_bytes) == 0) { return TRUE; } } for (i = 0; i < G_N_ELEMENTS (SPECIAL_CCTLDS); i ++) { if (t->length_in_characters == SPECIAL_CCTLDS[i].length && strncasecmp (t->start, SPECIAL_CCTLDS[i].str, t->length_in_bytes) == 0) { return TRUE; } } if (has_protocol) { for (i = 0; i < G_N_ELEMENTS (CCTLDS); i ++) { if (t->length_in_characters == CCTLDS[i].length && strncasecmp (t->start, CCTLDS[i].str, t->length_in_bytes) == 0) { return TRUE; } } } return FALSE; } static inline gboolean token_is_protocol (const Token *t) { if (t->type != TOK_TEXT) { return FALSE; } if (t->length_in_bytes != 4 && t->length_in_bytes != 5) { return FALSE; } return strncasecmp (t->start, "http", t->length_in_bytes) == 0 || strncasecmp (t->start, "https", t->length_in_bytes) == 0; } static inline gboolean char_splits (gunichar c) { switch (c) { case ',': case '.': case '/': case '?': case '(': case ')': case ':': case ';': case '=': case '@': case '#': case '-': case '_': case '\n': case '\t': case '\0': case ' ': case '\'': case '"': case '$': case '|': case '&': case '^': case '%': case '+': case '*': case '\\': case '{': case '}': case '[': case ']': case '`': case '~': case '!': return TRUE; default: return FALSE; } return FALSE; } static inline gsize entity_length_in_characters (const TlEntity *e) { switch (e->type) { case TL_ENT_LINK: return LINK_LENGTH; default: return e->length_in_characters; } } /* * tokenize: * * Returns: (transfer full): Tokens */ static GArray * tokenize (const char *input, gsize length_in_bytes) { GArray *tokens = g_array_new (FALSE, TRUE, sizeof (Token)); const char *p = input; gsize cur_character_index = 0; while (p - input < (long)length_in_bytes) { const char *cur_start = p; gunichar cur_char = g_utf8_get_char (p); gsize cur_length = 0; gsize length_in_chars = 0; guint last_token_type = 0; /* If this char already splits, it's a one-char token */ if (char_splits (cur_char)) { const char *old_p = p; p = g_utf8_next_char (p); emplace_token (tokens, cur_start, p - old_p, cur_character_index, 1); cur_character_index ++; continue; } last_token_type = token_type_from_char (cur_char); do { const char *old_p = p; p = g_utf8_next_char (p); cur_char = g_utf8_get_char (p); cur_length += p - old_p; length_in_chars ++; if (token_type_from_char (cur_char) != last_token_type) break; } while (!char_splits (cur_char) && p - input < (long)length_in_bytes); emplace_token (tokens, cur_start, cur_length, cur_character_index, length_in_chars); cur_character_index += length_in_chars; } return g_steal_pointer (&tokens); } static gboolean parse_link_tail (GArray *entities, const Token *tokens, gsize n_tokens, guint *current_position) { guint i = *current_position; const Token *t; gsize paren_level = 0; int first_paren_index = -1; for (;;) { t = &tokens[i]; if (t->type == TOK_WHITESPACE || t->type == TOK_APOSTROPHE) { i --; break; } if (tokens[i].type == TOK_OPEN_PAREN) { if (first_paren_index == -1) { first_paren_index = i; } paren_level ++; if (paren_level == 3) { break; } } else if (tokens[i].type == TOK_CLOSE_PAREN) { if (first_paren_index == -1) { first_paren_index = i; } paren_level --; } i ++; if (i == n_tokens) { i --; break; } } if (paren_level != 0) { g_assert (first_paren_index != -1); i = first_paren_index - 1; // Before that paren } t = &tokens[i]; /* Whatever happened, don't count trailing punctuation */ if (token_in (t, INVALID_AFTER_URL_CHARS)) { i --; } *current_position = i; return TRUE; } // Returns whether a link has been parsed or not. static gboolean parse_link (GArray *entities, const Token *tokens, gsize n_tokens, guint *current_position) { guint i = *current_position; const Token *t; guint start_token = *current_position; guint end_token; gboolean has_protocol = FALSE; t = &tokens[i]; // Some may not even appear before a protocol if (i > 0 && token_in (&tokens[i - 1], INVALID_BEFORE_URL_CHARS)) { return FALSE; } if (token_is_protocol (t)) { // need "://" now. t = &tokens[i + 1]; if (t->type != TOK_COLON) { return FALSE; } i ++; t = &tokens[i + 1]; if (t->type != TOK_SLASH) { return FALSE; } i ++; t = &tokens[i + 1]; if (t->type != TOK_SLASH) { return FALSE; } // If we are at the end now, this is not a link, just the protocol. if (i + 1 == n_tokens - 1) { return FALSE; } i += 2; // Skip to token after second slash has_protocol = TRUE; } else { // Lookbehind: Token before may not be an @, they are not supported. if (i > 0 && token_in (&tokens[i - 1], INVALID_BEFORE_NON_PROTOCOL_URL_CHARS)) { return FALSE; } } if (token_in (&tokens[i], INVALID_URL_CHARS)) { return FALSE; } // Now read until .tld. There can be multiple (e.g. in http://foobar.com.com.com"), // so we need to do this in a greedy way. guint tld_index = i; guint tld_iter = i; gboolean tld_found = FALSE; while (tld_iter < n_tokens - 1) { const Token *t = &tokens[tld_iter]; if (t->type == TOK_WHITESPACE) { if (!tld_found) { return FALSE; } } if (!(t->type == TOK_NUMBER || t->type == TOK_TEXT || t->type == TOK_DOT || t->type == TOK_DASH)) { if (!tld_found) { return FALSE; } else { break; } } if (t->type == TOK_DOT && token_is_tld (&tokens[tld_iter + 1], has_protocol)) { tld_index = tld_iter; tld_found = TRUE; } tld_iter ++; } if (tld_index >= n_tokens - 1 || !tld_found || token_in (&tokens[tld_index - 1], INVALID_URL_CHARS)) { return FALSE; } // tld_index is the TOK_DOT g_assert (tokens[tld_index].type == TOK_DOT); i = tld_index + 1; // If the next token is a colon, we are reading a port if (i < n_tokens - 1 && tokens[i + 1].type == TOK_COLON) { i ++; // i == COLON if (tokens[i + 1].type != TOK_NUMBER) { // According to twitter.com, the link reaches until before the COLON i --; } else { // Skip port number i ++; } } // To continue a link, the next token must be a slash or a question mark // If it isn't, we stop here. if (i < n_tokens - 1) { // A trailing slash is part of the link, other punctuation is not. if (tokens[i + 1].type == TOK_SLASH || tokens[i + 1].type == TOK_QUESTIONMARK) { i ++; if (i < n_tokens - 1) { if (!parse_link_tail (entities, tokens, n_tokens, &i)) { return FALSE; } } else if (tokens[i].type == TOK_QUESTIONMARK) { // Trailing questionmark is not part of the link i --; } } else if (tokens[i + 1].type == TOK_AT) { // We cannot just return FALSE for all non-slash/non-questionmark tokens here since // The Rules say some of them make a link until this token and some of them cause the // entire parsing to produce no link at all, like in the @ case (don't want to turn // email addresses into links). return FALSE; } } end_token = i; g_assert (end_token < n_tokens); emplace_entity_for_tokens (entities, tokens, TL_ENT_LINK, start_token, end_token); *current_position = end_token + 1; // Hop to the next token! return TRUE; } static gboolean parse_mention (GArray *entities, const Token *tokens, gsize n_tokens, guint *current_position) { guint i = *current_position; const guint start_token = i; guint end_token; g_assert (tokens[i].type == TOK_AT); // Lookback at the previous token. If it was a text token // without whitespace between, this is not going to be a mention... if (i > 0) { // Text tokens before an @-token generally destroy the mention, // except in a few cases... if (tokens[i - 1].type == TOK_TEXT && !token_in (&tokens[i - 1], VALID_BEFORE_MENTION_CHARS) && !token_ends_in_accented (&tokens[i - 1])) { return FALSE; } // Numbers and special invalid chars always ruin the mention if (tokens[i - 1].type == TOK_NUMBER || token_in (&tokens[i - 1], INVALID_BEFORE_MENTION_CHARS)) { return FALSE; } } // Skip @ i ++; for (;;) { if (i >= n_tokens) { i --; break; } if (token_in (&tokens[i], INVALID_MENTION_CHARS)) { i --; break; } if (tokens[i].type != TOK_TEXT && tokens[i].type != TOK_NUMBER && tokens[i].type != TOK_UNDERSCORE) { i --; break; } if (tokens[i].type == TOK_TEXT) { const char *text = tokens[i].start; // Special rules apply about what characters may appear in a @screen_name const char *p = text; while (p - text < (long)tokens[i].length_in_bytes) { gunichar c = g_utf8_get_char (p); if (!is_valid_mention_char (c)) { return FALSE; } p = g_utf8_next_char (p); } } i ++; } if (i == start_token) { return FALSE; } // Mentions ending in an '@' are no mentions, e.g. @_@ if (i < n_tokens - 1 && tokens[i + 1].type == TOK_AT) { return FALSE; } end_token = i; g_assert (end_token < n_tokens); emplace_entity_for_tokens (entities, tokens, TL_ENT_MENTION, start_token, end_token); *current_position = end_token + 1; // Hop to the next token! return TRUE; } static gboolean parse_hashtag (GArray *entities, const Token *tokens, gsize n_tokens, guint *current_position) { gsize i = *current_position; const guint start_token = i; guint end_token; gboolean text_found = FALSE; g_assert (tokens[i].type == TOK_HASH); // Lookback at the previous token. If it was a text token // without whitespace between, this is not going to be a mention... if (i > 0 && tokens[i - 1].type == TOK_TEXT && !token_in (&tokens[i - 1], VALID_BEFORE_HASHTAG_CHARS)) { return FALSE; } // Some chars make the entire hashtag invalid if (i > 0 && token_in (&tokens[i - 1], INVALID_BEFORE_HASHTAG_CHARS)) { return FALSE; } //skip # i ++; for (; i < n_tokens; i ++) { if (token_in (&tokens[i], INVALID_HASHTAG_CHARS)) { break; } if (tokens[i].type != TOK_TEXT && tokens[i].type != TOK_NUMBER && tokens[i].type != TOK_UNDERSCORE) { break; } text_found |= tokens[i].type == TOK_TEXT; } if (!text_found) { return FALSE; } end_token = i - 1; g_assert (end_token < n_tokens); emplace_entity_for_tokens (entities, tokens, TL_ENT_HASHTAG, start_token, end_token); *current_position = end_token + 1; // Hop to the next token! return TRUE; } /* * parse: * * Returns: (transfer full): list of tokens */ static GArray * parse (const Token *tokens, gsize n_tokens, gboolean extract_text_entities, guint *n_relevant_entities) { GArray *entities = g_array_new (FALSE, TRUE, sizeof (TlEntity)); guint i = 0; guint relevant_entities = 0; while (i < n_tokens) { const Token *token = &tokens[i]; // We always have to do this since links can begin with whatever word if (parse_link (entities, tokens, n_tokens, &i)) { relevant_entities ++; continue; } switch (token->type) { case TOK_AT: if (parse_mention (entities, tokens, n_tokens, &i)) { relevant_entities ++; continue; } break; case TOK_HASH: if (parse_hashtag (entities, tokens, n_tokens, &i)) { relevant_entities ++; continue; } break; } if (extract_text_entities && token->type == TOK_TEXT) { relevant_entities ++; } emplace_entity_for_tokens (entities, tokens, token->type == TOK_TEXT ? TL_ENT_TEXT : TL_ENT_WHITESPACE, i, i); i ++; } if (n_relevant_entities) { *n_relevant_entities = relevant_entities; } return entities; } static gsize count_entities_in_characters (GArray *entities) { guint i; gsize sum = 0; for (i = 0; i < entities->len; i ++) { const TlEntity *e = &g_array_index (entities, TlEntity, i); sum += entity_length_in_characters (e); } return sum; } /* * tl_count_chars: * input: (nullable): NUL-terminated tweet text * * Returns: The length of @input, in characters. */ gsize tl_count_characters (const char *input) { if (input == NULL || input[0] == '\0') { return 0; } return tl_count_characters_n (input, strlen (input)); } /* * tl_count_characters_n: * input: (nullable): Text to measure * length_in_bytes: Length of @input, in bytes. * * Returns: The length of @input, in characters. */ gsize tl_count_characters_n (const char *input, gsize length_in_bytes) { GArray *tokens; const Token *token_array; gsize n_tokens; GArray *entities; gsize length; if (input == NULL || input[0] == '\0') { return 0; } // From here on, input/length_in_bytes are trusted to be OK tokens = tokenize (input, length_in_bytes); n_tokens = tokens->len; token_array = (const Token *)g_array_free (tokens, FALSE); entities = parse (token_array, n_tokens, FALSE, NULL); length = count_entities_in_characters (entities); g_array_free (entities, TRUE); g_free ((char *)token_array); return length; } /** * tl_extract_entities: * @input: The input text to extract entities from * @out_n_entities: (out): Location to store the amount of entities in the returned * array. If 0, the return value is %NULL. * @out_text_length: (out) (optional): Return location for the complete * length of @input, in characters. This is the same value one would * get from calling tl_count_characters() or tl_count_characters_n() * on @input. * * Returns: An array of #TlEntity. If no entities are found, %NULL is returned. */ TlEntity * tl_extract_entities (const char *input, gsize *out_n_entities, gsize *out_text_length) { gsize dummy; g_return_val_if_fail (out_n_entities != NULL, NULL); if (out_text_length == NULL) { out_text_length = &dummy; } if (input == NULL || input[0] == '\0') { *out_n_entities = 0; *out_text_length = 0; return NULL; } return tl_extract_entities_n (input, strlen (input), out_n_entities, out_text_length); } static TlEntity * tl_extract_entities_internal (const char *input, gsize length_in_bytes, gsize *out_n_entities, gsize *out_text_length, gboolean extract_text_entities) { GArray *tokens; const Token *token_array; gsize n_tokens; GArray *entities; guint n_relevant_entities; TlEntity *result_entities; guint result_index = 0; tokens = tokenize (input, length_in_bytes); n_tokens = tokens->len; token_array = (const Token *)g_array_free (tokens, FALSE); entities = parse (token_array, n_tokens, extract_text_entities, &n_relevant_entities); *out_text_length = count_entities_in_characters (entities); g_free ((char *)token_array); // Only pass mentions, hashtags and links out result_entities = g_malloc (sizeof (TlEntity) * n_relevant_entities); for (guint i = 0; i < entities->len; i ++) { const TlEntity *e = &g_array_index (entities, TlEntity, i); switch (e->type) { case TL_ENT_LINK: case TL_ENT_HASHTAG: case TL_ENT_MENTION: memcpy (&result_entities[result_index], e, sizeof (TlEntity)); result_index ++; break; case TL_ENT_TEXT: if (extract_text_entities) { memcpy (&result_entities[result_index], e, sizeof (TlEntity)); result_index ++; } break; default: {} } } *out_n_entities = n_relevant_entities; g_array_free (entities, TRUE); return result_entities; } /** * tl_extract_entities_n: * @input: The input text to extract entities from * @length_in_bytes: The length of @input, in bytes * @out_n_entities: (out): Location to store the amount of entities in the returned * array. If 0, the return value is %NULL. * @out_text_length: (out) (optional): Return location for the complete * length of @input, in characters. This is the same value one would * get from calling tl_count_characters() or tl_count_characters_n() * on @input. * * Returns: An array of #TlEntity. If no entities are found, %NULL is returned. */ TlEntity * tl_extract_entities_n (const char *input, gsize length_in_bytes, gsize *out_n_entities, gsize *out_text_length) { gsize dummy; g_return_val_if_fail (out_n_entities != NULL, NULL); if (out_text_length == NULL) { out_text_length = &dummy; } if (input == NULL || input[0] == '\0') { *out_n_entities = 0; *out_text_length = 0; return NULL; } return tl_extract_entities_internal (input, length_in_bytes, out_n_entities, out_text_length, FALSE); } /** * tl_extract_entities_and_text: * @input: The input text to extract entities from * @out_n_entities: (out): Location to store the amount of entities in the returned * array. If 0, the return value is %NULL. * @out_text_length: (out) (optional): Return location for the complete * length of @input, in characters. This is the same value one would * get from calling tl_count_characters() or tl_count_characters_n() * on @input. * * This is different from tl_extract_entities() in that it returns all entities * and not just hashtags, links and mentions. This allows for further post-processing * from the caller. * * Returns: An array of #TlEntity. If no entities are found, %NULL is returned. */ TlEntity * tl_extract_entities_and_text (const char *input, gsize *out_n_entities, gsize *out_text_length) { gsize dummy; g_return_val_if_fail (out_n_entities != NULL, NULL); if (out_text_length == NULL) { out_text_length = &dummy; } if (input == NULL || input[0] == '\0') { *out_n_entities = 0; *out_text_length = 0; return NULL; } return tl_extract_entities_internal (input, strlen (input), out_n_entities, out_text_length, TRUE); } /** * tl_extract_entities_and_text_n: * @input: The input text to extract entities from * @length_in_bytes: The length of @input, in bytes * @out_n_entities: (out): Location to store the amount of entities in the returned * array. If 0, the return value is %NULL. * @out_text_length: (out) (optional): Return location for the complete * length of @input, in characters. This is the same value one would * get from calling tl_count_characters() or tl_count_characters_n() * on @input. * * This is different from tl_extract_entities_n() in that it returns all entities * and not just hashtags, links and mentions. This allows for further post-processing * from the caller. * * Returns: An array of #TlEntity. If no entities are found, %NULL is returned. */ TlEntity * tl_extract_entities_and_text_n (const char *input, gsize length_in_bytes, gsize *out_n_entities, gsize *out_text_length) { gsize dummy; g_return_val_if_fail (out_n_entities != NULL, NULL); if (out_text_length == NULL) { out_text_length = &dummy; } if (input == NULL || input[0] == '\0') { *out_n_entities = 0; *out_text_length = 0; return NULL; } return tl_extract_entities_internal (input, length_in_bytes, out_n_entities, out_text_length, TRUE); } corebird-1.7.4/src/libtl/libtweetlength.h000066400000000000000000000044401324604713000204050ustar00rootroot00000000000000/* This file is part of libtweetlength * Copyright (C) 2017 Timm Bäder * * libtweetlength is free software: you can 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. * * libtweetlength is distributed in the hope that it will be useful, * but WITHOUT 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 libtweetlength. If not, see . */ #ifndef __LIBTWEETLENGTH_H__ #define __LIBTWEETLENGTH_H__ #include struct _TlEntity { guint type; const char *start; gsize length_in_bytes; gsize start_character_index; gsize length_in_characters; }; typedef struct _TlEntity TlEntity; typedef enum { TL_ENT_TEXT = 1, TL_ENT_HASHTAG = 2, TL_ENT_LINK = 3, TL_ENT_MENTION = 4, TL_ENT_WHITESPACE = 5, } TlEntityType; gsize tl_count_characters (const char *input); gsize tl_count_characters_n (const char *input, gsize length_in_bytes); TlEntity * tl_extract_entities (const char *input, gsize *out_n_entities, gsize *out_text_length); TlEntity * tl_extract_entities_n (const char *input, gsize length_in_bytes, gsize *out_n_entities, gsize *out_text_length); TlEntity * tl_extract_entities_and_text (const char *input, gsize *out_n_entities, gsize *out_text_length); TlEntity * tl_extract_entities_and_text_n (const char *input, gsize length_in_bytes, gsize *out_n_entities, gsize *out_text_length); #endif corebird-1.7.4/src/list/000077500000000000000000000000001324604713000150565ustar00rootroot00000000000000corebird-1.7.4/src/list/AddListEntry.vala000066400000000000000000000023771324604713000203020ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class AddListEntry : Gtk.ListBoxRow { public AddListEntry (string label) { var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 5); var img = new Gtk.Image.from_icon_name ("list-add-symbolic", Gtk.IconSize.DIALOG); img.pixel_size = 32; img.margin_start = 10; img.hexpand = true; img.halign = Gtk.Align.END; box.add (img); var l = new Gtk.Label (label); l.hexpand = true; l.halign = Gtk.Align.START; box.add (l); box.margin_bottom = 4; box.margin_top = 4; add (box); } } corebird-1.7.4/src/list/DMListEntry.vala000066400000000000000000000110711324604713000201010ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class DMListEntry : Gtk.ListBoxRow, Cb.TwitterItem { private AvatarWidget avatar_image; private Gtk.Label text_label; private Gtk.Label screen_name_label; private TextButton name_button; private Gtk.Label time_delta_label; public string text { set { text_label.label = value; } } public string screen_name { set { screen_name_label.label = "@" + value; } } public new string name { set { name_button.set_markup (value.replace ("&", "&")); } } public Cairo.Surface avatar { set { avatar_image.surface = value; } } public bool seen { get { return true; } set {} } private GLib.TimeSpan last_timediff; public int64 timestamp; public int64 id; public int64 user_id; public unowned MainWindow main_window; public DMListEntry () { this.set_activatable (false); this.get_style_context ().add_class ("tweet"); var grid = new Gtk.Grid (); grid.margin = 6; grid.show (); this.add (grid); this.avatar_image = new AvatarWidget (); avatar_image.size = 48; avatar_image.set_valign (Gtk.Align.START); avatar_image.margin = 4; avatar_image.margin_end = 12; avatar_image.show (); grid.attach (avatar_image, 0, 0, 1, 2); this.name_button = new TextButton (); name_button.set_valign (Gtk.Align.BASELINE); name_button.show (); grid.attach (name_button, 1, 0, 1, 1); this.screen_name_label = new Gtk.Label (null); screen_name_label.set_margin_start (6); screen_name_label.set_margin_end (6); screen_name_label.set_valign (Gtk.Align.BASELINE); screen_name_label.get_style_context ().add_class ("dim-label"); screen_name_label.show (); grid.attach (screen_name_label, 2, 0, 1, 1); this.time_delta_label = new Gtk.Label (null); time_delta_label.set_halign (Gtk.Align.END); time_delta_label.set_valign (Gtk.Align.BASELINE); time_delta_label.set_hexpand (true); time_delta_label.show (); time_delta_label.get_style_context ().add_class ("dim-label"); grid.attach (time_delta_label, 3, 0, 1, 1); this.text_label = new Gtk.Label (null); text_label.set_margin_top (6); text_label.set_margin_end (6); text_label.set_margin_bottom (6); text_label.set_hexpand (true); text_label.set_vexpand (true); text_label.set_xalign (0.0f); text_label.set_line_wrap (true); text_label.set_line_wrap_mode (Pango.WrapMode.WORD_CHAR); text_label.set_use_markup (true); text_label.set_use_markup (true); text_label.set_selectable (true); text_label.show (); grid.attach (text_label, 1, 1, 3, 1); name_button.clicked.connect (() => { var bundle = new Cb.Bundle (); bundle.put_int64 (ProfilePage.KEY_USER_ID, user_id); bundle.put_string (ProfilePage.KEY_SCREEN_NAME, screen_name_label.label.substring (1)); main_window.main_widget.switch_page (Page.PROFILE, bundle); }); this.show (); } public void load_avatar (string avatar_url) { string url = avatar_url; if (this.get_scale_factor () == 2) url = url.replace ("_normal", "_bigger"); Twitter.get ().get_avatar.begin (user_id, url, avatar_image, 48 * this.get_scale_factor ()); } public int update_time_delta (GLib.DateTime? now = null) { GLib.DateTime cur_time; if (now == null) cur_time = new GLib.DateTime.now_local (); else cur_time = now; GLib.DateTime then = new GLib.DateTime.from_unix_local (timestamp); time_delta_label.label = Utils.get_time_delta (then, cur_time); return (int)(cur_time.difference (then) / 1000.0 / 1000.0); } public int64 get_sort_factor () { return timestamp; } public int64 get_timestamp () { return timestamp; } public GLib.TimeSpan get_last_set_timediff () { return this.last_timediff; } public void set_last_set_timediff (GLib.TimeSpan span) { this.last_timediff = span; } } corebird-1.7.4/src/list/DMThreadEntry.vala000066400000000000000000000041261324604713000204000ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/dm-thread-entry.ui")] class DMThreadEntry : Gtk.ListBoxRow { [GtkChild] private Gtk.Label name_label; [GtkChild] private Gtk.Label screen_name_label; [GtkChild] private Gtk.Label last_message_label; [GtkChild] private AvatarWidget avatar_image; [GtkChild] private Gtk.Label unread_count_label; public int64 user_id; public new string name { set { name_label.label = value; } } public string screen_name { set { screen_name_label.label = "@" + value; } } public string last_message { set { last_message_label.label = value; } } public Cairo.Surface? avatar { set { avatar_image.surface = value;} } private int _unread_count = 0; public int unread_count { get { return this._unread_count; } set { this._unread_count = value; this.update_unread_count (); } } public DMThreadEntry (int64 user_id) { this.user_id = user_id; update_unread_count (); } private void update_unread_count () { if (unread_count == 0) unread_count_label.hide (); else { unread_count_label.show (); unread_count_label.label = ngettext ("(%d unread)", "(%d unread)", unread_count).printf(unread_count); } } } corebird-1.7.4/src/list/FavImageRow.vala000066400000000000000000000116761324604713000201050ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class FavImageRow : Gtk.FlowBoxChild { private const int THUMB_WIDTH = 80; private const int THUMB_HEIGHT = 50; private static Cairo.ImageSurface play_icon; private Gtk.EventBox event_box; private Gtk.Image image; private string file_path; private Gtk.GestureMultiPress gesture; public bool is_gif = false; static construct { try { play_icon = (Cairo.ImageSurface)Gdk.cairo_surface_create_from_pixbuf ( new Gdk.Pixbuf.from_resource ("/org/baedert/corebird/data/play.png"), 1, null); } catch (GLib.Error e) { critical (e.message); } } public FavImageRow (string path) { this.file_path = path; event_box = new Gtk.EventBox (); event_box.show (); image = new Gtk.Image (); image.set_size_request (THUMB_WIDTH, THUMB_HEIGHT); image.set_halign (Gtk.Align.CENTER); image.set_valign (Gtk.Align.CENTER); image.margin = 3; image.show (); event_box.add (image); this.add (event_box); this.set_valign (Gtk.Align.START); /* Sigh */ event_box.enter_notify_event.connect (() => { var flags = this.get_state_flags (); this.set_state_flags (flags | Gtk.StateFlags.PRELIGHT, true); return false; }); event_box.leave_notify_event.connect (() => { this.unset_state_flags (Gtk.StateFlags.PRELIGHT); return false; }); gesture = new Gtk.GestureMultiPress (event_box); gesture.set_propagation_phase (Gtk.PropagationPhase.CAPTURE); gesture.set_button (0); gesture.pressed.connect (() => { Gdk.EventSequence sequence = this.gesture.get_current_sequence (); Gdk.EventButton event = (Gdk.EventButton)this.gesture.get_last_event (sequence); if (event.triggers_context_menu ()) { var menu = new Gtk.Menu (); var delete_item = new Gtk.MenuItem.with_label (_("Delete")); delete_item.activate.connect (() => { var flowbox = this.get_parent (); if (!(flowbox is Gtk.FlowBox)) { warning ("Parent is not a flowbox"); return; } try { var file = GLib.File.new_for_path (this.file_path); file.trash (); flowbox.remove (this); } catch (GLib.Error e) { warning (e.message); } }); menu.add (delete_item); menu.attach_to_widget (this, null); menu.show_all (); menu.popup (null, null, null, event.button, event.time); } else { this.set_state_flags (this.get_state_flags () | Gtk.StateFlags.ACTIVE, true); } gesture.set_state (Gtk.EventSequenceState.CLAIMED); }); gesture.released.connect (() => { Gdk.EventSequence sequence = this.gesture.get_current_sequence (); Gdk.EventButton? event = (Gdk.EventButton?)this.gesture.get_last_event (sequence); this.unset_state_flags (Gtk.StateFlags.ACTIVE); if (event != null && event.button == Gdk.BUTTON_PRIMARY) { /* This gesture blocks the flowbox gesture so implement activating manually. */ if (this.get_parent () is Gtk.FlowBox) { ((Gtk.FlowBox)this.get_parent ()).child_activated (this); } } }); this.get_style_context ().add_class ("fav-image-item"); load_image.begin (); } public override bool draw (Cairo.Context ct) { base.draw (ct); if (this.is_gif) { double scale = 0.6; int width = this.get_allocated_width (); int height = this.get_allocated_height (); double x = (width / 2.0) / scale - (play_icon.get_width () / 2.0); double y = (height / 2.0) / scale - (play_icon.get_height () / 2.0); ct.scale (scale, scale); ct.set_source_surface (play_icon, x, y); ct.paint (); } return false; } public unowned string get_image_path () { return file_path; } private async void load_image () { try { var in_stream = GLib.File.new_for_path (file_path).read (); var pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async (in_stream, THUMB_WIDTH, THUMB_HEIGHT, true); in_stream.close (); this.image.set_from_pixbuf (pixbuf); } catch (GLib.Error e) { warning (e.message); } } } corebird-1.7.4/src/list/FilterListEntry.vala000066400000000000000000000050741324604713000210340ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/filter-list-entry.ui")] class FilterListEntry : Gtk.ListBoxRow { [GtkChild] private Gtk.Label content_label; [GtkChild] private Gtk.Revealer revealer; [GtkChild] private Gtk.Stack stack; [GtkChild] private Gtk.Grid normal_box; [GtkChild] private Gtk.Box delete_box; private unowned Cb.Filter _filter; public unowned Cb.Filter filter { set { content_label.label = value.get_contents (); _filter = value; } get { return _filter; } } public string content { set { content_label.label = value; } get { return content_label.label; } } private unowned Account account; private unowned MainWindow main_window; public FilterListEntry (Cb.Filter f, Account account, MainWindow main_window) { this.filter = f; this.account = account; this.main_window = main_window; } construct { revealer.notify["child-revealed"].connect (() => { if (!revealer.child_revealed) { ((Gtk.Container)this.get_parent ()).remove (this); } }); } [GtkCallback] private void menu_button_clicked_cb () { stack.visible_child = delete_box; this.activatable = false; } [GtkCallback] private void cancel_button_clicked_cb () { stack.visible_child = normal_box; this.activatable = true; } [GtkCallback] private void delete_button_clicked_cb () { for (int i = 0; i < account.filters.length; i ++) { var f = account.filters.get (i); if (f.get_id () == this.filter.get_id ()) { account.filters.remove (f); account.db.exec ("DELETE FROM `filters` WHERE `id`='%d'".printf (f.get_id ())); revealer.reveal_child = false; main_window.rerun_filters (); return; } } } } corebird-1.7.4/src/list/ListListEntry.vala000066400000000000000000000126131324604713000205170ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/list-list-entry.ui")] public class ListListEntry : Gtk.ListBoxRow { public static int sort_func (Gtk.ListBoxRow r1, Gtk.ListBoxRow r2) { if (!(r1 is ListListEntry)) return -1; return ((ListListEntry)r1).name.ascii_casecmp (((ListListEntry)r2).name); } [GtkChild] private Gtk.Label name_label; public new string name { set { name_label.label = normalize_name (value); } get { return name_label.label; } } [GtkChild] private Gtk.Label description_label; public string description { set { description_label.label = value; } get { return description_label.label; } } [GtkChild] private Gtk.Stack stack; [GtkChild] private Gtk.Button subscribe_button; [GtkChild] private Gtk.Button unsubscribe_button; [GtkChild] private Gtk.Button delete_button; [GtkChild] private Gtk.Button cancel_button; public int64 id; public bool user_list = false; public string creator_screen_name; public int n_subscribers; public int n_members = 0; public int64 created_at; public string mode; private unowned Account account; public ListListEntry.from_json_data (Json.Object obj, Account account) { this.account = account; var user = obj.get_object_member ("user"); name = normalize_name (obj.get_string_member ("full_name")); description = obj.get_string_member ("description"); id = obj.get_int_member ("id"); creator_screen_name = user.get_string_member ("screen_name"); n_subscribers = (int)obj.get_int_member ("subscriber_count"); n_members = (int)obj.get_int_member ("member_count"); created_at = Cb.Utils.parse_date (obj.get_string_member ("created_at")).to_unix (); mode = obj.get_string_member ("mode"); bool following = obj.get_boolean_member ("following"); if (following || user.get_int_member ("id") == account.id) { unsubscribe_button.show (); subscribe_button.hide (); } else { unsubscribe_button.hide (); subscribe_button.show (); } if (user.get_int_member ("id") == account.id) { user_list = true; unsubscribe_button.hide (); } else { delete_button.hide (); } } private string normalize_name (string name) { if (name.contains ("/lists/")) { return name.replace ("/lists/", "/"); } return name; } [GtkCallback] private void delete_button_clicked_cb () { this.sensitive = false; var call = account.proxy.new_call (); call.set_function ("1.1/lists/destroy.json"); call.set_method ("POST"); call.add_param ("list_id", id.to_string ()); call.invoke_async.begin (null, (o, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), e.message, GLib.Log.LINE, GLib.Log.FILE); this.sensitive = true; return; } }); } [GtkCallback] private void subscribe_button_clicked_cb () { subscribe_button.sensitive = false; cancel_button.sensitive = false; var call = account.proxy.new_call (); call.set_function ("1.1/lists/subscribers/create.json"); call.set_method ("POST"); call.add_param ("list_id", id.to_string ()); call.invoke_async.begin (null, (o, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), e.message, GLib.Log.LINE, GLib.Log.FILE); return; } finally { subscribe_button.sensitive = true; cancel_button.sensitive = true; } subscribe_button.hide (); unsubscribe_button.show (); }); } [GtkCallback] private void unsubscribe_button_clicked_cb () { this.sensitive = false; var call = account.proxy.new_call (); call.set_function ("1.1/lists/subscribers/destroy.json"); call.set_method ("POST"); call.add_param ("list_id", id.to_string ()); call.invoke_async.begin (null, (o, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), e.message, GLib.Log.LINE, GLib.Log.FILE); return; } }); } [GtkCallback] private void more_button_clicked_cb () { stack.visible_child_name = "more"; this.activatable = false; } [GtkCallback] private void cancel_button_clicked_cb () { stack.visible_child_name = "default"; this.activatable = true; } [GtkCallback] private bool focus_out_cb () { stack.visible_child_name = "default"; return false; } } corebird-1.7.4/src/list/NewListEntry.vala000066400000000000000000000034421324604713000203350ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/new-list-entry.ui")] class NewListEntry : Gtk.ListBoxRow { [GtkChild] private Gtk.Entry list_name_entry; [GtkChild] private Gtk.Revealer revealer; [GtkChild] private Gtk.Button create_list_button; public signal void create_activated (string list_name); construct { list_name_entry.buffer.notify["text"].connect (name_text_changed_cb); } public void reveal () { revealer.reveal_child = true; this.activatable = false; list_name_entry.grab_focus (); } public void unreveal () { revealer.reveal_child = false; this.activatable = true; list_name_entry.text = ""; } [GtkCallback] private void create_list_button_clicked_cb () { create_activated (list_name_entry.text); } private void name_text_changed_cb () { string name = list_name_entry.text; create_list_button.sensitive = false; if (name.length == 0 || name.char_count () > 25) return; if (name.get_char (0).isdigit()) return; create_list_button.sensitive = true; } } corebird-1.7.4/src/list/SnippetListEntry.vala000066400000000000000000000040711324604713000212250ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class SnippetListEntry : Gtk.ListBoxRow { private Gtk.Label key_label; private Gtk.Label value_label; private Gtk.Revealer revealer; public string key { get { return key_label.label; } set { key_label.label = value; } } public string value { get { return value_label.label; } set { value_label.label = value; } } public SnippetListEntry (string key, string value) { this.revealer = new Gtk.Revealer (); revealer.reveal_child = true; var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12); box.margin = 6; box.homogeneous = true; key_label = new Gtk.Label (key); key_label.halign = Gtk.Align.START; key_label.hexpand = true; key_label.ellipsize = Pango.EllipsizeMode.END; box.add (key_label); value_label = new Gtk.Label (value); value_label.halign = Gtk.Align.START; value_label.hexpand = true; value_label.xalign = 0; value_label.ellipsize = Pango.EllipsizeMode.END; box.add (value_label); revealer.add (box); this.add (revealer); this.get_style_context ().add_class ("tweet"); } public void reveal () { revealer.notify["child-revealed"].connect (() => { if (!revealer.child_revealed) { this.get_parent ().remove (this); } }); revealer.reveal_child = false; } } corebird-1.7.4/src/list/StartConversationEntry.vala000066400000000000000000000164611324604713000224450ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/start-conversation-entry.ui")] class StartConversationEntry : Gtk.ListBoxRow { private const int MAX_RESULTS = 7; [GtkChild] private Gtk.Revealer revealer; [GtkChild] private ReplyEntry name_entry; [GtkChild] private Gtk.Stack go_stack; [GtkChild] private Gtk.Spinner go_spinner; private UserCompletion user_completion; private Gtk.Window completion_window; private Gtk.ListBox completion_list = new Gtk.ListBox (); private int current_match = -1; public signal void start (int64 user_id, string screen_name, string name, string avatar_url); private unowned Account account; public StartConversationEntry (Account account) { this.account = account; completion_window = new Gtk.Window (Gtk.WindowType.POPUP); completion_window.set_type_hint (Gdk.WindowTypeHint.COMBO); completion_window.set_screen (name_entry.get_screen ()); completion_window.destroy_with_parent = true; completion_window.focus_out_event.connect (() => { completion_window.hide (); return false; }); name_entry.focus_out_event.connect (() => { completion_window.hide (); return false; }); var popup_frame = new Gtk.Frame (null); var scroller = new Gtk.ScrolledWindow (null, null); popup_frame.add (scroller); scroller.add (completion_list); completion_window.add (popup_frame); completion_list.activate_on_single_click = false; completion_list.row_activated.connect (go_button_clicked_cb); user_completion = new UserCompletion (account, MAX_RESULTS); user_completion.connect_to (name_entry.buffer, "text"); user_completion.start_completion.connect (() => { completion_window.set_attached_to (this.name_entry); completion_window.set_transient_for ((Gtk.Window)this.get_toplevel ()); position_popup_window (); completion_window.show_all (); completion_list.foreach ((w) => { completion_list.remove (w); }); }); user_completion.populate_completion.connect ((screen_name, name) => { var l = new CompletionListEntry (screen_name, name); l.show_all (); completion_list.add (l); }); name_entry.key_press_event.connect (name_entry_key_pressed); // activate.connect (() => { // go_stack.visible_child_name = "spinner"; // go_spinner.start (); // }); } private void position_popup_window () { int x, y; Gtk.Allocation alloc; name_entry.get_allocation (out alloc); name_entry.get_window ().get_origin (out x, out y); x += alloc.x; y += alloc.y + alloc.height; completion_window.move (x, y); completion_window.resize (alloc.width, 50); } private bool name_entry_key_pressed (Gdk.EventKey evt) { uint num_results = completion_list.get_children ().length (); if (num_results == 0) return Gdk.EVENT_PROPAGATE; if (evt.keyval == Gdk.Key.Down) { current_match = (current_match + 1) % (int)num_results; var row = completion_list.get_row_at_index (current_match); completion_list.select_row (row); return Gdk.EVENT_STOP; } else if (evt.keyval == Gdk.Key.Up) { current_match --; if (current_match < 0) current_match = (int)num_results - 1; var row = completion_list.get_row_at_index (current_match); completion_list.select_row (row); return Gdk.EVENT_STOP; } else if (evt.keyval == Gdk.Key.Return) { } return Gdk.EVENT_PROPAGATE; } construct { name_entry.cancelled.connect (() => { unreveal (); this.grab_focus (); }); } public void reveal () { revealer.reveal_child = true; name_entry.grab_focus (); this.activatable = false; } public void unreveal () { revealer.reveal_child = false; completion_window.hide (); this.activatable = true; } [GtkCallback] private void go_button_clicked_cb () { // if (name_entry.text.length > 0) // activated (); string screen_name; if (completion_list.get_selected_row () != null) { screen_name = ((CompletionListEntry)completion_list.get_selected_row ()).get_screen_name (); name_entry.text = screen_name; } else screen_name = name_entry.text; if (screen_name.has_prefix ("@")) screen_name = screen_name.substring (1); if (screen_name.length <= 0) return; go_stack.visible_child_name = "spinner"; go_spinner.start(); name_entry.sensitive = false; var call = account.proxy.new_call (); call.set_function ("1.1/users/show.json"); call.set_method ("GET"); call.add_param ("include_entities", "false"); call.add_param ("screen_name", screen_name); call.invoke_async.begin (null, (obj, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { critical (e.message); go_stack.visible_child_name = "button"; name_entry.sensitive = true; return; } // do stuff Json.Parser parser = new Json.Parser (); try { parser.load_from_data (call.get_payload ()); } catch (GLib.Error e) { critical (e.message); go_stack.visible_child_name = "button"; name_entry.sensitive = true; return; } var root = parser.get_root ().get_object (); int64 user_id = root.get_int_member ("id"); UserUtils.load_friendship.begin (account, user_id, "", (obj, res) => { uint fr = UserUtils.load_friendship.end (res); if ((fr & FRIENDSHIP_CAN_DM) == 0) { go_stack.visible_child_name = "button"; name_entry.sensitive = true; return; } string name = root.get_string_member ("name"); string avatar_url = root.get_string_member ("profile_image_url"); start (user_id, screen_name, name, avatar_url); name_entry.sensitive = true; go_stack.visible_child_name = "button"; }); }); } } class CompletionListEntry : Gtk.ListBoxRow { private Gtk.Label name_label; private Gtk.Label screen_name_label; public CompletionListEntry (string screen_name, string name) { var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6); name_label = new Gtk.Label (name); name_label.ellipsize = Pango.EllipsizeMode.END; name_label.use_markup = true; screen_name_label = new Gtk.Label ("@" + screen_name); name_label.set_valign (Gtk.Align.BASELINE); screen_name_label.set_valign (Gtk.Align.BASELINE); screen_name_label.get_style_context ().add_class ("dim-label"); box.add (name_label); box.add (screen_name_label); add (box); } public string get_screen_name () { return screen_name_label.get_label ().substring (1); } } corebird-1.7.4/src/list/TweetListEntry.vala000066400000000000000000000574131324604713000207030ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/tweet-list-entry.ui")] public class TweetListEntry : Cb.TwitterItem, Gtk.ListBoxRow { private const GLib.ActionEntry[] action_entries = { {"quote", quote_activated}, {"delete", delete_activated} }; [GtkChild] private Gtk.Label screen_name_label; [GtkChild] private TextButton name_button; [GtkChild] private Gtk.Label time_delta_label; [GtkChild] private AvatarWidget avatar_image; [GtkChild] private Gtk.Label text_label; [GtkChild] private Gtk.Label rt_label; [GtkChild] private Gtk.Image rt_image; [GtkChild] private Gtk.Image rt_status_image; [GtkChild] private Gtk.Image fav_status_image; [GtkChild] private DoubleTapButton retweet_button; [GtkChild] private Gtk.ToggleButton favorite_button; [GtkChild] private Gtk.Grid grid; [GtkChild] private Gtk.Stack stack; [GtkChild] private Gtk.Box action_box; [GtkChild] private Gtk.Label reply_label; /* Conditionally created widgets... */ private Gtk.Label? quote_label = null; private TextButton? quote_name = null; private Gtk.Label? quote_time_delta = null; private Gtk.Label? quote_screen_name = null; private Gtk.Label? quote_reply_label = null; private Gtk.Grid? quote_grid = null; private Gtk.Stack? media_stack = null; private MultiMediaWidget? mm_widget = null; private bool _read_only = false; public bool read_only { set { assert (value); if (mm_widget != null) mm_widget.sensitive = !value; this.grid.remove (name_button); var name_label = new Gtk.Label ("" + tweet.get_user_name () + ""); name_label.set_use_markup (true); name_label.valign = Gtk.Align.BASELINE; name_label.ellipsize = Pango.EllipsizeMode.END; name_label.xalign = 0; name_label.show (); this.grid.attach (name_label, 1, 0, 1, 1); this.get_style_context ().add_class ("read-only"); this._read_only = value; } } public bool shows_actions { get { return stack.visible_child == action_box; } } private unowned Account account; private unowned MainWindow main_window; public Cb.Tweet tweet; private bool values_set = false; private bool delete_first_activated = false; private GLib.TimeSpan last_timediff; [Signal (action = true)] private signal void reply_tweet (); [Signal (action = true)] private signal void favorite_tweet (); [Signal (action = true)] private signal void retweet_tweet (); [Signal (action = true)] private signal void delete_tweet (); [Signal (action = true)] private signal void quote_tweet (); public TweetListEntry (Cb.Tweet tweet, MainWindow? main_window, Account account, bool restrict_height = false) { this.account = account; this.tweet = tweet; this.main_window = main_window; name_button.set_markup (tweet.get_user_name ()); screen_name_label.label = "@" + tweet.get_screen_name (); if (tweet.avatar_url != null) { string avatar_url = tweet.avatar_url; if (this.get_scale_factor () == 2) avatar_url = avatar_url.replace ("_normal", "_bigger"); Twitter.get ().get_avatar.begin (tweet.get_user_id (), avatar_url, avatar_image, 48 * this.get_scale_factor ()); } avatar_image.verified = tweet.is_flag_set (Cb.TweetState.VERIFIED); text_label.label = tweet.get_trimmed_text (Settings.get_text_transform_flags ()).strip (); if (tweet.retweeted_tweet != null) { rt_label.show (); rt_image.show (); var buff = new StringBuilder (); buff.append ("") .append (tweet.source_tweet.author.user_name) .append (""); rt_label.label = buff.str; } if ((tweet.retweeted_tweet != null && tweet.retweeted_tweet.reply_id != 0) || tweet.source_tweet.reply_id != 0) { var buff = new StringBuilder (); if (tweet.retweeted_tweet != null) Cb.Utils.write_reply_text (ref tweet.retweeted_tweet, buff); else Cb.Utils.write_reply_text (ref tweet.source_tweet, buff); reply_label.label = buff.str; reply_label.show (); } if (tweet.quoted_tweet != null) { this.create_quote_grid (tweet.quoted_tweet.reply_id != 0); quote_label.label = Cb.TextTransform.tweet (ref tweet.quoted_tweet, Settings.get_text_transform_flags (), 0); if (quote_label.label.length == 0) quote_label.hide (); quote_name.set_markup (tweet.quoted_tweet.author.user_name); quote_screen_name.label = "@" + tweet.quoted_tweet.author.screen_name; if (tweet.quoted_tweet.reply_id != 0) { var buff = new GLib.StringBuilder (); Cb.Utils.write_reply_text (ref tweet.quoted_tweet, buff); quote_reply_label.label = buff.str; } } retweet_button.active = tweet.is_flag_set (Cb.TweetState.RETWEETED); retweet_button.sensitive = !(tweet.is_flag_set (Cb.TweetState.PROTECTED) && tweet.get_user_id () != account.id); favorite_button.active = tweet.is_flag_set (Cb.TweetState.FAVORITED); tweet.state_changed.connect (state_changed_cb); if (tweet.has_inline_media ()) { this.create_media_widget (tweet.is_flag_set (Cb.TweetState.NSFW)); mm_widget.restrict_height = restrict_height; mm_widget.set_all_media (tweet.get_medias ()); mm_widget.media_clicked.connect (media_clicked_cb); mm_widget.media_invalid.connect (media_invalid_cb); mm_widget.window = main_window; if (text_label.label.length == 0 && tweet.quoted_tweet == null) { if (this.media_stack == null) this.grid.child_set (mm_widget, "top-attach", 2); else this.grid.child_set (media_stack, "top-attach", 2); } if (tweet.is_flag_set (Cb.TweetState.NSFW)) Settings.get ().changed["hide-nsfw-content"].connect (hide_nsfw_content_changed_cb); Settings.get ().changed["media-visibility"].connect (media_visibility_changed_cb); mm_widget.visible = (Settings.get_media_visiblity () == MediaVisibility.SHOW); } var actions = new GLib.SimpleActionGroup (); actions.add_action_entries (action_entries, this); this.insert_action_group ("tweet", actions); if (tweet.get_user_id () != account.id) ((GLib.SimpleAction)actions.lookup_action ("delete")).set_enabled (false); if (tweet.is_flag_set (Cb.TweetState.PROTECTED)) ((GLib.SimpleAction)actions.lookup_action ("quote")).set_enabled (false); reply_tweet.connect (reply_tweet_activated); delete_tweet.connect (delete_tweet_activated); quote_tweet.connect (quote_activated); favorite_tweet.connect (() => { favorite_button.active = !favorite_button.active; }); retweet_tweet.connect (() => { retweet_button.tap (); }); if (tweet.is_flag_set (Cb.TweetState.FAVORITED)) fav_status_image.show (); if (tweet.is_flag_set (Cb.TweetState.RETWEETED)) rt_status_image.show (); values_set = true; update_time_delta (); // TODO All these settings signal connections with lots of tweets could be costly... Settings.get ().changed["text-transform-flags"].connect (transform_flags_changed_cb); } ~TweetListEntry () { Settings.get ().changed["text-transform-flags"].disconnect (transform_flags_changed_cb); if (tweet.is_flag_set (Cb.TweetState.NSFW) && this.media_stack != null) Settings.get ().changed["hide-nsfw-content"].disconnect (hide_nsfw_content_changed_cb); if (this.mm_widget != null) Settings.get ().changed["media-visibility"].disconnect (media_visibility_changed_cb); } private void media_visibility_changed_cb () { if (Settings.get_media_visiblity () == MediaVisibility.SHOW) this.mm_widget.show (); else this.mm_widget.hide (); } private void transform_flags_changed_cb () { text_label.label = tweet.get_trimmed_text (Settings.get_text_transform_flags ()); if (this.tweet.quoted_tweet != null) { this.quote_label.label = Cb.TextTransform.tweet (ref tweet.quoted_tweet, Settings.get_text_transform_flags (), 0); } if (this.mm_widget != null && this.tweet.quoted_tweet == null) { Gtk.Widget w = media_stack != null ? ((Gtk.Widget)media_stack) : ((Gtk.Widget)mm_widget); if (text_label.label.length == 0) this.grid.child_set (w, "top-attach", 2); else this.grid.child_set (w, "top-attach", 8); } } private void hide_nsfw_content_changed_cb () { assert (this.media_stack != null); if (this.tweet.is_flag_set (Cb.TweetState.NSFW) && Settings.hide_nsfw_content ()) this.media_stack.visible_child_name = "nsfw"; else this.media_stack.visible_child = mm_widget; } private void media_clicked_cb (Cb.Media m, int index, double px, double py) { TweetUtils.handle_media_click (this.tweet, this.main_window, index, px, py); } private void delete_tweet_activated () { if (tweet.get_user_id () != account.id) return; // Nope. if (delete_first_activated) { TweetUtils.delete_tweet.begin (account, tweet, () => { sensitive = false; }); } else delete_first_activated = true; } static construct { unowned Gtk.BindingSet binding_set = Gtk.BindingSet.by_class ((GLib.ObjectClass)typeof (TweetListEntry).class_ref ()); Gtk.BindingEntry.add_signal (binding_set, Gdk.Key.r, 0, "reply-tweet", 0, null); Gtk.BindingEntry.add_signal (binding_set, Gdk.Key.d, 0, "delete-tweet", 0, null); Gtk.BindingEntry.add_signal (binding_set, Gdk.Key.t, 0, "retweet-tweet", 0, null); Gtk.BindingEntry.add_signal (binding_set, Gdk.Key.f, 0, "favorite-tweet", 0, null); Gtk.BindingEntry.add_signal (binding_set, Gdk.Key.q, 0, "quote-tweet", 0, null); } [GtkCallback] private bool focus_out_cb (Gdk.EventFocus evt) { delete_first_activated = false; retweet_button.reset (); return false; } [GtkCallback] private bool key_released_cb (Gdk.EventKey evt) { #if DEBUG switch(evt.keyval) { case Gdk.Key.k: stdout.printf (tweet.json_data+"\n"); message ("Seen : %s", tweet.get_seen ().to_string ()); message ("My retweet: %s", tweet.my_retweet.to_string ()); message ("Retweeted: %s", tweet.is_flag_set (Cb.TweetState.RETWEETED).to_string ()); message ("Favorited: %s", tweet.is_flag_set (Cb.TweetState.FAVORITED).to_string ()); message ("Protected: %s", tweet.is_flag_set (Cb.TweetState.PROTECTED).to_string ()); message ("State : %s", tweet.state.to_string ()); message ("Source tweet author id: %s", tweet.source_tweet.author.id.to_string ()); message ("Source tweet author screen_name: %s", tweet.source_tweet.author.screen_name); if (tweet.retweeted_tweet != null) { message ("Retweet!"); message ("Retweet author id: %s", tweet.retweeted_tweet.author.id.to_string ()); message ("Retweet author screen_name: %s", tweet.retweeted_tweet.author.screen_name); } if (tweet.has_inline_media ()) { foreach (Cb.Media m in tweet.get_medias ()) { message ("Media: %p", m); } } return Gdk.EVENT_STOP; } #endif return Gdk.EVENT_PROPAGATE; } /** * Retweets or un-retweets the tweet. */ [GtkCallback] private void retweet_button_toggled_cb () { bool retweetable = tweet.get_user_id () == account.id || !tweet.is_flag_set (Cb.TweetState.PROTECTED); if (!retweetable || !values_set) return; retweet_button.sensitive = false; TweetUtils.set_retweet_status.begin (account, tweet, retweet_button.active, () => { retweet_button.sensitive = true; }); if (shows_actions) toggle_mode (); } [GtkCallback] private void favorite_button_toggled_cb () { if (!values_set) return; favorite_button.sensitive = false; TweetUtils.set_favorite_status.begin (account, tweet, favorite_button.active, () => { favorite_button.sensitive = true; }); if (shows_actions) toggle_mode (); } [GtkCallback] private void name_button_clicked_cb () { int64 user_id; string screen_name; if (tweet.retweeted_tweet != null) { user_id = tweet.retweeted_tweet.author.id; screen_name = tweet.retweeted_tweet.author.screen_name; } else { user_id = tweet.source_tweet.author.id; screen_name = tweet.source_tweet.author.screen_name; } var bundle = new Cb.Bundle (); bundle.put_int64 (ProfilePage.KEY_USER_ID, user_id); bundle.put_string (ProfilePage.KEY_SCREEN_NAME, screen_name); main_window.main_widget.switch_page (Page.PROFILE, bundle); } private void quote_name_button_clicked_cb () { assert (tweet.quoted_tweet != null); var bundle = new Cb.Bundle (); bundle.put_int64 (ProfilePage.KEY_USER_ID, tweet.quoted_tweet.author.id); bundle.put_string (ProfilePage.KEY_SCREEN_NAME, tweet.quoted_tweet.author.screen_name); main_window.main_widget.switch_page (Page.PROFILE, bundle); } [GtkCallback] private void reply_button_clicked_cb () { ComposeTweetWindow ctw = new ComposeTweetWindow (this.main_window, this.account, this.tweet, ComposeTweetWindow.Mode.REPLY); ctw.show (); if (shows_actions) toggle_mode (); } private void show_media_clicked_cb () { media_stack.visible_child = mm_widget; } private void quote_activated () { ComposeTweetWindow ctw = new ComposeTweetWindow (this.main_window, this.account, this.tweet, ComposeTweetWindow.Mode.QUOTE); ctw.show (); if (shows_actions) toggle_mode (); } private void reply_tweet_activated () { ComposeTweetWindow ctw = new ComposeTweetWindow (this.main_window, this.account, this.tweet, ComposeTweetWindow.Mode.REPLY); ctw.show (); } private void delete_activated () { delete_first_activated = true; delete_tweet (); toggle_mode (); } [GtkCallback] private bool link_activated_cb (string uri) { if (this._read_only) { return false; } this.grab_focus (); return TweetUtils.activate_link (uri, main_window); } [GtkCallback] private void populate_popup_cb (Gtk.Label source, Gtk.Menu menu) { var link_text = source.get_current_uri (); if (link_text.has_prefix ("#")) { var item = new Gtk.MenuItem.with_label (_("Block %s").printf (link_text)); item.show (); item.activate.connect (() => { Utils.create_persistent_filter (link_text, account); main_window.rerun_filters (); }); menu.add (item); } } private void media_invalid_cb () { Cb.TransformFlags flags = Settings.get_text_transform_flags () & ~Cb.TransformFlags.REMOVE_MEDIA_LINKS; string new_text; if (tweet.retweeted_tweet != null) new_text = Cb.TextTransform.tweet (ref tweet.retweeted_tweet, flags, 0); else new_text = Cb.TextTransform.tweet (ref tweet.source_tweet, flags, 0); this.text_label.label = new_text; if (tweet.quoted_tweet != null) { string new_quote_text = Cb.TextTransform.tweet (ref tweet.quoted_tweet, flags, 0); this.quote_label.label = new_quote_text; } } private void state_changed_cb () { this.values_set = false; this.fav_status_image.visible = tweet.is_flag_set (Cb.TweetState.FAVORITED); this.favorite_button.active = tweet.is_flag_set (Cb.TweetState.FAVORITED); this.retweet_button.active = tweet.is_flag_set (Cb.TweetState.RETWEETED); this.rt_status_image.visible = tweet.is_flag_set (Cb.TweetState.RETWEETED); if (tweet.is_flag_set (Cb.TweetState.DELETED)) { this.sensitive = false; stack.visible_child = grid; } this.values_set = true; } public void set_avatar (Cairo.Surface surface) { /* This should only ever be called from the settings page. */ this.avatar_image.surface = surface; } /** * Updates the time delta label in the upper right * * @return The seconds between the current time and * the time the tweet was created */ public int update_time_delta (GLib.DateTime? now = null) { GLib.DateTime cur_time; if (now == null) cur_time = new GLib.DateTime.now_local (); else cur_time = now; GLib.DateTime then = new GLib.DateTime.from_unix_local ( tweet.retweeted_tweet != null ? tweet.retweeted_tweet.created_at : tweet.source_tweet.created_at); time_delta_label.label = Utils.get_time_delta (then, cur_time); if (quote_time_delta != null) { then = new GLib.DateTime.from_unix_local (tweet.quoted_tweet.created_at); quote_time_delta.label = Utils.get_time_delta (then, cur_time); } return (int)(cur_time.difference (then) / 1000.0 / 1000.0); } public int64 get_sort_factor () { return tweet.source_tweet.id; } public int64 get_timestamp () { return tweet.source_tweet.created_at; } public GLib.TimeSpan get_last_set_timediff () { return this.last_timediff; } public void set_last_set_timediff (GLib.TimeSpan span) { this.last_timediff = span; } public void toggle_mode () { if (this._read_only) return; if (stack.visible_child == action_box) { stack.visible_child = grid; this.activatable = true; } else { stack.visible_child = action_box; this.activatable = false; } } private int64 start_time; private int64 end_time; private bool anim_tick (Gtk.Widget widget, Gdk.FrameClock frame_clock) { int64 now = frame_clock.get_frame_time (); if (now > end_time) { this.opacity = 1.0; return false; } double t = (now - start_time) / (double)(end_time - start_time); t = ease_out_cubic (t); this.opacity = t; return true; } public void fade_in () { if (this.get_realized ()) { this.show (); return; } ulong realize_id = 0; realize_id = this.realize.connect (() => { this.start_time = this.get_frame_clock ().get_frame_time (); this.end_time = start_time + TRANSITION_DURATION; this.add_tick_callback (anim_tick); this.disconnect (realize_id); }); this.show (); } private void create_media_widget (bool nsfw) { this.mm_widget = new MultiMediaWidget (); mm_widget.halign = Gtk.Align.FILL; mm_widget.hexpand = true; mm_widget.margin_top = 6; if (nsfw) { this.media_stack = new Gtk.Stack (); media_stack.transition_type = Gtk.StackTransitionType.CROSSFADE; media_stack.add (mm_widget); var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 12); box.valign = Gtk.Align.CENTER; var label = new Gtk.Label (_("This tweet contains images marked as inappropriate")); label.margin_start = 12; label.margin_end = 12; label.wrap = true; label.wrap_mode = Pango.WrapMode.WORD_CHAR; box.add (label); var button = new Gtk.Button.with_label (_("Show anyway")); button.halign = Gtk.Align.CENTER; button.valign = Gtk.Align.CENTER; button.clicked.connect (show_media_clicked_cb); box.add (button); media_stack.add_named (box, "nsfw"); media_stack.show_all (); if (Settings.hide_nsfw_content ()) media_stack.visible_child_name = "nsfw"; else media_stack.visible_child = mm_widget; if (this.tweet.quoted_tweet != null) { media_stack.margin_start = 12; this.quote_grid.attach (media_stack, 0, 3, 3, 1); } else { this.grid.attach (media_stack, 1, 7, 7, 1); } } else { /* We will never have to hide mm_widget */ mm_widget.show_all (); if (this.tweet.quoted_tweet != null) { mm_widget.margin_start = 12; this.quote_grid.attach (mm_widget, 0, 3, 3, 1); } else { this.grid.attach (mm_widget, 1, 7, 7, 1); } } } private bool quote_link_activated_cb (string uri) { if (this._read_only) { return false; } this.grab_focus (); return TweetUtils.activate_link (uri, main_window); } private void create_quote_grid (bool reply) { this.quote_grid = new Gtk.Grid (); quote_grid.margin_top = 6; quote_grid.margin_end = 6; quote_grid.get_style_context ().add_class ("quote"); this.quote_name = new TextButton (); quote_name.halign = Gtk.Align.START; quote_name.valign = Gtk.Align.BASELINE; quote_name.margin_start = 12; quote_name.margin_end = 6; quote_name.clicked.connect (quote_name_button_clicked_cb); quote_grid.attach (quote_name, 0, 0, 1, 1); this.quote_screen_name = new Gtk.Label (""); quote_screen_name.halign = Gtk.Align.START; quote_screen_name.valign = Gtk.Align.BASELINE; quote_screen_name.hexpand = true; quote_screen_name.get_style_context ().add_class ("dim-label"); quote_grid.attach (quote_screen_name, 1, 0, 1, 1); if (reply) { this.quote_reply_label = new Gtk.Label (""); quote_reply_label.halign = Gtk.Align.START; quote_reply_label.set_use_markup (true); quote_reply_label.xalign = 0; quote_reply_label.set_margin_start (12); quote_reply_label.set_margin_bottom (4); quote_reply_label.activate_link.connect (quote_link_activated_cb); quote_reply_label.get_style_context ().add_class ("dim-label"); quote_reply_label.get_style_context ().add_class ("invisible-links"); quote_reply_label.set_no_show_all (true); quote_grid.attach (quote_reply_label, 0, 1, 3, 1); } this.quote_label = new Gtk.Label (""); quote_label.halign = Gtk.Align.START; quote_label.hexpand = true; quote_label.xalign = 0; quote_label.use_markup = true; quote_label.wrap = true; quote_label.wrap_mode = Pango.WrapMode.WORD_CHAR; quote_label.track_visited_links = false; quote_label.margin_start = 12; quote_label.activate_link.connect (quote_link_activated_cb); quote_label.populate_popup.connect (populate_popup_cb); var attrs = new Pango.AttrList (); attrs.insert (Pango.attr_style_new (Pango.Style.ITALIC)); quote_label.set_attributes (attrs); if (reply) quote_grid.attach (quote_label, 0, 2, 3, 1); else quote_grid.attach (quote_label, 0, 1, 3, 1); this.quote_time_delta = new Gtk.Label (""); quote_time_delta.halign = Gtk.Align.END; quote_time_delta.get_style_context ().add_class ("dim-label"); quote_grid.attach (quote_time_delta, 2, 0, 1, 1); quote_grid.show_all (); this.grid.attach (quote_grid, 1, 3, 6, 1); } } corebird-1.7.4/src/list/UserFilterEntry.vala000066400000000000000000000047631324604713000210430ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/user-filter-entry.ui")] class UserFilterEntry : Gtk.ListBoxRow, Cb.TwitterItem { [GtkChild] private Gtk.Label name_label; [GtkChild] private Gtk.Label screen_name_label; [GtkChild] private AvatarWidget avatar_image; [GtkChild] private Gtk.Stack stack; [GtkChild] private Gtk.Box delete_box; [GtkChild] private Gtk.Grid grid; [GtkChild] private Gtk.Revealer revealer; public new string name { set { name_label.label = value; } } public string screen_name { set { screen_name_label.label = "@" + value; } } public string avatar_url { set { real_set_avatar (value); } } public bool seen { get { return true; } set {} } public int64 user_id; public signal void deleted (int64 id); public bool muted = false; public bool blocked = false; private GLib.TimeSpan last_timediff; private void real_set_avatar (string avatar_url) { Twitter.get ().get_avatar.begin (user_id, avatar_url, avatar_image, 48 * this.get_scale_factor ()); } public int update_time_delta (GLib.DateTime? now = null) {return 0;} public int64 get_sort_factor () { return 2; } public int64 get_timestamp () { return 0; } public GLib.TimeSpan get_last_set_timediff () { return this.last_timediff; } public void set_last_set_timediff (GLib.TimeSpan span) { this.last_timediff = span; } [GtkCallback] private void menu_button_clicked_cb () { stack.visible_child = delete_box; } [GtkCallback] private void cancel_button_clicked_cb () { stack.visible_child = grid; } [GtkCallback] private void delete_button_clicked_cb () { revealer.reveal_child = false; revealer.notify["child-revealed"].connect (() => { deleted (user_id); }); } } corebird-1.7.4/src/list/UserListEntry.vala000066400000000000000000000120411324604713000205150ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/user-list-entry.ui")] class UserListEntry : Gtk.ListBoxRow, Cb.TwitterItem { [GtkChild] private Gtk.Label name_label; [GtkChild] private Gtk.Label screen_name_label; [GtkChild] private AvatarWidget avatar_image; [GtkChild] private Gtk.Button settings_button; [GtkChild] private Gtk.Button new_window_button; [GtkChild] private Gtk.Button profile_button; public new string name { set { name_label.label = value; } } public string screen_name { owned get { return screen_name_label.label.substring (1); } } public void set_screen_name (string sn) { screen_name_label.label = sn; } public string avatar_url { set { real_set_avatar (value); } } public Cairo.Surface avatar_surface { set { avatar_image.surface = value; } } public bool verified { set { this.avatar_image.verified = value; } } public bool seen { get { return true; } set {} } public bool show_settings { set { settings_button.visible = value; new_window_button.visible = value; profile_button.visible = value; } } public int64 user_id { get; set; } private GLib.TimeSpan last_timediff; public signal void action_clicked (); private unowned Account account; public UserListEntry.from_account (Account acc) { this.screen_name_label.label = "@" + acc.screen_name; this.name = acc.name; this.avatar_surface = acc.avatar; this.account = acc; this.user_id = acc.id; acc.info_changed.connect ((screen_name, name, nop, avatar) => { this.screen_name_label.label = "@" + acc.screen_name; this.name = name; this.avatar_surface = avatar; }); acc.notify["avatar"].connect (() => { this.avatar_surface = this.account.avatar; }); var cb = (Corebird) GLib.Application.get_default (); cb.window_added.connect ((window) => { if (window is MainWindow) { update_window_button_sensitivity (window, false); } }); cb.window_removed.connect ((window) => { if (window is MainWindow) { update_window_button_sensitivity (window, true); } }); cb.account_window_changed.connect ((old_id, new_id) => { if (old_id == this.user_id) new_window_button.sensitive = true; else if (new_id == this.user_id) new_window_button.sensitive = false; }); // Set initial sensitivitiy of new_window_button new_window_button.sensitive = !(cb.is_window_open_for_user_id (acc.id)); } private void real_set_avatar (string avatar_url) { Twitter.get ().get_avatar.begin (user_id, avatar_url, avatar_image, 48 * this.get_scale_factor ()); } public int update_time_delta (GLib.DateTime? now = null) {return 0;} public int64 get_sort_factor () { return int64.MAX - 1; } public int64 get_timestamp () { return 0; } public GLib.TimeSpan get_last_set_timediff () { return this.last_timediff; } public void set_last_set_timediff (GLib.TimeSpan span) { this.last_timediff = span; } private void update_window_button_sensitivity (Gtk.Window window, bool new_value) { if (((MainWindow)window).account.screen_name == this.account.screen_name) { new_window_button.sensitive = new_value; } } [GtkCallback] private void settings_button_clicked_cb () { action_clicked (); var active_window = ((Gtk.Application)GLib.Application.get_default ()).active_window; var dialog = new AccountDialog (this.account); dialog.set_transient_for (active_window); dialog.modal = true; dialog.show (); } [GtkCallback] private void new_window_button_clicked_cb () { var cb = (Corebird) GLib.Application.get_default (); var window = new MainWindow (cb, this.account); cb.add_window (window); window.show_all (); action_clicked (); } [GtkCallback] private void profile_button_clicked_cb () { action_clicked (); var active_window = ((Gtk.Application)GLib.Application.get_default ()).active_window; if (active_window is MainWindow) { var mw = (MainWindow) active_window; var bundle = new Cb.Bundle (); bundle.put_int64 (ProfilePage.KEY_USER_ID, this.user_id); bundle.put_string (ProfilePage.KEY_SCREEN_NAME, this.screen_name); mw.main_widget.switch_page (Page.PROFILE, bundle); } } } corebird-1.7.4/src/main.vala000066400000000000000000000022221324604713000156720ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ int main (string[] args) { #if VIDEO Gst.init (ref args); #endif //no initialisation of static fields :( Settings.init (); var corebird = new Corebird (); int ret = corebird.run (args); #if DEBUG var list = Gtk.Window.list_toplevels (); debug ("Toplevels Left: %u", list.length ()); foreach (var w in list) { debug ("Toplevel: %s", __class_name (w)); w.destroy (); } #endif return ret; } corebird-1.7.4/src/model/000077500000000000000000000000001324604713000152035ustar00rootroot00000000000000corebird-1.7.4/src/model/DMThreadsModel.vala000066400000000000000000000112251324604713000206450ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public class DMThread : GLib.Object { public Cb.UserIdentity user; /* id, name, screen_name */ public int64 last_message_id; public string last_message; public int unread_count = 0; public string? notification_id = null; public Cairo.Surface? avatar_surface = null; public async void load_avatar (Account account, int scale_factor) { assert (this.user.id != 0); if (this.avatar_surface != null) return; this.avatar_surface = yield Twitter.get ().load_avatar_for_user_id (account, user.id, 48 * scale_factor); if (this.avatar_surface != null) Twitter.get ().ref_avatar (this.avatar_surface); } ~DMThread () { if (this.avatar_surface != null) Twitter.get ().unref_avatar (this.avatar_surface); } } /* Let's hope there aren't a lof of threads */ public class DMThreadsModel : GLib.ListModel, GLib.Object { private GLib.GenericArray threads = new GLib.GenericArray (); public GLib.Object? get_item (uint index) { return this.threads.get ((int)index); } public uint get_n_items () { return (uint) this.threads.length; } public GLib.Type get_item_type () { return typeof (DMThread); } public void add (DMThread thread) { bool added = false; for (int i = 0; i < threads.length; i ++) { if (thread.last_message_id > threads.get (i).last_message_id) { this.threads.insert (i, thread); this.items_changed (i, 0, 1); added = true; break; } } if (!added) { this.threads.add (thread); this.items_changed (this.threads.length - 1, 0, 1); } } public void update_last_message (int64 sender_id, int64 message_id, string message_text) { #if DEBUG assert (this.has_thread (sender_id)); #endif int index = 0; for (int i = 0; i < threads.length; i ++) { var thread = threads.get (i); if (thread.user.id == sender_id) { if (message_id > thread.last_message_id) { thread.last_message_id = message_id; thread.last_message = message_text; this.threads.remove (thread); this.items_changed (index, 1, 0); this.add (thread); } else { warning ("id %s is < than %s", message_id.to_string (), thread.last_message_id.to_string ()); } /* Don't call items_changed, the caller DMManager has to call thread_changed */ break; } index ++; } } public bool has_thread (int64 user_id) { for (int i = 0; i < threads.length; i ++) { var thread = threads.get (i); if (thread.user.id == user_id) return true; } return false; } public int reset_unread_count (int64 user_id) { for (int i = 0; i < threads.length; i ++) { var thread = threads.get (i); if (thread.user.id == user_id) { int k = thread.unread_count; thread.unread_count = 0; return k; } } return 0; } public string? reset_notification_id (int64 user_id) { for (int i = 0; i < threads.length; i ++) { var thread = threads.get (i); if (thread.user.id == user_id) { string k = thread.notification_id; thread.notification_id = null; return k; } } return null; } public void increase_unread_count (int64 user_id, int amount = 1) { for (int i = 0; i < threads.length; i ++) { var thread = threads.get (i); if (thread.user.id == user_id) { thread.unread_count += amount; /* Don't call items_changed, the caller DMManager has to call thread_changed */ break; } } } public DMThread? get_thread (int64 user_id) { for (int i = 0; i < threads.length; i ++) { var thread = threads.get (i); if (thread.user.id == user_id) { return thread; } } return null; } } corebird-1.7.4/src/rest/000077500000000000000000000000001324604713000150605ustar00rootroot00000000000000corebird-1.7.4/src/rest/.gitignore000066400000000000000000000023231324604713000170500ustar00rootroot00000000000000*.gir *.typelib *.la *.lo *.o Makefile Makefile.in .deps .libs *.bak *~ *.pc *.trs *.log aclocal.m4 autom4te.cache compile configure config.* depcomp gtk-doc.make gtk-doc.m4 install-sh libtool.m4 ltoptions.m4 ltsugar.m4 ltversion.m4 lt~obsolete.m4 libtool ltmain.sh missing stamp-h1 build/test-driver docs/reference/rest*/*-decl*.txt docs/reference/rest*/*-overrides.txt docs/reference/rest*/*-undeclared.txt docs/reference/rest*/*-undocumented.txt docs/reference/rest*/*-unused.txt docs/reference/rest*/*.args docs/reference/rest*/*.hierarchy docs/reference/rest*/*.interfaces docs/reference/rest*/*.prerequisites docs/reference/rest*/*.signals docs/reference/rest*/*.stamp docs/reference/rest*/html/ docs/reference/rest*/xml/ examples/continuous-twitter examples/dump-xml examples/get-fireeagle-location examples/get-flickr-favorites examples/lastfm-shout examples/post-twitter examples/post-twitter-media examples/test-raw examples/test-xml rest-extras/test-runner rest/rest-enum-types.c rest/rest-enum-types.h rest/stamp-rest-enum-types.h rest/rest-marshal.[ch] rest/test-runner tests/custom-serialize tests/flickr tests/lastfm tests/oauth tests/oauth-async tests/oauth2 tests/proxy tests/proxy-continuous tests/threaded tests/xml corebird-1.7.4/src/rest/AUTHORS000066400000000000000000000001061324604713000161250ustar00rootroot00000000000000Rob Bradford Ross Burton corebird-1.7.4/src/rest/COPYING000066400000000000000000000636371324604713000161320ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, 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 this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser 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 Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey 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 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. This library is distributed in the hope that 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 this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! corebird-1.7.4/src/rest/README000066400000000000000000000045211324604713000157420ustar00rootroot00000000000000librest ======= This library has been designed to make it easier to access web services that claim to be "RESTful". A reasonable definition of what this means can be found on Wikipedia [1]. However a reasonable description is that a RESTful service should have urls that represent remote objects which methods can then be called on. However it should be noted that the majority of services don't actually adhere to this strict definition. Instead their RESTful end-point usually has an API that is just simpler to use compared to other types of APIs they may support (XML-RPC, for instance.). It is this kind of API that this library is attempting to support. It comprises of two parts: the first aims to make it easier to make requests by providing a wrapper around libsoup [2], the second aids with XML parsing by wrapping libxml2 [3]. Making requests ~~~~~~~~~~~~~~~ When a proxy is created for a url you are able to present a format string. This format string is intended to represent the type of path for a remote object, it is then possible to bind this format string with specific names of objects to make this proxy concrete rather than abstract. We abstract the parameters required for a particular call into it's own object which we can then invoke both asynchronously and pseudo-asynchronously (by spinning the main loop whilst waiting for an answer.) This has the advantage of allowing us to support complex behaviour that depends on the parameters, for instance signing a request: a requirement for many web services. Handling the result ~~~~~~~~~~~~~~~~~~~ Standard XML parsers require a significant amount of work to parse a piece of XML. In terms of a SAX parser this involves setting up the functions before hand, in terms of a DOM parser this means dealing with complexity of a DOM tree. The XML parsing components of librest are designed to try and behave a bit like the BeautifulSoup parser [4]. It does this by parsing the XML into an easily walkable tree were nodes have children for their descendents and siblings for the nodes of the same type that share the same parent. This makes it easy for instance to get a list of all the "photo" nodes in a document from the root. [1] - http://en.wikipedia.org/wiki/Representational_State_Transfer [2] - http://live.gnome.org/LibSoup [3] - http://xmlsoft.org/ [4] - http://www.crummy.com/software/BeautifulSoup/ corebird-1.7.4/src/rest/rest/000077500000000000000000000000001324604713000160355ustar00rootroot00000000000000corebird-1.7.4/src/rest/rest/oauth-proxy-call.c000066400000000000000000000233711324604713000214170ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2008, 2009, Intel Corporation. * * Authors: Rob Bradford * Ross Burton * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #include #include #include "rest-proxy-call.h" #include "oauth-proxy-call.h" #include "oauth-proxy-private.h" #include "rest-proxy-call-private.h" #include "sha1.h" G_DEFINE_TYPE (OAuthProxyCall, oauth_proxy_call, REST_TYPE_PROXY_CALL) #define OAUTH_ENCODE_STRING(x_) (x_ ? soup_uri_encode( (x_), "!$&'()*+,;=@") : g_strdup ("")) static char * sign_plaintext (OAuthProxyPrivate *priv) { char *cs; char *ts; char *rv; cs = OAUTH_ENCODE_STRING (priv->consumer_secret); ts = OAUTH_ENCODE_STRING (priv->token_secret); rv = g_strconcat (cs, "&", ts, NULL); g_free (cs); g_free (ts); return rv; } static char * encode_params (GHashTable *hash) { GList *l, *keys; GString *s; s = g_string_new (NULL); keys = g_hash_table_get_keys (hash); keys = g_list_sort (keys, (GCompareFunc)strcmp); for (l = keys; l; l = l->next) { const char *key; const char *value; char *k, *v; key = l->data; value = g_hash_table_lookup (hash, key); k = OAUTH_ENCODE_STRING (key); v = OAUTH_ENCODE_STRING (value); if (s->len) g_string_append (s, "&"); g_string_append_printf (s, "%s=%s", k, v); g_free (k); g_free (v); } g_list_free (keys); return g_string_free (s, FALSE); } /* * Add the keys in @from to @hash. */ static void merge_hashes (GHashTable *hash, GHashTable *from) { GHashTableIter iter; gpointer key, value; g_hash_table_iter_init (&iter, from); while (g_hash_table_iter_next (&iter, &key, &value)) { g_hash_table_insert (hash, key, value); } } static void merge_params (GHashTable *hash, RestParams *params) { RestParamsIter iter; const char *name; RestParam *param; rest_params_iter_init (&iter, params); while (rest_params_iter_next (&iter, &name, ¶m)) { if (rest_param_is_string (param)) g_hash_table_insert (hash, (gpointer)name, (gpointer)rest_param_get_content (param)); } } static char * sign_hmac (OAuthProxy *proxy, RestProxyCall *call, GHashTable *oauth_params) { OAuthProxyPrivate *priv; const char *url_str; char *key, *signature, *ep, *eep; const char *content_type; GString *text; GHashTable *all_params; RestParamsIter params_iter; RestParam *param; gboolean encode_query_params = TRUE; priv = PROXY_GET_PRIVATE (proxy); url_str = rest_proxy_call_get_url (call); text = g_string_new (NULL); g_string_append (text, rest_proxy_call_get_method (call)); g_string_append_c (text, '&'); if (priv->oauth_echo) { g_string_append_uri_escaped (text, priv->service_url, NULL, FALSE); } else if (priv->signature_host != NULL) { SoupURI *url = soup_uri_new (url_str); gchar *signing_url; soup_uri_set_host (url, priv->signature_host); signing_url = soup_uri_to_string (url, FALSE); g_string_append_uri_escaped (text, signing_url, NULL, FALSE); soup_uri_free (url); g_free (signing_url); } else { g_string_append_uri_escaped (text, url_str, NULL, FALSE); } g_string_append_c (text, '&'); /* If one of the call's parameters is a multipart/form-data parameter, the signature base string must be generated with only the oauth parameters */ rest_params_iter_init(¶ms_iter, rest_proxy_call_get_params (call)); while(rest_params_iter_next(¶ms_iter, (gpointer)&key, (gpointer)¶m)) { content_type = rest_param_get_content_type(param); if (strcmp(content_type, "multipart/form-data") == 0){ encode_query_params = FALSE; break; } } /* Merge the OAuth parameters with the query parameters */ all_params = g_hash_table_new (g_str_hash, g_str_equal); merge_hashes (all_params, oauth_params); if (encode_query_params && !priv->oauth_echo) { merge_params (all_params, rest_proxy_call_get_params (call)); } ep = encode_params (all_params); eep = OAUTH_ENCODE_STRING (ep); g_string_append (text, eep); g_free (ep); g_free (eep); g_hash_table_destroy (all_params); /* PLAINTEXT signature value is the HMAC-SHA1 key value */ key = sign_plaintext (priv); signature = hmac_sha1 (key, text->str); g_free (key); g_string_free (text, TRUE); return signature; } /* * From the OAuth parameters in @params, construct a HTTP Authorized header. */ static char * make_authorized_header (GHashTable *oauth_params) { GString *auth; GHashTableIter iter; const char *key, *value; g_assert (oauth_params); /* TODO: is "" okay for the realm, or should this be magically calculated or a parameter? */ auth = g_string_new ("OAuth realm=\"\""); g_hash_table_iter_init (&iter, oauth_params); while (g_hash_table_iter_next (&iter, (gpointer)&key, (gpointer)&value)) { gchar *encoded_value = OAUTH_ENCODE_STRING (value); g_string_append_printf (auth, ", %s=\"%s\"", key, encoded_value); g_free (encoded_value); } return g_string_free (auth, FALSE); } /* * Remove any OAuth parameters from the @call parameters and add them to * @oauth_params for building an Authorized header with. */ static void steal_oauth_params (RestProxyCall *call, GHashTable *oauth_params) { RestParams *params; RestParamsIter iter; const char *name; RestParam *param; GList *to_remove = NULL; params = rest_proxy_call_get_params (call); rest_params_iter_init (&iter, params); while (rest_params_iter_next (&iter, &name, ¶m)) { if (rest_param_is_string (param) && g_str_has_prefix (name, "oauth_")) { g_hash_table_insert (oauth_params, g_strdup (name), g_strdup (rest_param_get_content (param))); to_remove = g_list_prepend (to_remove, g_strdup (name)); } } while (to_remove) { rest_params_remove (params, to_remove->data); g_free (to_remove->data); to_remove = g_list_delete_link (to_remove, to_remove); } } static gboolean _prepare (RestProxyCall *call, GError **error) { OAuthProxy *proxy = NULL; OAuthProxyPrivate *priv; char *s; GHashTable *oauth_params; g_object_get (call, "proxy", &proxy, NULL); priv = PROXY_GET_PRIVATE (proxy); /* We have to make this hash free the strings and thus duplicate when we put * them in since when we call call steal_oauth_params that has to duplicate * the param names since it removes them from the main hash */ oauth_params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); /* First, steal any OAuth properties in the regular params */ steal_oauth_params (call, oauth_params); g_hash_table_insert (oauth_params, g_strdup ("oauth_version"), g_strdup ("1.0")); s = g_strdup_printf ("%"G_GINT64_FORMAT , (gint64) time (NULL)); g_hash_table_insert (oauth_params, g_strdup ("oauth_timestamp"), s); s = g_strdup_printf ("%u", g_random_int ()); g_hash_table_insert (oauth_params, g_strdup ("oauth_nonce"), s); g_hash_table_insert (oauth_params, g_strdup ("oauth_consumer_key"), g_strdup (priv->consumer_key)); if (priv->token) g_hash_table_insert (oauth_params, g_strdup ("oauth_token"), g_strdup (priv->token)); switch (priv->method) { case PLAINTEXT: g_hash_table_insert (oauth_params, g_strdup ("oauth_signature_method"), g_strdup ("PLAINTEXT")); s = sign_plaintext (priv); break; case HMAC_SHA1: g_hash_table_insert (oauth_params, g_strdup ("oauth_signature_method"), g_strdup ("HMAC-SHA1")); s = sign_hmac (proxy, call, oauth_params); break; } g_hash_table_insert (oauth_params, g_strdup ("oauth_signature"), s); s = make_authorized_header (oauth_params); if (priv->oauth_echo) { rest_proxy_call_take_header (call, "X-Verify-Credentials-Authorization", g_steal_pointer (&s)); rest_proxy_call_add_header (call, "X-Auth-Service-Provider", priv->service_url); } else { rest_proxy_call_take_header (call, "Authorization", g_steal_pointer (&s)); } g_hash_table_destroy (oauth_params); g_object_unref (proxy); return TRUE; } static void oauth_proxy_call_class_init (OAuthProxyCallClass *klass) { RestProxyCallClass *call_class = REST_PROXY_CALL_CLASS (klass); call_class->prepare = _prepare; } static void oauth_proxy_call_init (OAuthProxyCall *self) { } void oauth_proxy_call_parse_token_response (OAuthProxyCall *call) { OAuthProxyPrivate *priv; GHashTable *form; OAuthProxy *proxy; /* TODO: sanity checks, error handling, probably return gboolean */ g_return_if_fail (OAUTH_IS_PROXY_CALL (call)); g_object_get (call, "proxy", &proxy, NULL); priv = PROXY_GET_PRIVATE (proxy); g_object_unref (proxy); g_assert (priv); form = soup_form_decode (rest_proxy_call_get_payload (REST_PROXY_CALL (call))); g_free (priv->token); g_free (priv->token_secret); priv->token = g_strdup (g_hash_table_lookup (form, "oauth_token")); priv->token_secret = g_strdup (g_hash_table_lookup (form, "oauth_token_secret")); /* This header should only exist for request_token replies, but its easier just to always check it */ priv->oauth_10a = g_hash_table_lookup (form, "oauth_callback_confirmed") != NULL; g_hash_table_destroy (form); } corebird-1.7.4/src/rest/rest/oauth-proxy-call.h000066400000000000000000000036711324604713000214250ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2008, 2009, Intel Corporation. * * Authors: Rob Bradford * Ross Burton * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #ifndef _OAUTH_PROXY_CALL #define _OAUTH_PROXY_CALL #include "rest-proxy-call.h" G_BEGIN_DECLS #define OAUTH_TYPE_PROXY_CALL oauth_proxy_call_get_type() #define OAUTH_PROXY_CALL(obj) \ (G_TYPE_CHECK_INSTANCE_CAST ((obj), OAUTH_TYPE_PROXY_CALL, OAuthProxyCall)) #define OAUTH_PROXY_CALL_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST ((klass), OAUTH_TYPE_PROXY_CALL, OAuthProxyCallClass)) #define OAUTH_IS_PROXY_CALL(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), OAUTH_TYPE_PROXY_CALL)) #define OAUTH_IS_PROXY_CALL_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE ((klass), OAUTH_TYPE_PROXY_CALL)) #define OAUTH_PROXY_CALL_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), OAUTH_TYPE_PROXY_CALL, OAuthProxyCallClass)) /** * OAuthProxyCall: * * #OAuthProxyCall has no publicly available members. */ typedef struct { RestProxyCall parent; } OAuthProxyCall; typedef struct { RestProxyCallClass parent_class; } OAuthProxyCallClass; GType oauth_proxy_call_get_type (void); void oauth_proxy_call_parse_token_response (OAuthProxyCall *call); G_END_DECLS #endif /* _OAUTH_PROXY_CALL */ corebird-1.7.4/src/rest/rest/oauth-proxy-private.h000066400000000000000000000026501324604713000221600ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2008, 2009, Intel Corporation. * * Authors: Rob Bradford * Ross Burton * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #include "oauth-proxy.h" #define PROXY_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), OAUTH_TYPE_PROXY, OAuthProxyPrivate)) typedef struct { /* Application "consumer" keys */ char *consumer_key; char *consumer_secret; /* Authorisation "user" tokens */ char *token; char *token_secret; /* How we're signing */ OAuthSignatureMethod method; /* OAuth 1.0a */ gboolean oauth_10a; char *verifier; /* OAuth Echo */ gboolean oauth_echo; char *service_url; /* URL to use for signatures */ char *signature_host; } OAuthProxyPrivate; corebird-1.7.4/src/rest/rest/oauth-proxy.c000066400000000000000000000472121324604713000205060ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2008, 2009, Intel Corporation. * * Authors: Rob Bradford * Ross Burton * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #include "rest-proxy.h" #include #include "oauth-proxy.h" #include "oauth-proxy-private.h" #include "oauth-proxy-call.h" G_DEFINE_TYPE (OAuthProxy, oauth_proxy, REST_TYPE_PROXY) enum { PROP_0, PROP_CONSUMER_KEY, PROP_CONSUMER_SECRET, PROP_TOKEN, PROP_TOKEN_SECRET, PROP_SIGNATURE_HOST, PROP_SIGNATURE_METHOD, }; static RestProxyCall * _new_call (RestProxy *proxy) { RestProxyCall *call; call = g_object_new (OAUTH_TYPE_PROXY_CALL, "proxy", proxy, NULL); return call; } static void oauth_proxy_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { OAuthProxyPrivate *priv = PROXY_GET_PRIVATE (object); switch (property_id) { case PROP_CONSUMER_KEY: g_value_set_string (value, priv->consumer_key); break; case PROP_CONSUMER_SECRET: g_value_set_string (value, priv->consumer_secret); break; case PROP_TOKEN: g_value_set_string (value, priv->token); break; case PROP_TOKEN_SECRET: g_value_set_string (value, priv->token_secret); break; case PROP_SIGNATURE_HOST: g_value_set_string (value, priv->signature_host); break; case PROP_SIGNATURE_METHOD: g_value_set_enum (value, priv->method); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void oauth_proxy_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { OAuthProxyPrivate *priv = PROXY_GET_PRIVATE (object); switch (property_id) { case PROP_CONSUMER_KEY: if (priv->consumer_key) g_free (priv->consumer_key); priv->consumer_key = g_value_dup_string (value); break; case PROP_CONSUMER_SECRET: if (priv->consumer_secret) g_free (priv->consumer_secret); priv->consumer_secret = g_value_dup_string (value); break; case PROP_TOKEN: if (priv->token) g_free (priv->token); priv->token = g_value_dup_string (value); break; case PROP_TOKEN_SECRET: if (priv->token_secret) g_free (priv->token_secret); priv->token_secret = g_value_dup_string (value); break; case PROP_SIGNATURE_HOST: if (priv->signature_host) g_free (priv->signature_host); priv->signature_host = g_value_dup_string (value); break; case PROP_SIGNATURE_METHOD: priv->method = g_value_get_enum (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void oauth_proxy_finalize (GObject *object) { OAuthProxyPrivate *priv = PROXY_GET_PRIVATE (object); g_free (priv->consumer_key); g_free (priv->consumer_secret); g_free (priv->token); g_free (priv->token_secret); g_free (priv->verifier); g_free (priv->service_url); G_OBJECT_CLASS (oauth_proxy_parent_class)->finalize (object); } #ifndef G_PARAM_STATIC_STRINGS #define G_PARAM_STATIC_STRINGS (G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB) #endif static void oauth_proxy_class_init (OAuthProxyClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); RestProxyClass *proxy_class = REST_PROXY_CLASS (klass); GParamSpec *pspec; g_type_class_add_private (klass, sizeof (OAuthProxyPrivate)); object_class->get_property = oauth_proxy_get_property; object_class->set_property = oauth_proxy_set_property; object_class->finalize = oauth_proxy_finalize; proxy_class->new_call = _new_call; pspec = g_param_spec_string ("consumer-key", "consumer-key", "The consumer key", NULL, G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY|G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_CONSUMER_KEY, pspec); pspec = g_param_spec_string ("consumer-secret", "consumer-secret", "The consumer secret", NULL, G_PARAM_READWRITE|G_PARAM_CONSTRUCT_ONLY|G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_CONSUMER_SECRET, pspec); pspec = g_param_spec_string ("token", "token", "The request or access token", NULL, G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_TOKEN, pspec); pspec = g_param_spec_string ("token-secret", "token-secret", "The request or access token secret", NULL, G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_TOKEN_SECRET, pspec); pspec = g_param_spec_string ("signature-host", "signature-host", "The base URL used in the signature string", NULL, G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_SIGNATURE_HOST, pspec); pspec = g_param_spec_enum ("signature-method", "signature-method", "Signature method used", OAUTH_TYPE_SIGNATURE_METHOD, HMAC_SHA1, G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_SIGNATURE_METHOD, pspec); } static void oauth_proxy_init (OAuthProxy *self) { PROXY_GET_PRIVATE (self)->method = HMAC_SHA1; } /** * oauth_proxy_new: * @consumer_key: the Consumer Key * @consumer_secret: the Consumer Secret * @url_format: the endpoint URL * @binding_required: whether the URL needs to be bound before calling * * Create a new #OAuthProxy for the specified endpoint @url_format, using the * specified API key and secret. * * This proxy won't have the Token or Token Secret set so as such will be * unauthorised. If the tokens are unknown then oauth_proxy_request_token() and * oauth_proxy_access_token() should be called to do the OAuth authorisation, or * the tokens should be set using oauth_proxy_set_token() and * oauth_proxy_set_token_secret(). * * Set @binding_required to %TRUE if the URL contains string formatting * operations (for example "http://foo.com/%s". These must be expanded * using rest_proxy_bind() before invoking the proxy. * * Returns: A new #OAuthProxy. */ RestProxy * oauth_proxy_new (const char *consumer_key, const char *consumer_secret, const gchar *url_format, gboolean binding_required) { return g_object_new (OAUTH_TYPE_PROXY, "consumer-key", consumer_key, "consumer-secret", consumer_secret, "url-format", url_format, "binding-required", binding_required, NULL); } /** * oauth_proxy_new_with_token: * @consumer_key: the Consumer Key * @consumer_secret: the Consumer Secret * @token: the Access Token * @token_secret: the Token Secret * @url_format: the endpoint URL * @binding_required: whether the URL needs to be bound before calling * * Create a new #OAuthProxy for the specified endpoint @url_format, using the * specified API key and secret. * * @token and @token_secret are used for the Access Token and Token Secret, so * if they are still valid then this proxy is authorised. * * Set @binding_required to %TRUE if the URL contains string formatting * operations (for example "http://foo.com/%s". These must be expanded * using rest_proxy_bind() before invoking the proxy. * * Returns: A new #OAuthProxy. */ RestProxy * oauth_proxy_new_with_token (const char *consumer_key, const char *consumer_secret, const char *token, const char *token_secret, const gchar *url_format, gboolean binding_required) { return g_object_new (OAUTH_TYPE_PROXY, "consumer-key", consumer_key, "consumer-secret", consumer_secret, "token", token, "token-secret", token_secret, "url-format", url_format, "binding-required", binding_required, NULL); } static void request_token_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GTask *task = G_TASK (user_data); RestProxyCall *call = REST_PROXY_CALL (source_object); GError *error = NULL; gboolean call_status; call_status = rest_proxy_call_invoke_finish (call, result, &error); if (error != NULL) { g_task_return_error (task, error); } else { oauth_proxy_call_parse_token_response (OAUTH_PROXY_CALL (call)); g_task_return_boolean (task, call_status); } g_object_unref (task); } /** * oauth_proxy_request_token_async: * @proxy: an #OAuthProxy * @function: (nullable): the function name to invoke * @callback_uri: (nullable): the callback URI * @callback: (scope async): a #OAuthProxyAuthCallback to invoke on completion * @user_data: user data to pass to @callback * * Perform the Request Token phase of OAuth, invoking @function (defaulting to * "request_token" if @function is NULL). * * The value of @callback depends on whether you wish to use OAuth 1.0 or 1.0a. * If you wish to use 1.0 then callback must be NULL. To use 1.0a then * @callback should either be your callback URI, or "oob" (out-of-band). * * This method will return once the method has been queued, @callback will be * invoked when it has completed. */ void oauth_proxy_request_token_async (OAuthProxy *proxy, const char *function, const char *callback_uri, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { RestProxyCall *call; GTask *task; call = rest_proxy_new_call (REST_PROXY (proxy)); rest_proxy_call_set_function (call, function ? function : "request_token"); rest_proxy_call_set_method (call, "POST"); if (callback_uri) rest_proxy_call_add_param (call, "oauth_callback", callback_uri); task = g_task_new (proxy, cancellable, callback, user_data); rest_proxy_call_invoke_async (call, cancellable, request_token_cb, task); g_object_unref (call); } /** * oauth_proxy_request_token_finish: * @proxy: a #OAuthProxy * @result: a #GAsyncResult * @error: a #GError or %NULL * * Finishes an operation started with oauth_proxy_request_token_async() * * Returns: %TRUE on success, %FALSE if an error occurred, in which case * @error will be set. */ gboolean oauth_proxy_request_token_finish (OAuthProxy *proxy, GAsyncResult *result, GError **error) { g_return_val_if_fail (OAUTH_IS_PROXY (proxy), FALSE); g_return_val_if_fail (g_task_is_valid (result, proxy), FALSE); return g_task_propagate_boolean (G_TASK (result), error); } static void access_token_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GTask *task = G_TASK (user_data); RestProxyCall *call = REST_PROXY_CALL (source_object); GError *error = NULL; gboolean call_status; call_status = rest_proxy_call_invoke_finish (call, result, &error); if (error != NULL) { g_task_return_error (task, error); } else { oauth_proxy_call_parse_token_response (OAUTH_PROXY_CALL (call)); g_task_return_boolean (task, call_status); } g_object_unref (task); } /** * oauth_proxy_access_token_async: * @proxy: an #OAuthProxy * @function: the function name to invoke * @verifier: the verifier * @callback: (scope async): a #OAuthProxyAuthCallback to invoke on completion * @user_data: user data to pass to @callback * * Perform the Access Token phase of OAuth, invoking @function (defaulting to * "access_token" if @function is NULL). * * @verifier is only used if you are using OAuth 1.0a. This is either the * "oauth_verifier" parameter that was passed to your callback URI, or a string * that the user enters in some other manner (for example in a popup dialog) if * "oob" was passed to oauth_proxy_request_token(). For OAuth 1.0, pass %NULL. * * This method will return once the method has been queued, @callback will be * invoked when it has completed. */ void oauth_proxy_access_token_async (OAuthProxy *proxy, const char *function, const char *verifier, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { RestProxyCall *call; GTask *task; call = rest_proxy_new_call (REST_PROXY (proxy)); rest_proxy_call_set_function (call, function ? function : "access_token"); rest_proxy_call_set_method (call, "POST"); if (verifier) rest_proxy_call_add_param (call, "oauth_verifier", verifier); task = g_task_new (proxy, cancellable, callback, user_data); rest_proxy_call_invoke_async (call, cancellable, access_token_cb, task); g_object_unref (call); } gboolean oauth_proxy_access_token_finish (OAuthProxy *proxy, GAsyncResult *result, GError **error) { g_return_val_if_fail (OAUTH_IS_PROXY (proxy), FALSE); g_return_val_if_fail (g_task_is_valid (result, proxy), FALSE); return g_task_propagate_boolean (G_TASK (result), error); } /** * oauth_proxy_get_token: * @proxy: an #OAuthProxy * * Get the current request or access token. * * Returns: the token, or %NULL if there is no token yet. This string is owned * by #OAuthProxy and should not be freed. */ const char * oauth_proxy_get_token (OAuthProxy *proxy) { OAuthProxyPrivate *priv = PROXY_GET_PRIVATE (proxy); return priv->token; } /** * oauth_proxy_set_token: * @proxy: an #OAuthProxy * @token: the access token * * Set the access token. */ void oauth_proxy_set_token (OAuthProxy *proxy, const char *token) { OAuthProxyPrivate *priv; g_return_if_fail (OAUTH_IS_PROXY (proxy)); priv = PROXY_GET_PRIVATE (proxy); g_free (priv->token); priv->token = g_strdup (token); } /** * oauth_proxy_get_token_secret: * @proxy: an #OAuthProxy * * Get the current request or access token secret. * * Returns: the token secret, or %NULL if there is no token secret yet. This * string is owned by #OAuthProxy and should not be freed. */ const char * oauth_proxy_get_token_secret (OAuthProxy *proxy) { OAuthProxyPrivate *priv = PROXY_GET_PRIVATE (proxy); return priv->token_secret; } /** * oauth_proxy_set_token_secret: * @proxy: an #OAuthProxy * @token_secret: the access token secret * * Set the access token secret. */ void oauth_proxy_set_token_secret (OAuthProxy *proxy, const char *token_secret) { OAuthProxyPrivate *priv; g_return_if_fail (OAUTH_IS_PROXY (proxy)); priv = PROXY_GET_PRIVATE (proxy); if (priv->token_secret) g_free (priv->token_secret); priv->token_secret = g_strdup (token_secret); } /** * oauth_proxy_is_oauth10a: * @proxy: a valid #OAuthProxy * * Determines if the server supports OAuth 1.0a with this proxy. This is only * valid after oauth_proxy_request_token() or oauth_proxy_request_token_async() * has been called. * * Returns: %TRUE if the server supports OAuth 1.0a, %FALSE otherwise. */ gboolean oauth_proxy_is_oauth10a (OAuthProxy *proxy) { g_return_val_if_fail (OAUTH_IS_PROXY (proxy), FALSE); return PROXY_GET_PRIVATE (proxy)->oauth_10a; } /** * oauth_proxy_get_signature_host: * @proxy: an #OAuthProxy * * Get the signature hostname used when creating a signature base string. * * Returns: the signature hostname, or %NULL if there is none set. * This string is owned by #OAuthProxy and should not be freed. */ const char * oauth_proxy_get_signature_host (OAuthProxy *proxy) { OAuthProxyPrivate *priv; g_return_val_if_fail (OAUTH_IS_PROXY (proxy), NULL); priv = PROXY_GET_PRIVATE (proxy); return priv->signature_host; } /** * oauth_proxy_set_signature_host: * @proxy: an #OAuthProxy * @signature_host: the signature host * * Set the signature hostname used when creating a signature base string. */ void oauth_proxy_set_signature_host (OAuthProxy *proxy, const char *signature_host) { OAuthProxyPrivate *priv; g_return_if_fail (OAUTH_IS_PROXY (proxy)); priv = PROXY_GET_PRIVATE (proxy); g_free (priv->signature_host); priv->signature_host = g_strdup (signature_host); } /** * oauth_proxy_new_echo_proxy: * @proxy: an #OAuthProxy * @service_url: the service URL * @url_format: the URL format * @binding_required: whether a binding is required * * Create a new OAuth * Echo proxy. * * Returns: (transfer full): a new OAuth Echo proxy */ RestProxy * oauth_proxy_new_echo_proxy (OAuthProxy *proxy, /* TODO: should this be a function on the base url? */ const char *service_url, const gchar *url_format, gboolean binding_required) { OAuthProxy *echo_proxy; OAuthProxyPrivate *priv, *echo_priv; g_return_val_if_fail (OAUTH_IS_PROXY (proxy), NULL); g_return_val_if_fail (service_url, NULL); g_return_val_if_fail (url_format, NULL); priv = PROXY_GET_PRIVATE (proxy); echo_proxy = g_object_new (OAUTH_TYPE_PROXY, "url-format", url_format, "binding-required", binding_required, "consumer-key", priv->consumer_key, "consumer-secret", priv->consumer_secret, "token", priv->token, "token-secret", priv->token_secret, NULL); echo_priv = PROXY_GET_PRIVATE (echo_proxy); echo_priv->oauth_echo = TRUE; echo_priv->service_url = g_strdup (service_url); return (RestProxy *)echo_proxy; } GType oauth_signature_method_get_type (void) { static GType enum_type_id = 0; if (G_UNLIKELY (!enum_type_id)) { static const GEnumValue values[] = { { PLAINTEXT, "PLAINTEXT", "plaintext" }, { HMAC_SHA1, "HMAC_SHA1", "hmac-sha1" }, { 0, NULL, NULL } }; enum_type_id = g_enum_register_static ("OAuthSignatureMethod", values); } return enum_type_id; } corebird-1.7.4/src/rest/rest/oauth-proxy.h000066400000000000000000000114151324604713000205070ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2008, 2009, Intel Corporation. * * Authors: Rob Bradford * Ross Burton * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #ifndef _OAUTH_PROXY #define _OAUTH_PROXY #include "rest-proxy.h" G_BEGIN_DECLS #define OAUTH_TYPE_PROXY oauth_proxy_get_type() #define OAUTH_PROXY(obj) \ (G_TYPE_CHECK_INSTANCE_CAST ((obj), OAUTH_TYPE_PROXY, OAuthProxy)) #define OAUTH_PROXY_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST ((klass), OAUTH_TYPE_PROXY, OAuthProxyClass)) #define OAUTH_IS_PROXY(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), OAUTH_TYPE_PROXY)) #define OAUTH_IS_PROXY_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE ((klass), OAUTH_TYPE_PROXY)) #define OAUTH_PROXY_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), OAUTH_TYPE_PROXY, OAuthProxyClass)) /** * OAuthProxy: * * #OAuthProxy has no publicly available members. */ typedef struct { RestProxy parent; } OAuthProxy; typedef struct { RestProxyClass parent_class; } OAuthProxyClass; GType oauth_signature_method_get_type (void) G_GNUC_CONST; #define OAUTH_TYPE_SIGNATURE_METHOD (oauth_signature_method_get_type()) /** * OAuthSignatureMethod: * @PLAINTEXT: plain text signatures (not recommended) * @HMAC_SHA1: HMAC-SHA1 signatures (recommended) * * The signature method to use when signing method calls. @PLAINTEXT is only * recommended for testing, in general @HMAC_SHA1 is well supported and more * secure. */ typedef enum { PLAINTEXT, HMAC_SHA1 } OAuthSignatureMethod; GType oauth_proxy_get_type (void); RestProxy* oauth_proxy_new (const char *consumer_key, const char *consumer_secret, const gchar *url_format, gboolean binding_required); RestProxy* oauth_proxy_new_with_token (const char *consumer_key, const char *consumer_secret, const char *token, const char *token_secret, const gchar *url_format, gboolean binding_required); void oauth_proxy_request_token_async (OAuthProxy *proxy, const char *function, const char *callback_uri, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean oauth_proxy_request_token_finish (OAuthProxy *proxy, GAsyncResult *result, GError **error); gboolean oauth_proxy_is_oauth10a (OAuthProxy *proxy); void oauth_proxy_access_token_async (OAuthProxy *proxy, const char *function, const char *verifier, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean oauth_proxy_access_token_finish (OAuthProxy *proxy, GAsyncResult *result, GError **error); const char * oauth_proxy_get_token (OAuthProxy *proxy); void oauth_proxy_set_token (OAuthProxy *proxy, const char *token); const char * oauth_proxy_get_token_secret (OAuthProxy *proxy); void oauth_proxy_set_token_secret (OAuthProxy *proxy, const char *token_secret); const char * oauth_proxy_get_signature_host (OAuthProxy *proxy); void oauth_proxy_set_signature_host (OAuthProxy *proxy, const char *signature_host); RestProxy *oauth_proxy_new_echo_proxy (OAuthProxy *proxy, const char *service_url, const gchar *url_format, gboolean binding_required); G_END_DECLS #endif /* _OAUTH_PROXY */ corebird-1.7.4/src/rest/rest/rest-main.c000066400000000000000000000026331324604713000201040ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2008, 2009, Intel Corporation. * * Authors: Rob Bradford * Ross Burton * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #include "rest-private.h" guint rest_debug_flags = 0; /* * "Private" function used to set debugging flags based on environment * variables. Called upon entry into all public functions. */ void _rest_setup_debugging (void) { static gboolean setup_done = FALSE; static const GDebugKey keys[] = { { "proxy", REST_DEBUG_PROXY } }; if (G_LIKELY (setup_done)) return; rest_debug_flags = g_parse_debug_string (g_getenv ("REST_DEBUG"), keys, G_N_ELEMENTS (keys)); setup_done = TRUE; } corebird-1.7.4/src/rest/rest/rest-marshal.c000066400000000000000000000104601324604713000206040ustar00rootroot00000000000000#include "rest-marshal.h" #include #ifdef G_ENABLE_DEBUG #define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) #define g_marshal_value_peek_char(v) g_value_get_schar (v) #define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) #define g_marshal_value_peek_int(v) g_value_get_int (v) #define g_marshal_value_peek_uint(v) g_value_get_uint (v) #define g_marshal_value_peek_long(v) g_value_get_long (v) #define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) #define g_marshal_value_peek_int64(v) g_value_get_int64 (v) #define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) #define g_marshal_value_peek_enum(v) g_value_get_enum (v) #define g_marshal_value_peek_flags(v) g_value_get_flags (v) #define g_marshal_value_peek_float(v) g_value_get_float (v) #define g_marshal_value_peek_double(v) g_value_get_double (v) #define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) #define g_marshal_value_peek_param(v) g_value_get_param (v) #define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) #define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) #define g_marshal_value_peek_object(v) g_value_get_object (v) #define g_marshal_value_peek_variant(v) g_value_get_variant (v) #else /* !G_ENABLE_DEBUG */ /* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. * Do not access GValues directly in your code. Instead, use the * g_value_get_*() functions */ #define g_marshal_value_peek_boolean(v) (v)->data[0].v_int #define g_marshal_value_peek_char(v) (v)->data[0].v_int #define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint #define g_marshal_value_peek_int(v) (v)->data[0].v_int #define g_marshal_value_peek_uint(v) (v)->data[0].v_uint #define g_marshal_value_peek_long(v) (v)->data[0].v_long #define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong #define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 #define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 #define g_marshal_value_peek_enum(v) (v)->data[0].v_long #define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong #define g_marshal_value_peek_float(v) (v)->data[0].v_float #define g_marshal_value_peek_double(v) (v)->data[0].v_double #define g_marshal_value_peek_string(v) (v)->data[0].v_pointer #define g_marshal_value_peek_param(v) (v)->data[0].v_pointer #define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer #define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer #define g_marshal_value_peek_object(v) (v)->data[0].v_pointer #define g_marshal_value_peek_variant(v) (v)->data[0].v_pointer #endif /* !G_ENABLE_DEBUG */ /* BOOLEAN:OBJECT,BOOLEAN (rest-marshal.txt:1) */ void g_cclosure_user_marshal_BOOLEAN__OBJECT_BOOLEAN (GClosure *closure, GValue *return_value G_GNUC_UNUSED, guint n_param_values, const GValue *param_values, gpointer invocation_hint G_GNUC_UNUSED, gpointer marshal_data) { typedef gboolean (*GMarshalFunc_BOOLEAN__OBJECT_BOOLEAN) (gpointer data1, gpointer arg_1, gboolean arg_2, gpointer data2); GMarshalFunc_BOOLEAN__OBJECT_BOOLEAN callback; GCClosure *cc = (GCClosure*) closure; gpointer data1, data2; gboolean v_return; g_return_if_fail (return_value != NULL); g_return_if_fail (n_param_values == 3); if (G_CCLOSURE_SWAP_DATA (closure)) { data1 = closure->data; data2 = g_value_peek_pointer (param_values + 0); } else { data1 = g_value_peek_pointer (param_values + 0); data2 = closure->data; } callback = (GMarshalFunc_BOOLEAN__OBJECT_BOOLEAN) (marshal_data ? marshal_data : cc->callback); v_return = callback (data1, g_marshal_value_peek_object (param_values + 1), g_marshal_value_peek_boolean (param_values + 2), data2); g_value_set_boolean (return_value, v_return); } corebird-1.7.4/src/rest/rest/rest-marshal.txt000066400000000000000000000000271324604713000211770ustar00rootroot00000000000000BOOLEAN:OBJECT,BOOLEAN corebird-1.7.4/src/rest/rest/rest-param.c000066400000000000000000000221371324604713000202610ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2010 Intel Corporation. * * Authors: Ross Burton * Rob Bradford * * RestParam is inspired by libsoup's SoupBuffer * Copyright (C) 2000-2030 Ximian, Inc * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #include #include #include "rest-param.h" /** * SECTION:rest-param * @short_description: Name/value parameter data type with intelligent memory * management * @see_also: #RestParams, #RestProxyCall. */ /* Internal RestMemoryUse values */ enum { REST_MEMORY_OWNED = REST_MEMORY_COPY + 1 }; struct _RestParam { char *name; RestMemoryUse use; gconstpointer data; gsize length; const char *content_type; char *filename; volatile gint ref_count; gpointer owner; GDestroyNotify owner_dnotify; }; G_DEFINE_BOXED_TYPE (RestParam, rest_param, rest_param_ref, rest_param_unref) /** * rest_param_new_full: * @name: the parameter name * @use: the #RestMemoryUse describing how the memory can be used * @data: (array length=length) (element-type guint8): a pointer to * the start of the data * @length: the length of the data * @content_type: the content type of the data * @filename: (nullable): the original filename, or %NULL * * Create a new #RestParam called @name with @length bytes of @data as the * value. @content_type is the type of the data as a MIME type, for example * "text/plain" for simple string parameters. * * If the parameter is a file upload it can be passed as @filename. * * Returns: a new #RestParam. **/ RestParam * rest_param_new_full (const char *name, RestMemoryUse use, gconstpointer data, gsize length, const char *content_type, const char *filename) { RestParam *param; g_return_val_if_fail (name != NULL, NULL); g_return_val_if_fail (content_type != NULL, NULL); g_return_val_if_fail (data != NULL, NULL); param = g_slice_new0 (RestParam); if (use == REST_MEMORY_COPY) { data = g_memdup (data, length); use = REST_MEMORY_TAKE; } param->name = g_strdup (name); param->use = use; param->data = data; param->length = length; param->content_type = g_intern_string (content_type); param->filename = g_strdup (filename); param->ref_count = 1; if (use == REST_MEMORY_TAKE) { param->owner = (gpointer)data; param->owner_dnotify = g_free; } return param; } /** * rest_param_new_with_owner: * @name: the parameter name * @data: (array length=length) (element-type guint8): a pointer to * the start of the data * @length: the length of the data * @content_type: the content type of the data * @filename: (allow-none): the original filename, or %NULL * @owner: (transfer full): pointer to an object that owns @data * @owner_dnotify: (allow-none): a function to free/unref @owner when * the buffer is freed * * Create a new #RestParam called @name with @length bytes of @data as the * value. @content_type is the type of the data as a MIME type, for example * "text/plain" for simple string parameters. * * If the parameter is a file upload it can be passed as @filename. * * When the #RestParam is freed, it will call @owner_dnotify, passing @owner to * it. This allows you to do something like this: * * |[ * GMappedFile *map = g_mapped_file_new (filename, FALSE, &error); * RestParam *param = rest_param_new_with_owner ("media", * g_mapped_file_get_contents (map), * g_mapped_file_get_length (map), * "image/jpeg", * filename, * map, * (GDestroyNotify)g_mapped_file_unref); * ]| * * Returns: a new #RestParam. **/ RestParam * rest_param_new_with_owner (const char *name, gconstpointer data, gsize length, const char *content_type, const char *filename, gpointer owner, GDestroyNotify owner_dnotify) { RestParam *param; g_return_val_if_fail (name, NULL); g_return_val_if_fail (data, NULL); g_return_val_if_fail (content_type, NULL); param = g_slice_new0 (RestParam); param->name = g_strdup (name); param->use = REST_MEMORY_OWNED; param->data = data; param->length = length; param->content_type = g_intern_string (content_type); param->filename = g_strdup (filename); param->ref_count = 1; param->owner = owner; param->owner_dnotify = owner_dnotify; return param; } /** * rest_param_new_string: * @name: the parameter name * @use: the #RestMemoryUse describing how the memory can be used * @string: the parameter value * * A convience constructor to create a #RestParam from a given UTF-8 string. * The resulting #RestParam will have a content type of "text/plain". * * Returns: a new #RestParam. **/ RestParam * rest_param_new_string (const char *name, RestMemoryUse use, const char *string) { g_return_val_if_fail (name != NULL, NULL); g_return_val_if_fail (string != NULL, NULL); if (string == NULL) { use = REST_MEMORY_STATIC; string = ""; } return rest_param_new_full (name, use, string, strlen (string) + 1, g_intern_static_string ("text/plain"), NULL); } /** * rest_param_get_name: * @param: a valid #RestParam * * Get the name of the parameter. * * Returns: the parameter name. **/ const char * rest_param_get_name (RestParam *param) { g_return_val_if_fail (param != NULL, NULL); return param->name; } /** * rest_param_get_content_type: * @param: a valid #RestParam * * Get the MIME type of the parameter. For example, basic strings have the MIME * type "text/plain". * * Returns: the MIME type **/ const char * rest_param_get_content_type (RestParam *param) { g_return_val_if_fail (param != NULL, NULL); return param->content_type; } /** * rest_param_get_file_name: * @param: a valid #RestParam * * Get the original file name of the parameter, if one is available. * * Returns: the filename if set, or %NULL. **/ const char * rest_param_get_file_name (RestParam *param) { g_return_val_if_fail (param != NULL, FALSE); return param->filename; } /** * rest_param_is_string: * @param: a valid #RestParam * * Determine if the parameter is a string value, i.e. the content type is "text/plain". * * Returns: %TRUE if the parameter is a string, %FALSE otherwise. */ gboolean rest_param_is_string (RestParam *param) { g_return_val_if_fail (param != NULL, FALSE); return param->content_type == g_intern_static_string ("text/plain"); } /** * rest_param_get_content: * @param: a valid #RestParam * * Get the content of @param. The content should be treated as read-only and * not modified in any way. * * Returns: (transfer none): the content. **/ gconstpointer rest_param_get_content (RestParam *param) { g_return_val_if_fail (param != NULL, NULL); return param->data; } /** * rest_param_get_content_length: * @param: a valid #RestParam * * Get the length of the content of @param. * * Returns: the length of the content **/ gsize rest_param_get_content_length (RestParam *param) { g_return_val_if_fail (param != NULL, 0); return param->length; } /** * rest_param_ref: * @param: a valid #RestParam * * Increase the reference count on @param. * * Returns: the #RestParam **/ RestParam * rest_param_ref (RestParam *param) { /* TODO: bring back REST_MEMORY_TEMPORARY? */ g_return_val_if_fail (param != NULL, NULL); g_return_val_if_fail (param->ref_count > 0, NULL); g_atomic_int_inc (¶m->ref_count); return param; } /** * rest_param_unref: * @param: a valid #RestParam * * Decrease the reference count on @param, destroying it if the reference count * reaches 0. **/ void rest_param_unref (RestParam *param) { g_return_if_fail (param); if (g_atomic_int_dec_and_test (¶m->ref_count)) { if (param->owner_dnotify) param->owner_dnotify (param->owner); g_free (param->name); g_free (param->filename); g_slice_free (RestParam, param); } } corebird-1.7.4/src/rest/rest/rest-param.h000066400000000000000000000057521324604713000202720ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2010 Intel Corporation. * * Authors: Ross Burton * Rob Bradford * * RestParam is inspired by libsoup's SoupBuffer * Copyright (C) 2000-2030 Ximian, Inc * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #ifndef _REST_PARAM #define _REST_PARAM #include G_BEGIN_DECLS #define REST_TYPE_PARAM (rest_param_get_type ()) /** * RestMemoryUse: * @REST_MEMORY_STATIC: the memory block can be assumed to always exist for the * lifetime of the parameter, #RestParam will use it directly. * @REST_MEMORY_TAKE: #RestParam will take ownership of the memory block, and * g_free() it when it isn't used. * @REST_MEMORY_COPY: #RestParam will make a copy of the memory block. */ typedef enum { REST_MEMORY_STATIC, REST_MEMORY_TAKE, REST_MEMORY_COPY, } RestMemoryUse; typedef struct _RestParam RestParam; GType rest_param_get_type (void) G_GNUC_CONST; RestParam *rest_param_new_string (const char *name, RestMemoryUse use, const char *string); RestParam *rest_param_new_full (const char *name, RestMemoryUse use, gconstpointer data, gsize length, const char *content_type, const char *filename); RestParam *rest_param_new_with_owner (const char *name, gconstpointer data, gsize length, const char *content_type, const char *filename, gpointer owner, GDestroyNotify owner_dnotify); gboolean rest_param_is_string (RestParam *param); const char *rest_param_get_name (RestParam *param); const char *rest_param_get_content_type (RestParam *param); const char *rest_param_get_file_name (RestParam *param); gconstpointer rest_param_get_content (RestParam *param); gsize rest_param_get_content_length (RestParam *param); RestParam *rest_param_ref (RestParam *param); void rest_param_unref (RestParam *param); G_END_DECLS #endif /* _REST_PARAM */ corebird-1.7.4/src/rest/rest/rest-params.c000066400000000000000000000144201324604713000204400ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2008, 2009, Intel Corporation. * * Authors: Rob Bradford * Ross Burton * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #include #include #include "rest-params.h" /** * SECTION:rest-params * @short_description: Container for call parameters * @see_also: #RestParam, #RestProxyCall. */ /* * RestParams is an alias for GHashTable achieved by opaque types in the public * headers and casting internally. This has several limitations, mainly * supporting multiple parameters with the same name and preserving the ordering * of parameters. * * These are not requirements for the bulk of the web services, but this * limitation does mean librest can't be used for a few web services. * * TODO: this should be a list to support multiple parameters with the same * name. */ /** * rest_params_new: * * Create a new #RestParams. * * Returns: A empty #RestParams. **/ RestParams * rest_params_new (void) { /* The key is a string that is owned by the RestParam, so we don't need to explicitly free it on removal. */ return (RestParams *) g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)rest_param_unref); } /** * rest_params_free: * @params: a valid #RestParams * * Destroy the #RestParams and the #RestParam objects that it contains. **/ void rest_params_free (RestParams *params) { GHashTable *hash = (GHashTable *)params; g_return_if_fail (params); g_hash_table_destroy (hash); } /** * rest_params_add: * @params: a valid #RestParams * @param: a valid #RestParam * * Add @param to @params. **/ void rest_params_add (RestParams *params, RestParam *param) { GHashTable *hash = (GHashTable *)params; g_return_if_fail (params); g_return_if_fail (param); g_hash_table_replace (hash, (gpointer)rest_param_get_name (param), param); } /** * rest_params_get: * @params: a valid #RestParams * @name: a parameter name * * Return the #RestParam called @name, or %NULL if it doesn't exist. * * Returns: a #RestParam or %NULL if the name doesn't exist **/ RestParam * rest_params_get (RestParams *params, const char *name) { GHashTable *hash = (GHashTable *)params; g_return_val_if_fail (params, NULL); g_return_val_if_fail (name, NULL); return g_hash_table_lookup (hash, name); } /** * rest_params_remove: * @params: a valid #RestParams * @name: a parameter name * * Remove the #RestParam called @name. **/ void rest_params_remove (RestParams *params, const char *name) { GHashTable *hash = (GHashTable *)params; g_return_if_fail (params); g_return_if_fail (name); g_hash_table_remove (hash, name); } /** * rest_params_are_strings: * @params: a valid #RestParams * * Checks if the parameters are all simple strings (have a content type of * "text/plain"). * * Returns: %TRUE if all of the parameters are simple strings, %FALSE otherwise. **/ gboolean rest_params_are_strings (RestParams *params) { GHashTable *hash = (GHashTable *)params; GHashTableIter iter; RestParam *param; g_return_val_if_fail (params, FALSE); g_hash_table_iter_init (&iter, hash); while (g_hash_table_iter_next (&iter, NULL, (gpointer)¶m)) { if (!rest_param_is_string (param)) return FALSE; } return TRUE; } /** * rest_params_as_string_hash_table: * @params: a valid #RestParams * * Create a new #GHashTable which contains the name and value of all string * (content type of text/plain) parameters. * * The values are owned by the #RestParams, so don't destroy the #RestParams * before the hash table. * * Returns: (element-type utf8 Rest.Param) (transfer container): a new #GHashTable. **/ GHashTable * rest_params_as_string_hash_table (RestParams *params) { GHashTable *hash, *strings; GHashTableIter iter; const char *name = NULL; RestParam *param = NULL; g_return_val_if_fail (params, NULL); hash = (GHashTable *)params; strings = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_iter_init (&iter, hash); while (g_hash_table_iter_next (&iter, (gpointer)&name, (gpointer)¶m)) { if (rest_param_is_string (param)) g_hash_table_insert (strings, (gpointer)name, (gpointer)rest_param_get_content (param)); } return strings; } /** * rest_params_iter_init: * @iter: an uninitialized #RestParamsIter * @params: a valid #RestParams * * Initialize a parameter iterator over @params. Modifying @params after calling * this function invalidates the returned iterator. * |[ * RestParamsIter iter; * const char *name; * RestParam *param; * * rest_params_iter_init (&iter, params); * while (rest_params_iter_next (&iter, &name, ¶m)) { * /* do something with name and param */ * } * ]| **/ void rest_params_iter_init (RestParamsIter *iter, RestParams *params) { g_return_if_fail (iter); g_return_if_fail (params); g_hash_table_iter_init ((GHashTableIter *)iter, (GHashTable *)params); } /** * rest_params_iter_next: * @iter: an initialized #RestParamsIter * @name: a location to store the name, or %NULL * @param: a location to store the #RestParam, or %NULL * * Advances @iter and retrieves the name and/or parameter that are now pointed * at as a result of this advancement. If FALSE is returned, @name and @param * are not set and the iterator becomes invalid. * * Returns: %FALSE if the end of the #RestParams has been reached, %TRUE otherwise. **/ gboolean rest_params_iter_next (RestParamsIter *iter, const char **name, RestParam **param) { g_return_val_if_fail (iter, FALSE); return g_hash_table_iter_next ((GHashTableIter *)iter, (gpointer)name, (gpointer)param); } corebird-1.7.4/src/rest/rest/rest-params.h000066400000000000000000000032261324604713000204470ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2008, 2009, Intel Corporation. * * Authors: Rob Bradford * Ross Burton * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #ifndef _REST_PARAMS #define _REST_PARAMS #include #include "rest-param.h" G_BEGIN_DECLS typedef struct _RestParams RestParams; typedef struct _GHashTableIter RestParamsIter; RestParams * rest_params_new (void); void rest_params_free (RestParams *params); void rest_params_add (RestParams *params, RestParam *param); RestParam *rest_params_get (RestParams *params, const char *name); void rest_params_remove (RestParams *params, const char *name); gboolean rest_params_are_strings (RestParams *params); GHashTable * rest_params_as_string_hash_table (RestParams *params); void rest_params_iter_init (RestParamsIter *iter, RestParams *params); gboolean rest_params_iter_next (RestParamsIter *iter, const char **name, RestParam **param); G_END_DECLS #endif /* _REST_PARAMS */ corebird-1.7.4/src/rest/rest/rest-private.h000066400000000000000000000040051324604713000206320ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2008, 2009, Intel Corporation. * * Authors: Rob Bradford * Ross Burton * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #ifndef _REST_PRIVATE #define _REST_PRIVATE #include #include "rest-proxy.h" #include "rest-proxy-call.h" #include G_BEGIN_DECLS typedef enum { REST_DEBUG_PROXY = 1 << 1, REST_DEBUG_ALL = REST_DEBUG_PROXY } RestDebugFlags; extern guint rest_debug_flags; #define REST_DEBUG_ENABLED(category) (rest_debug_flags & REST_DEBUG_##category) #define REST_DEBUG(category,x,a...) G_STMT_START { \ if (REST_DEBUG_ENABLED(category)) \ { g_message ("[" #category "] " G_STRLOC ": " x, ##a); } \ } G_STMT_END void _rest_setup_debugging (void); gboolean _rest_proxy_get_binding_required (RestProxy *proxy); const gchar *_rest_proxy_get_bound_url (RestProxy *proxy); void _rest_proxy_queue_message (RestProxy *proxy, SoupMessage *message, SoupSessionCallback callback, gpointer user_data); void _rest_proxy_cancel_message (RestProxy *proxy, SoupMessage *message); G_END_DECLS #endif /* _REST_PRIVATE */ corebird-1.7.4/src/rest/rest/rest-proxy-call-private.h000066400000000000000000000022041324604713000227210ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2008, 2009, Intel Corporation. * * Authors: Rob Bradford * Ross Burton * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #ifndef _REST_PROXY_CALL_PRIVATE #define _REST_PROXY_CALL_PRIVATE #include "rest-proxy.h" #include "rest-proxy-call.h" #include "rest-params.h" G_BEGIN_DECLS const char *rest_proxy_call_get_url (RestProxyCall *call); G_END_DECLS #endif /* _REST_PROXY_CALL_PRIVATE */ corebird-1.7.4/src/rest/rest/rest-proxy-call.c000066400000000000000000001063631324604713000212570ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2008, 2009, Intel Corporation. * * Authors: Rob Bradford * Ross Burton * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #include "rest-proxy.h" #include "rest-proxy-call.h" #include "rest-params.h" #include #include "rest-private.h" #include "rest-proxy-call-private.h" struct _RestProxyCallAsyncClosure { RestProxyCall *call; RestProxyCallAsyncCallback callback; GObject *weak_object; gpointer userdata; SoupMessage *message; }; typedef struct _RestProxyCallAsyncClosure RestProxyCallAsyncClosure; struct _RestProxyCallContinuousClosure { RestProxyCall *call; RestProxyCallContinuousCallback callback; GObject *weak_object; gpointer userdata; SoupMessage *message; }; typedef struct _RestProxyCallContinuousClosure RestProxyCallContinuousClosure; struct _RestProxyCallUploadClosure { RestProxyCall *call; RestProxyCallUploadCallback callback; GObject *weak_object; gpointer userdata; SoupMessage *message; gsize uploaded; }; typedef struct _RestProxyCallUploadClosure RestProxyCallUploadClosure; #define GET_PRIVATE(o) ((RestProxyCallPrivate*)(rest_proxy_call_get_instance_private (REST_PROXY_CALL(o)))) struct _RestProxyCallPrivate { gchar *method; gchar *function; GHashTable *headers; RestParams *params; /* The real URL we're about to invoke */ gchar *url; GHashTable *response_headers; goffset length; gchar *payload; GCancellable *cancellable; gulong cancel_sig; RestProxy *proxy; RestProxyCallAsyncClosure *cur_call_closure; }; typedef struct _RestProxyCallPrivate RestProxyCallPrivate; G_DEFINE_TYPE_WITH_PRIVATE (RestProxyCall, rest_proxy_call, G_TYPE_OBJECT) enum { PROP_0 = 0, PROP_PROXY }; GQuark rest_proxy_call_error_quark (void) { return g_quark_from_static_string ("rest-proxy-call-error-quark"); } static void rest_proxy_call_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { RestProxyCallPrivate *priv = GET_PRIVATE (object); switch (property_id) { case PROP_PROXY: g_value_set_object (value, priv->proxy); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void rest_proxy_call_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { RestProxyCallPrivate *priv = GET_PRIVATE (object); switch (property_id) { case PROP_PROXY: priv->proxy = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void rest_proxy_call_dispose (GObject *object) { RestProxyCallPrivate *priv = GET_PRIVATE (object); if (priv->cancellable) { g_signal_handler_disconnect (priv->cancellable, priv->cancel_sig); g_clear_object (&priv->cancellable); } g_clear_pointer (&priv->params, rest_params_free); g_clear_pointer (&priv->headers, g_hash_table_unref); g_clear_pointer (&priv->response_headers, g_hash_table_unref); g_clear_object (&priv->proxy); G_OBJECT_CLASS (rest_proxy_call_parent_class)->dispose (object); } static void rest_proxy_call_finalize (GObject *object) { RestProxyCallPrivate *priv = GET_PRIVATE (object); g_free (priv->method); g_free (priv->function); g_free (priv->payload); g_free (priv->url); G_OBJECT_CLASS (rest_proxy_call_parent_class)->finalize (object); } static void rest_proxy_call_class_init (RestProxyCallClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GParamSpec *pspec; object_class->get_property = rest_proxy_call_get_property; object_class->set_property = rest_proxy_call_set_property; object_class->dispose = rest_proxy_call_dispose; object_class->finalize = rest_proxy_call_finalize; pspec = g_param_spec_object ("proxy", "proxy", "Proxy for this call", REST_TYPE_PROXY, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_PROXY, pspec); } static void rest_proxy_call_init (RestProxyCall *self) { RestProxyCallPrivate *priv = GET_PRIVATE (self); priv->method = g_strdup ("GET"); priv->params = rest_params_new (); priv->headers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); priv->response_headers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); } /** * rest_proxy_call_set_method: * @call: The #RestProxyCall * @method: The HTTP method to use * * Set the HTTP method to use when making the call, for example GET or POST. */ void rest_proxy_call_set_method (RestProxyCall *call, const gchar *method) { RestProxyCallPrivate *priv = GET_PRIVATE (call); g_return_if_fail (REST_IS_PROXY_CALL (call)); g_free (priv->method); if (method) priv->method = g_strdup (method); else priv->method = g_strdup ("GET"); } /** * rest_proxy_call_get_method: * @call: The #RestProxyCall * * Get the HTTP method to use when making the call, for example GET or POST. */ const char * rest_proxy_call_get_method (RestProxyCall *call) { g_return_val_if_fail (REST_IS_PROXY_CALL (call), NULL); return GET_PRIVATE (call)->method; } /** * rest_proxy_call_set_function: * @call: The #RestProxyCall * @function: The function to call * * Set the REST "function" to call on the proxy. This is appended to the URL, * so that for example a proxy with the URL * http://www.example.com/ and the function * test would actually access the URL * http://www.example.com/test */ void rest_proxy_call_set_function (RestProxyCall *call, const gchar *function) { RestProxyCallPrivate *priv = GET_PRIVATE (call); g_return_if_fail (REST_IS_PROXY_CALL (call)); g_free (priv->function); priv->function = g_strdup (function); } /** * rest_proxy_call_get_function: * @call: The #RestProxyCall * * Get the REST function that is going to be called on the proxy. * * Returns: The REST "function" for the current call, see also * rest_proxy_call_set_function(). This string is owned by the #RestProxyCall * and should not be freed. * Since: 0.7.92 */ const char * rest_proxy_call_get_function (RestProxyCall *call) { g_return_val_if_fail (REST_IS_PROXY_CALL (call), NULL); return GET_PRIVATE (call)->function; } /** * rest_proxy_call_add_header: * @call: The #RestProxyCall * @header: The name of the header to set * @value: The value of the header * * Add a header called @header with the value @value to the call. If a * header with this name already exists, the new value will replace the old. */ void rest_proxy_call_add_header (RestProxyCall *call, const gchar *header, const gchar *value) { RestProxyCallPrivate *priv = GET_PRIVATE (call); g_return_if_fail (REST_IS_PROXY_CALL (call)); g_hash_table_insert (priv->headers, g_strdup (header), g_strdup (value)); } void rest_proxy_call_take_header (RestProxyCall *call, const gchar *header, gchar *value) { RestProxyCallPrivate *priv = GET_PRIVATE (call); g_return_if_fail (REST_IS_PROXY_CALL (call)); g_hash_table_insert (priv->headers, g_strdup (header), value); } /** * rest_proxy_call_lookup_header: * @call: The #RestProxyCall * @header: The header name * * Get the value of the header called @header. * * Returns: The header value, or %NULL if it does not exist. This string is * owned by the #RestProxyCall and should not be freed. */ const gchar * rest_proxy_call_lookup_header (RestProxyCall *call, const gchar *header) { g_return_val_if_fail (REST_IS_PROXY_CALL (call), NULL); return g_hash_table_lookup (GET_PRIVATE (call)->headers, header); } /** * rest_proxy_call_remove_header: * @call: The #RestProxyCall * @header: The header name * * Remove the header named @header from the call. */ void rest_proxy_call_remove_header (RestProxyCall *call, const gchar *header) { g_return_if_fail (REST_IS_PROXY_CALL (call)); g_hash_table_remove (GET_PRIVATE (call)->headers, header); } /** * rest_proxy_call_add_param: * @call: The #RestProxyCall * @name: The name of the parameter to set * @value: The value of the parameter * * Add a query parameter called @param with the string value @value to the call. * If a parameter with this name already exists, the new value will replace the * old. */ void rest_proxy_call_add_param (RestProxyCall *call, const gchar *name, const gchar *value) { RestProxyCallPrivate *priv = GET_PRIVATE (call); RestParam *param; g_return_if_fail (REST_IS_PROXY_CALL (call)); param = rest_param_new_string (name, REST_MEMORY_COPY, value); rest_params_add (priv->params, param); } void rest_proxy_call_take_param (RestProxyCall *call, const char *name, char *value) { RestProxyCallPrivate *priv = GET_PRIVATE (call); RestParam *param; g_return_if_fail (REST_IS_PROXY_CALL (call)); param = rest_param_new_string (name, REST_MEMORY_TAKE, value); rest_params_add (priv->params, param); } void rest_proxy_call_add_param_full (RestProxyCall *call, RestParam *param) { g_return_if_fail (REST_IS_PROXY_CALL (call)); g_return_if_fail (param); rest_params_add (GET_PRIVATE (call)->params, param); } /** * rest_proxy_call_lookup_param: * @call: The #RestProxyCall * @name: The parameter name * * Get the value of the parameter called @name. * * Returns: The parameter value, or %NULL if it does not exist. This string is * owned by the #RestProxyCall and should not be freed. */ RestParam * rest_proxy_call_lookup_param (RestProxyCall *call, const gchar *name) { g_return_val_if_fail (REST_IS_PROXY_CALL (call), NULL); return rest_params_get (GET_PRIVATE (call)->params, name); } /** * rest_proxy_call_remove_param: * @call: The #RestProxyCall * @name: The parameter name * * Remove the parameter named @name from the call. */ void rest_proxy_call_remove_param (RestProxyCall *call, const gchar *name) { g_return_if_fail (REST_IS_PROXY_CALL (call)); rest_params_remove (GET_PRIVATE (call)->params, name); } /** * rest_proxy_call_get_params: * @call: The #RestProxyCall * * Get the parameters as a #RestParams of parameter names to values. The * returned value is owned by the RestProxyCall instance and should not * be freed by the caller. * * Returns: (transfer none): A #RestParams. */ RestParams * rest_proxy_call_get_params (RestProxyCall *call) { g_return_val_if_fail (REST_IS_PROXY_CALL (call), NULL); return GET_PRIVATE (call)->params; } static void _call_async_weak_notify_cb (gpointer *data, GObject *dead_object); static void _populate_headers_hash_table (const gchar *name, const gchar *value, gpointer userdata) { GHashTable *headers = (GHashTable *)userdata; g_hash_table_insert (headers, g_strdup (name), g_strdup (value)); } /* I apologise for this macro, but it saves typing ;-) */ #define error_helper(x) g_set_error_literal(error, REST_PROXY_ERROR, x, message->reason_phrase) static void _handle_error_from_message (SoupMessage *message, GError **error) { if (message->status_code < 100) { switch (message->status_code) { case SOUP_STATUS_CANCELLED: error_helper (REST_PROXY_ERROR_CANCELLED); break; case SOUP_STATUS_CANT_RESOLVE: case SOUP_STATUS_CANT_RESOLVE_PROXY: error_helper (REST_PROXY_ERROR_RESOLUTION); break; case SOUP_STATUS_CANT_CONNECT: case SOUP_STATUS_CANT_CONNECT_PROXY: error_helper (REST_PROXY_ERROR_CONNECTION); break; case SOUP_STATUS_SSL_FAILED: error_helper (REST_PROXY_ERROR_SSL); break; case SOUP_STATUS_IO_ERROR: error_helper (REST_PROXY_ERROR_IO); break; case SOUP_STATUS_MALFORMED: case SOUP_STATUS_TRY_AGAIN: default: error_helper (REST_PROXY_ERROR_FAILED); break; } } else if (message->status_code >= 200 && message->status_code < 300) { /* Good */ return; } else { /* If we are here we must be in some kind of HTTP error, lets try */ g_set_error_literal (error, REST_PROXY_ERROR, message->status_code, message->reason_phrase); } } static void finish_call (RestProxyCall *call, SoupMessage *message, GError **error) { RestProxyCallPrivate *priv = GET_PRIVATE (call); g_assert (call); g_assert (message); /* Convert the soup headers in to hash */ /* FIXME: Eeek..are you allowed duplicate headers? ... */ g_hash_table_remove_all (priv->response_headers); soup_message_headers_foreach (message->response_headers, (SoupMessageHeadersForeachFunc)_populate_headers_hash_table, priv->response_headers); priv->payload = g_memdup (message->response_body->data, message->response_body->length + 1); priv->length = message->response_body->length; _handle_error_from_message (message, error); } static void _continuous_call_message_completed_cb (SoupSession *session, SoupMessage *message, gpointer userdata) { RestProxyCallContinuousClosure *closure; RestProxyCall *call; RestProxyCallPrivate *priv; GError *error = NULL; closure = (RestProxyCallContinuousClosure *)userdata; call = closure->call; priv = GET_PRIVATE (call); _handle_error_from_message (message, &error); closure->callback (closure->call, NULL, 0, error, closure->weak_object, closure->userdata); g_clear_error (&error); /* Success. We don't need the weak reference any more */ if (closure->weak_object) { g_object_weak_unref (closure->weak_object, (GWeakNotify)_call_async_weak_notify_cb, closure); } priv->cur_call_closure = NULL; g_object_unref (closure->call); g_slice_free (RestProxyCallContinuousClosure, closure); } static void _call_async_weak_notify_cb (gpointer *data, GObject *dead_object) { RestProxyCallAsyncClosure *closure; closure = (RestProxyCallAsyncClosure *)data; /* Will end up freeing the closure */ rest_proxy_call_cancel (closure->call); closure->weak_object = NULL; } static void set_header (gpointer key, gpointer value, gpointer user_data) { const char *name = key; SoupMessageHeaders *headers = user_data; soup_message_headers_replace (headers, name, value); } static gboolean set_url (RestProxyCall *call) { RestProxyCallPrivate *priv = GET_PRIVATE (call); const gchar *bound_url; bound_url =_rest_proxy_get_bound_url (priv->proxy); if (_rest_proxy_get_binding_required (priv->proxy) && !bound_url) { g_critical (G_STRLOC ": URL requires binding and is unbound"); return FALSE; } g_free (priv->url); /* FIXME: Perhaps excessive memory duplication */ if (priv->function) { if (g_str_has_suffix (bound_url, "/") || g_str_has_prefix (priv->function, "/")) { priv->url = g_strdup_printf ("%s%s", bound_url, priv->function); } else { priv->url = g_strdup_printf ("%s/%s", bound_url, priv->function); } } else { priv->url = g_strdup (bound_url); } return TRUE; } static SoupMessage * prepare_message (RestProxyCall *call, GError **error_out) { RestProxyCallPrivate *priv = GET_PRIVATE (call); RestProxyCallClass *call_class; SoupMessage *message; GError *error = NULL; call_class = REST_PROXY_CALL_GET_CLASS (call); /* Emit a warning if the caller is re-using RestProxyCall objects */ if (priv->url) { g_warning (G_STRLOC ": re-use of RestProxyCall %p, don't do this", call); } /* Allow an overrideable prepare function that is called before every * invocation so subclasses can do magic */ if (call_class->prepare) { if (!call_class->prepare (call, &error)) { g_propagate_error (error_out, error); return NULL; } } if (call_class->serialize_params) { gchar *content; gchar *content_type; gsize content_len; if (!call_class->serialize_params (call, &content_type, &content, &content_len, &error)) { g_propagate_error (error_out, error); return NULL; } /* Reset priv->url as the serialize_params vcall may have called * rest_proxy_call_set_function() */ if (!set_url (call)) { g_free (content); g_free (content_type); g_set_error_literal (error_out, REST_PROXY_ERROR, REST_PROXY_ERROR_BINDING_REQUIRED, "URL is unbound"); return NULL; } message = soup_message_new (priv->method, priv->url); if (message == NULL) { g_free (content); g_free (content_type); g_set_error_literal (error_out, REST_PROXY_ERROR, REST_PROXY_ERROR_FAILED, "Could not parse URI"); return NULL; } soup_message_set_request (message, content_type, SOUP_MEMORY_TAKE, content, content_len); g_free (content_type); } else if (rest_params_are_strings (priv->params)) { GHashTable *hash; if (!set_url (call)) { g_set_error_literal (error_out, REST_PROXY_ERROR, REST_PROXY_ERROR_BINDING_REQUIRED, "URL is unbound"); return NULL; } hash = rest_params_as_string_hash_table (priv->params); message = soup_form_request_new_from_hash (priv->method, priv->url, hash); g_hash_table_unref (hash); if (!message) { g_set_error (error_out, REST_PROXY_ERROR, REST_PROXY_ERROR_URL_INVALID, "URL '%s' is not valid", priv->url); return NULL; } } else { SoupMultipart *mp; RestParamsIter iter; const char *name; RestParam *param; mp = soup_multipart_new (SOUP_FORM_MIME_TYPE_MULTIPART); rest_params_iter_init (&iter, priv->params); while (rest_params_iter_next (&iter, &name, ¶m)) { if (rest_param_is_string (param)) { soup_multipart_append_form_string (mp, name, rest_param_get_content (param)); } else { SoupBuffer *sb; sb = soup_buffer_new_with_owner (rest_param_get_content (param), rest_param_get_content_length (param), rest_param_ref (param), (GDestroyNotify)rest_param_unref); soup_multipart_append_form_file (mp, name, rest_param_get_file_name (param), rest_param_get_content_type (param), sb); soup_buffer_free (sb); } } if (!set_url (call)) { soup_multipart_free (mp); g_set_error_literal (error_out, REST_PROXY_ERROR, REST_PROXY_ERROR_BINDING_REQUIRED, "URL is unbound"); return NULL; } message = soup_form_request_new_from_multipart (priv->url, mp); soup_multipart_free (mp); } /* Set the headers */ g_hash_table_foreach (priv->headers, set_header, message->request_headers); return message; } static void _call_message_call_cancelled_cb (GCancellable *cancellable, RestProxyCall *call) { rest_proxy_call_cancel (call); } static void _call_message_call_completed_cb (SoupSession *session, SoupMessage *message, gpointer user_data) { GTask *task = user_data; RestProxyCall *call; GError *error = NULL; call = REST_PROXY_CALL (g_task_get_source_object (task)); finish_call (call, message, &error); if (error != NULL) g_task_return_error (task, error); else g_task_return_boolean (task, TRUE); g_object_unref (task); } /** * rest_proxy_call_invoke_async: * @call: a #RestProxyCall * @cancellable: (nullable): an optional #GCancellable that can be used to * cancel the call, or %NULL * @callback: (scope async): callback to call when the async call is finished * @user_data: (closure): user data for the callback */ void rest_proxy_call_invoke_async (RestProxyCall *call, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { RestProxyCallPrivate *priv = GET_PRIVATE (call); GTask *task; SoupMessage *message; GError *error = NULL; g_return_if_fail (REST_IS_PROXY_CALL (call)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); g_assert (priv->proxy); message = prepare_message (call, &error); task = g_task_new (call, cancellable, callback, user_data); if (message == NULL) { g_task_return_error (task, error); return; } if (cancellable != NULL) { priv->cancel_sig = g_signal_connect (cancellable, "cancelled", G_CALLBACK (_call_message_call_cancelled_cb), call); priv->cancellable = g_object_ref (cancellable); } _rest_proxy_queue_message (priv->proxy, message, _call_message_call_completed_cb, task); } /** * rest_proxy_call_invoke_finish: * @call: a #RestProxyCall * @result: the result from the #GAsyncReadyCallback * @error: optional #GError * * Returns: %TRUE on success */ gboolean rest_proxy_call_invoke_finish (RestProxyCall *call, GAsyncResult *result, GError **error) { g_return_val_if_fail (REST_IS_PROXY_CALL (call), FALSE); g_return_val_if_fail (g_task_is_valid (result, call), FALSE); return g_task_propagate_boolean (G_TASK (result), error); } static void _continuous_call_message_got_chunk_cb (SoupMessage *msg, SoupBuffer *chunk, RestProxyCallContinuousClosure *closure) { closure->callback (closure->call, chunk->data, chunk->length, NULL, closure->weak_object, closure->userdata); } /** * rest_proxy_call_continuous: (skip) * @call: The #RestProxyCall * @callback: a #RestProxyCallContinuousCallback to invoke when data is available * @weak_object: The #GObject to weakly reference and tie the lifecycle to * @userdata: (closure): data to pass to @callback * @error: (out) (allow-none): a #GError, or %NULL * * Asynchronously invoke @call but expect a continuous stream of content. This * means that the body data will not be accumulated and thus you cannot use * rest_proxy_call_get_payload() * * When there is data @callback will be called and when the connection is * closed or the stream ends @callback will also be called. * * If @weak_object is disposed during the call then this call will be * cancelled. If the call is cancelled then the callback will be invoked with * an error state. * * You may unref the call after calling this function since there is an * internal reference, or you may unref in the callback. */ gboolean rest_proxy_call_continuous (RestProxyCall *call, RestProxyCallContinuousCallback callback, GObject *weak_object, gpointer userdata, GError **error) { RestProxyCallPrivate *priv = GET_PRIVATE (call); SoupMessage *message; RestProxyCallContinuousClosure *closure; g_return_val_if_fail (REST_IS_PROXY_CALL (call), FALSE); g_assert (priv->proxy); if (priv->cur_call_closure) { g_warning (G_STRLOC ": re-use of RestProxyCall %p, don't do this", call); return FALSE; } message = prepare_message (call, error); if (message == NULL) return FALSE; /* Must turn off accumulation */ soup_message_body_set_accumulate (message->response_body, FALSE); closure = g_slice_new0 (RestProxyCallContinuousClosure); closure->call = g_object_ref (call); closure->callback = callback; closure->weak_object = weak_object; closure->message = message; closure->userdata = userdata; priv->cur_call_closure = (RestProxyCallAsyncClosure *)closure; /* Weakly reference this object. We remove our callback if it goes away. */ if (closure->weak_object) { g_object_weak_ref (closure->weak_object, (GWeakNotify)_call_async_weak_notify_cb, closure); } g_signal_connect (message, "got-chunk", (GCallback)_continuous_call_message_got_chunk_cb, closure); _rest_proxy_queue_message (priv->proxy, message, _continuous_call_message_completed_cb, closure); return TRUE; } static void _upload_call_message_completed_cb (SoupSession *session, SoupMessage *message, gpointer user_data) { RestProxyCall *call; RestProxyCallPrivate *priv; GError *error = NULL; RestProxyCallUploadClosure *closure; closure = (RestProxyCallUploadClosure *) user_data; call = closure->call; priv = GET_PRIVATE (call); finish_call (call, message, &error); closure->callback (closure->call, closure->uploaded, closure->uploaded, error, closure->weak_object, closure->userdata); g_clear_error (&error); /* Success. We don't need the weak reference any more */ if (closure->weak_object) { g_object_weak_unref (closure->weak_object, (GWeakNotify)_call_async_weak_notify_cb, closure); } priv->cur_call_closure = NULL; g_object_unref (closure->call); g_slice_free (RestProxyCallUploadClosure, closure); } static void _upload_call_message_wrote_data_cb (SoupMessage *msg, SoupBuffer *chunk, RestProxyCallUploadClosure *closure) { closure->uploaded = closure->uploaded + chunk->length; if (closure->uploaded < msg->request_body->length) closure->callback (closure->call, msg->request_body->length, closure->uploaded, NULL, closure->weak_object, closure->userdata); } /** * rest_proxy_call_upload: * @call: The #RestProxyCall * @callback: (scope async): a #RestProxyCallUploadCallback to invoke when a chunk * of data was uploaded * @weak_object: The #GObject to weakly reference and tie the lifecycle to * @userdata: data to pass to @callback * @error: a #GError, or %NULL * * Asynchronously invoke @call but expect to have the callback invoked every time a * chunk of our request's body is written. * * When the callback is invoked with the uploaded byte count equaling the message * byte count, the call has completed. * * If @weak_object is disposed during the call then this call will be * cancelled. If the call is cancelled then the callback will be invoked with * an error state. * * You may unref the call after calling this function since there is an * internal reference, or you may unref in the callback. */ gboolean rest_proxy_call_upload (RestProxyCall *call, RestProxyCallUploadCallback callback, GObject *weak_object, GCancellable *cancellable, gpointer userdata, GError **error) { RestProxyCallPrivate *priv = GET_PRIVATE (call); SoupMessage *message; RestProxyCallUploadClosure *closure; g_return_val_if_fail (REST_IS_PROXY_CALL (call), FALSE); g_assert (priv->proxy); if (priv->cur_call_closure) { g_warning (G_STRLOC ": re-use of RestProxyCall %p, don't do this", call); return FALSE; } message = prepare_message (call, error); if (message == NULL) return FALSE; closure = g_slice_new0 (RestProxyCallUploadClosure); closure->call = g_object_ref (call); closure->callback = callback; closure->weak_object = weak_object; closure->message = message; closure->userdata = userdata; closure->uploaded = 0; priv->cur_call_closure = (RestProxyCallAsyncClosure *)closure; /* Weakly reference this object. We remove our callback if it goes away. */ if (closure->weak_object) { g_object_weak_ref (closure->weak_object, (GWeakNotify)_call_async_weak_notify_cb, closure); } priv->cancel_sig = g_signal_connect (cancellable, "cancelled", G_CALLBACK (_call_message_call_cancelled_cb), call); priv->cancellable = g_object_ref (cancellable); g_signal_connect (message, "wrote-body-data", (GCallback) _upload_call_message_wrote_data_cb, closure); _rest_proxy_queue_message (priv->proxy, message, _upload_call_message_completed_cb, closure); return TRUE; } /** * rest_proxy_call_cancel: (skip) * @call: The #RestProxyCall * * Cancel this call. It may be too late to not actually send the message, but * the callback will not be invoked. * * N.B. this method should only be used with rest_proxy_call_async() and NOT * rest_proxy_call_invoke_async(). */ gboolean rest_proxy_call_cancel (RestProxyCall *call) { RestProxyCallPrivate *priv = GET_PRIVATE (call); RestProxyCallAsyncClosure *closure; g_return_val_if_fail (REST_IS_PROXY_CALL (call), FALSE); closure = priv->cur_call_closure; if (priv->cancellable) { g_signal_handler_disconnect (priv->cancellable, priv->cancel_sig); g_clear_object (&priv->cancellable); } if (closure) { /* This will cause the _call_message_completed_cb to be fired which will * tidy up the closure and so forth */ _rest_proxy_cancel_message (priv->proxy, closure->message); } return TRUE; } /** * rest_proxy_call_lookup_response_header: * @call: The #RestProxyCall * @header: The name of the header to lookup. * * Get the string value of the header @header or %NULL if that header is not * present or there are no headers. */ const gchar * rest_proxy_call_lookup_response_header (RestProxyCall *call, const gchar *header) { RestProxyCallPrivate *priv = GET_PRIVATE (call); g_return_val_if_fail (REST_IS_PROXY_CALL (call), NULL); if (!priv->response_headers) { return NULL; } return g_hash_table_lookup (priv->response_headers, header); } /** * rest_proxy_call_get_response_headers: * @call: The #RestProxyCall * * Returns: (transfer container): pointer to a hash table of * headers. This hash table must not be changed. You should call * g_hash_table_unref() when you have finished with it. */ GHashTable * rest_proxy_call_get_response_headers (RestProxyCall *call) { RestProxyCallPrivate *priv = GET_PRIVATE (call); g_return_val_if_fail (REST_IS_PROXY_CALL (call), NULL); if (!priv->response_headers) { return NULL; } return g_hash_table_ref (priv->response_headers); } /** * rest_proxy_call_get_payload_length: * @call: The #RestProxyCall * * Get the length of the return payload. * * Returns: the length of the payload in bytes. */ goffset rest_proxy_call_get_payload_length (RestProxyCall *call) { g_return_val_if_fail (REST_IS_PROXY_CALL (call), 0); return GET_PRIVATE (call)->length; } /** * rest_proxy_call_get_payload: * @call: The #RestProxyCall * * Get the return payload. * * Returns: A pointer to the payload. This is owned by #RestProxyCall and should * not be freed. */ const gchar * rest_proxy_call_get_payload (RestProxyCall *call) { g_return_val_if_fail (REST_IS_PROXY_CALL (call), NULL); return GET_PRIVATE (call)->payload; } char * rest_proxy_call_take_payload (RestProxyCall *call) { return g_steal_pointer (&GET_PRIVATE (call)->payload); } /** * rest_proxy_call_serialize_params: * @call: The #RestProxyCall * @content_type: (out): Content type of the payload * @content: (out): The payload * @content_len: (out): Length of the payload data * @error: a #GError, or %NULL * * Invoker for a virtual method to serialize the parameters for this * #RestProxyCall. * * Returns: TRUE if the serialization was successful, FALSE otherwise. */ gboolean rest_proxy_call_serialize_params (RestProxyCall *call, gchar **content_type, gchar **content, gsize *content_len, GError **error) { RestProxyCallClass *call_class; call_class = REST_PROXY_CALL_GET_CLASS (call); if (call_class->serialize_params) { return call_class->serialize_params (call, content_type, content, content_len, error); } return FALSE; } G_GNUC_INTERNAL const char * rest_proxy_call_get_url (RestProxyCall *call) { /* Ensure priv::url is current before returning it. This method is used * by OAuthProxyCall::_prepare which expects set_url() to have been called, * but this has been changed/broken by c66b6df */ set_url(call); return GET_PRIVATE (call)->url; } corebird-1.7.4/src/rest/rest/rest-proxy-call.h000066400000000000000000000164021324604713000212560ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2008, 2009, Intel Corporation. * * Authors: Rob Bradford * Ross Burton * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #ifndef _REST_PROXY_CALL #define _REST_PROXY_CALL #include #include #include "rest-params.h" G_BEGIN_DECLS #define REST_TYPE_PROXY_CALL rest_proxy_call_get_type() G_DECLARE_DERIVABLE_TYPE (RestProxyCall, rest_proxy_call, REST, PROXY_CALL, GObject) /** * RestProxyCallClass: * @prepare: Virtual function called before making the request, This allows the * call to be modified, for example to add a signature. * @serialize_params: Virtual function allowing custom serialization of the * parameters, for example when the API doesn't expect standard form content. * * Class structure for #RestProxyCall for subclasses to implement specialised * behaviour. */ struct _RestProxyCallClass { /*< private >*/ GObjectClass parent_class; /*< public >*/ gboolean (*prepare)(RestProxyCall *call, GError **error); gboolean (*serialize_params) (RestProxyCall *call, gchar **content_type, gchar **content, gsize *content_len, GError **error); }; #define REST_PROXY_CALL_ERROR rest_proxy_call_error_quark () /** * RestProxyCallError: * @REST_PROXY_CALL_FAILED: the method call failed * * Error domain used when returning errors from #RestProxyCall. */ typedef enum { REST_PROXY_CALL_FAILED } RestProxyCallError; GQuark rest_proxy_call_error_quark (void); GType rest_proxy_call_get_type (void); /* Functions for dealing with request */ void rest_proxy_call_set_method (RestProxyCall *call, const gchar *method); const char * rest_proxy_call_get_method (RestProxyCall *call); void rest_proxy_call_set_function (RestProxyCall *call, const gchar *function); const char * rest_proxy_call_get_function (RestProxyCall *call); void rest_proxy_call_add_header (RestProxyCall *call, const gchar *header, const gchar *value); void rest_proxy_call_take_header (RestProxyCall *call, const gchar *header, gchar *value); const gchar *rest_proxy_call_lookup_header (RestProxyCall *call, const gchar *header); void rest_proxy_call_remove_header (RestProxyCall *call, const gchar *header); void rest_proxy_call_add_param (RestProxyCall *call, const gchar *name, const gchar *value); void rest_proxy_call_take_param (RestProxyCall *call, const gchar *name, gchar *value); void rest_proxy_call_add_param_full (RestProxyCall *call, RestParam *param); RestParam *rest_proxy_call_lookup_param (RestProxyCall *call, const gchar *name); void rest_proxy_call_remove_param (RestProxyCall *call, const gchar *name); RestParams *rest_proxy_call_get_params (RestProxyCall *call); typedef void (*RestProxyCallAsyncCallback)(RestProxyCall *call, const GError *error, GObject *weak_object, gpointer userdata); void rest_proxy_call_invoke_async (RestProxyCall *call, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); gboolean rest_proxy_call_invoke_finish (RestProxyCall *call, GAsyncResult *result, GError **error); typedef void (*RestProxyCallContinuousCallback) (RestProxyCall *call, const gchar *buf, gsize len, const GError *error, GObject *weak_object, gpointer userdata); gboolean rest_proxy_call_continuous (RestProxyCall *call, RestProxyCallContinuousCallback callback, GObject *weak_object, gpointer userdata, GError **error); typedef void (*RestProxyCallUploadCallback) (RestProxyCall *call, gsize total, gsize uploaded, const GError *error, GObject *weak_object, gpointer userdata); gboolean rest_proxy_call_upload (RestProxyCall *call, RestProxyCallUploadCallback callback, GObject *weak_object, GCancellable *cancellable, gpointer userdata, GError **error); gboolean rest_proxy_call_cancel (RestProxyCall *call); /* Functions for dealing with responses */ const gchar *rest_proxy_call_lookup_response_header (RestProxyCall *call, const gchar *header); GHashTable *rest_proxy_call_get_response_headers (RestProxyCall *call); goffset rest_proxy_call_get_payload_length (RestProxyCall *call); const gchar *rest_proxy_call_get_payload (RestProxyCall *call); char *rest_proxy_call_take_payload (RestProxyCall *call); gboolean rest_proxy_call_serialize_params (RestProxyCall *call, gchar **content_type, gchar **content, gsize *content_len, GError **error); G_END_DECLS #endif /* _REST_PROXY_CALL */ corebird-1.7.4/src/rest/rest/rest-proxy.c000066400000000000000000000274401324604713000203440ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2008, 2009, Intel Corporation. * * Authors: Rob Bradford * Ross Burton * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #include #include #include #include "rest-proxy.h" #include "rest-private.h" #define GET_PRIVATE(o) rest_proxy_get_instance_private(REST_PROXY(o)) typedef struct _RestProxyPrivate RestProxyPrivate; struct _RestProxyPrivate { gchar *url_format; gchar *url; gboolean binding_required; SoupSession *session; gboolean disable_cookies; char *ssl_ca_file; }; G_DEFINE_TYPE_WITH_PRIVATE (RestProxy, rest_proxy, G_TYPE_OBJECT) enum { PROP0 = 0, PROP_URL_FORMAT, PROP_BINDING_REQUIRED, PROP_DISABLE_COOKIES, PROP_SSL_STRICT, PROP_SSL_CA_FILE }; static RestProxyCall *_rest_proxy_new_call (RestProxy *proxy); GQuark rest_proxy_error_quark (void) { return g_quark_from_static_string ("rest-proxy-error-quark"); } static void rest_proxy_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { RestProxyPrivate *priv = GET_PRIVATE (object); switch (property_id) { case PROP_URL_FORMAT: g_value_set_string (value, priv->url_format); break; case PROP_BINDING_REQUIRED: g_value_set_boolean (value, priv->binding_required); break; case PROP_DISABLE_COOKIES: g_value_set_boolean (value, priv->disable_cookies); break; case PROP_SSL_STRICT: { gboolean ssl_strict; g_object_get (G_OBJECT(priv->session), "ssl-strict", &ssl_strict, NULL); g_value_set_boolean (value, ssl_strict); break; } case PROP_SSL_CA_FILE: g_value_set_string (value, priv->ssl_ca_file); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void rest_proxy_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { RestProxyPrivate *priv = GET_PRIVATE (object); switch (property_id) { case PROP_URL_FORMAT: g_free (priv->url_format); priv->url_format = g_value_dup_string (value); /* Clear the cached url */ g_free (priv->url); priv->url = NULL; break; case PROP_BINDING_REQUIRED: priv->binding_required = g_value_get_boolean (value); /* Clear cached url */ g_free (priv->url); priv->url = NULL; break; case PROP_DISABLE_COOKIES: priv->disable_cookies = g_value_get_boolean (value); break; case PROP_SSL_STRICT: g_object_set (G_OBJECT(priv->session), "ssl-strict", g_value_get_boolean (value), NULL); break; case PROP_SSL_CA_FILE: g_free(priv->ssl_ca_file); priv->ssl_ca_file = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void rest_proxy_dispose (GObject *object) { RestProxyPrivate *priv = GET_PRIVATE (object); g_clear_object (&priv->session); G_OBJECT_CLASS (rest_proxy_parent_class)->dispose (object); } static void rest_proxy_constructed (GObject *object) { RestProxyPrivate *priv = GET_PRIVATE (object); if (!priv->disable_cookies) { SoupSessionFeature *cookie_jar = (SoupSessionFeature *)soup_cookie_jar_new (); soup_session_add_feature (priv->session, cookie_jar); g_object_unref (cookie_jar); } if (REST_DEBUG_ENABLED(PROXY)) { SoupSessionFeature *logger = (SoupSessionFeature*)soup_logger_new (SOUP_LOGGER_LOG_BODY, 0); soup_session_add_feature (priv->session, logger); g_object_unref (logger); } } static void rest_proxy_finalize (GObject *object) { RestProxyPrivate *priv = GET_PRIVATE (object); g_free (priv->url); g_free (priv->url_format); g_free (priv->ssl_ca_file); G_OBJECT_CLASS (rest_proxy_parent_class)->finalize (object); } static void rest_proxy_class_init (RestProxyClass *klass) { GParamSpec *pspec; GObjectClass *object_class = G_OBJECT_CLASS (klass); RestProxyClass *proxy_class = REST_PROXY_CLASS (klass); _rest_setup_debugging (); object_class->get_property = rest_proxy_get_property; object_class->set_property = rest_proxy_set_property; object_class->dispose = rest_proxy_dispose; object_class->constructed = rest_proxy_constructed; object_class->finalize = rest_proxy_finalize; proxy_class->new_call = _rest_proxy_new_call; pspec = g_param_spec_string ("url-format", "url-format", "Format string for the RESTful url", NULL, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_URL_FORMAT, pspec); pspec = g_param_spec_boolean ("binding-required", "binding-required", "Whether the URL format requires binding", FALSE, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_BINDING_REQUIRED, pspec); pspec = g_param_spec_boolean ("disable-cookies", "disable-cookies", "Whether to disable cookie support", FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (object_class, PROP_DISABLE_COOKIES, pspec); pspec = g_param_spec_boolean ("ssl-strict", "Strictly validate SSL certificates", "Whether certificate errors should be considered a connection error", TRUE, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_SSL_STRICT, pspec); pspec = g_param_spec_string ("ssl-ca-file", "SSL CA file", "File containing SSL CA certificates.", NULL, G_PARAM_READWRITE); g_object_class_install_property (object_class, PROP_SSL_CA_FILE, pspec); } static void rest_proxy_init (RestProxy *self) { RestProxyPrivate *priv = GET_PRIVATE (self); priv->session = soup_session_new (); #ifdef REST_SYSTEM_CA_FILE /* with ssl-strict (defaults TRUE) setting ssl-ca-file forces all * certificates to be trusted */ g_object_set (priv->session, "ssl-ca-file", REST_SYSTEM_CA_FILE, NULL); #endif g_object_bind_property (self, "ssl-ca-file", priv->session, "ssl-ca-file", G_BINDING_BIDIRECTIONAL); } /** * rest_proxy_new: * @url_format: the endpoint URL * @binding_required: whether the URL needs to be bound before calling * * Create a new #RestProxy for the specified endpoint @url_format, using the * "GET" method. * * Set @binding_required to %TRUE if the URL contains string formatting * operations (for example "http://foo.com/%s". These must be expanded * using rest_proxy_bind() before invoking the proxy. * * Returns: A new #RestProxy. */ RestProxy * rest_proxy_new (const gchar *url_format, gboolean binding_required) { g_return_val_if_fail (url_format != NULL, NULL); return g_object_new (REST_TYPE_PROXY, "url-format", url_format, "binding-required", binding_required, NULL); } /** * rest_proxy_add_soup_feature: * @proxy: The #RestProxy * @feature: A #SoupSessionFeature * * This method can be used to add specific features to the #SoupSession objects * that are used by librest for its HTTP connections. For example, if one needs * extensive control over the cookies which are used for the REST HTTP * communication, it's possible to get full access to libsoup cookie API by * using * * * RestProxy *proxy = g_object_new(REST_TYPE_PROXY, * "url-format", url, * "disable-cookies", TRUE, * NULL); * SoupSessionFeature *cookie_jar = SOUP_SESSION_FEATURE(soup_cookie_jar_new ()); * rest_proxy_add_soup_feature(proxy, cookie_jar); * * * Since: 0.7.92 */ void rest_proxy_add_soup_feature (RestProxy *proxy, SoupSessionFeature *feature) { RestProxyPrivate *priv = GET_PRIVATE (proxy); g_return_if_fail (REST_IS_PROXY(proxy)); g_return_if_fail (feature != NULL); g_return_if_fail (priv->session != NULL); soup_session_add_feature (priv->session, feature); } static RestProxyCall * _rest_proxy_new_call (RestProxy *proxy) { RestProxyCall *call; call = g_object_new (REST_TYPE_PROXY_CALL, "proxy", proxy, NULL); return call; } /** * rest_proxy_new_call: * @proxy: the #RestProxy * * Create a new #RestProxyCall for making a call to the web service. This call * is one-shot and should not be re-used for making multiple calls. * * Returns: (transfer full): a new #RestProxyCall. */ RestProxyCall * rest_proxy_new_call (RestProxy *proxy) { RestProxyClass *proxy_class; g_return_val_if_fail (REST_IS_PROXY (proxy), NULL); proxy_class = REST_PROXY_GET_CLASS (proxy); return proxy_class->new_call (proxy); } gboolean _rest_proxy_get_binding_required (RestProxy *proxy) { RestProxyPrivate *priv = GET_PRIVATE (proxy); g_return_val_if_fail (REST_IS_PROXY (proxy), FALSE); return priv->binding_required; } const gchar * _rest_proxy_get_bound_url (RestProxy *proxy) { RestProxyPrivate *priv = GET_PRIVATE (proxy); g_return_val_if_fail (REST_IS_PROXY (proxy), NULL); if (!priv->url && !priv->binding_required) { priv->url = g_strdup (priv->url_format); } return priv->url; } void _rest_proxy_queue_message (RestProxy *proxy, SoupMessage *message, SoupSessionCallback callback, gpointer user_data) { RestProxyPrivate *priv = GET_PRIVATE (proxy); g_return_if_fail (REST_IS_PROXY (proxy)); g_return_if_fail (SOUP_IS_MESSAGE (message)); soup_session_queue_message (priv->session, message, callback, user_data); } void _rest_proxy_cancel_message (RestProxy *proxy, SoupMessage *message) { RestProxyPrivate *priv = GET_PRIVATE (proxy); g_return_if_fail (REST_IS_PROXY (proxy)); g_return_if_fail (SOUP_IS_MESSAGE (message)); soup_session_cancel_message (priv->session, message, SOUP_STATUS_CANCELLED); } corebird-1.7.4/src/rest/rest/rest-proxy.h000066400000000000000000000155451324604713000203540ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2008, 2009, Intel Corporation. * * Authors: Rob Bradford * Ross Burton * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #ifndef _REST_PROXY #define _REST_PROXY #include #include #include "rest-proxy-call.h" G_BEGIN_DECLS #define REST_TYPE_PROXY rest_proxy_get_type() G_DECLARE_DERIVABLE_TYPE (RestProxy, rest_proxy, REST, PROXY, GObject) typedef struct _RestProxy RestProxy; typedef struct _RestProxyClass RestProxyClass; /** * RestProxyClass: * @bind_valist: Virtual function called to bind parameters. * @new_call: Virtual function called to construct a new #RestProxyCall. * * Class structure for #RestProxy for subclasses to implement specialised * behaviour. * * Typically subclasses will override @new_call to construct a subclass of * #RestProxyCall. */ struct _RestProxyClass { /*< private >*/ GObjectClass parent_class; /*< public >*/ gboolean (*bind_valist)(RestProxy *proxy, va_list params); RestProxyCall *(*new_call)(RestProxy *proxy); }; #define REST_PROXY_ERROR rest_proxy_error_quark () /** * RestProxyError: * @REST_PROXY_ERROR_CANCELLED: Cancelled * @REST_PROXY_ERROR_RESOLUTION: Resolution * @REST_PROXY_ERROR_CONNECTION: Connection * @REST_PROXY_ERROR_SSL: SSL * @REST_PROXY_ERROR_IO: Input/Output * @REST_PROXY_ERROR_FAILED: Failure * @REST_PROXY_ERROR_URL_INVALID: Invalid URL * @REST_PROXY_ERROR_BINDING_REQUIRED: URL requires binding * @REST_PROXY_ERROR_HTTP_MULTIPLE_CHOICES: HTTP/Multiple choices * @REST_PROXY_ERROR_HTTP_MOVED_PERMANENTLY: HTTP/Moved permanently * @REST_PROXY_ERROR_HTTP_FOUND: HTTP/Found * @REST_PROXY_ERROR_HTTP_SEE_OTHER: HTTP/See other * @REST_PROXY_ERROR_HTTP_NOT_MODIFIED: HTTP/Not modified * @REST_PROXY_ERROR_HTTP_USE_PROXY: HTTP/Use proxy * @REST_PROXY_ERROR_HTTP_THREEOHSIX: HTTP/306 * @REST_PROXY_ERROR_HTTP_TEMPORARY_REDIRECT: HTTP/Temporary redirect * @REST_PROXY_ERROR_HTTP_BAD_REQUEST: HTTP/Bad request * @REST_PROXY_ERROR_HTTP_UNAUTHORIZED: HTTP/Unauthorized * @REST_PROXY_ERROR_HTTP_FOUROHTWO: HTTP/402 * @REST_PROXY_ERROR_HTTP_FORBIDDEN: HTTP/Forbidden * @REST_PROXY_ERROR_HTTP_NOT_FOUND: HTTP/Not found * @REST_PROXY_ERROR_HTTP_METHOD_NOT_ALLOWED: HTTP/Method not allowed * @REST_PROXY_ERROR_HTTP_NOT_ACCEPTABLE: HTTP/Not acceptable * @REST_PROXY_ERROR_HTTP_PROXY_AUTHENTICATION_REQUIRED: HTTP/Proxy authentication required * @REST_PROXY_ERROR_HTTP_REQUEST_TIMEOUT: HTTP/Request timeout * @REST_PROXY_ERROR_HTTP_CONFLICT: HTTP/Conflict * @REST_PROXY_ERROR_HTTP_GONE: HTTP/Gone * @REST_PROXY_ERROR_HTTP_LENGTH_REQUIRED: HTTP/Length required * @REST_PROXY_ERROR_HTTP_PRECONDITION_FAILED: HTTP/Precondition failed * @REST_PROXY_ERROR_HTTP_REQUEST_ENTITY_TOO_LARGE: HTTP/Request entity too large * @REST_PROXY_ERROR_HTTP_REQUEST_URI_TOO_LONG: HTTP/Request URI too long * @REST_PROXY_ERROR_HTTP_UNSUPPORTED_MEDIA_TYPE: HTTP/Unsupported media type * @REST_PROXY_ERROR_HTTP_REQUESTED_RANGE_NOT_SATISFIABLE: HTTP/Requested range not satisfiable * @REST_PROXY_ERROR_HTTP_EXPECTATION_FAILED: HTTP/Expectation failed * @REST_PROXY_ERROR_HTTP_INTERNAL_SERVER_ERROR: HTTP/Internal server error * @REST_PROXY_ERROR_HTTP_NOT_IMPLEMENTED: HTTP/Not implemented * @REST_PROXY_ERROR_HTTP_BAD_GATEWAY: HTTP/Bad gateway * @REST_PROXY_ERROR_HTTP_SERVICE_UNAVAILABLE: HTTP/Service unavailable * @REST_PROXY_ERROR_HTTP_GATEWAY_TIMEOUT: HTTP/Gateway timeout * @REST_PROXY_ERROR_HTTP_HTTP_VERSION_NOT_SUPPORTED: HTTP/Version not supported * * Error domain used when returning errors from a #RestProxy. */ typedef enum { REST_PROXY_ERROR_CANCELLED = 1, REST_PROXY_ERROR_RESOLUTION, REST_PROXY_ERROR_CONNECTION, REST_PROXY_ERROR_SSL, REST_PROXY_ERROR_IO, REST_PROXY_ERROR_FAILED, REST_PROXY_ERROR_URL_INVALID, REST_PROXY_ERROR_BINDING_REQUIRED, REST_PROXY_ERROR_HTTP_MULTIPLE_CHOICES = 300, REST_PROXY_ERROR_HTTP_MOVED_PERMANENTLY = 301, REST_PROXY_ERROR_HTTP_FOUND = 302, REST_PROXY_ERROR_HTTP_SEE_OTHER = 303, REST_PROXY_ERROR_HTTP_NOT_MODIFIED = 304, REST_PROXY_ERROR_HTTP_USE_PROXY = 305, REST_PROXY_ERROR_HTTP_THREEOHSIX = 306, REST_PROXY_ERROR_HTTP_TEMPORARY_REDIRECT = 307, REST_PROXY_ERROR_HTTP_BAD_REQUEST = 400, REST_PROXY_ERROR_HTTP_UNAUTHORIZED = 401, REST_PROXY_ERROR_HTTP_FOUROHTWO = 402, REST_PROXY_ERROR_HTTP_FORBIDDEN = 403, REST_PROXY_ERROR_HTTP_NOT_FOUND = 404, REST_PROXY_ERROR_HTTP_METHOD_NOT_ALLOWED = 405, REST_PROXY_ERROR_HTTP_NOT_ACCEPTABLE = 406, REST_PROXY_ERROR_HTTP_PROXY_AUTHENTICATION_REQUIRED = 407, REST_PROXY_ERROR_HTTP_REQUEST_TIMEOUT = 408, REST_PROXY_ERROR_HTTP_CONFLICT = 409, REST_PROXY_ERROR_HTTP_GONE = 410, REST_PROXY_ERROR_HTTP_LENGTH_REQUIRED = 411, REST_PROXY_ERROR_HTTP_PRECONDITION_FAILED = 412, REST_PROXY_ERROR_HTTP_REQUEST_ENTITY_TOO_LARGE = 413, REST_PROXY_ERROR_HTTP_REQUEST_URI_TOO_LONG = 414, REST_PROXY_ERROR_HTTP_UNSUPPORTED_MEDIA_TYPE = 415, REST_PROXY_ERROR_HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416, REST_PROXY_ERROR_HTTP_EXPECTATION_FAILED = 417, REST_PROXY_ERROR_HTTP_INTERNAL_SERVER_ERROR = 500, REST_PROXY_ERROR_HTTP_NOT_IMPLEMENTED = 501, REST_PROXY_ERROR_HTTP_BAD_GATEWAY = 502, REST_PROXY_ERROR_HTTP_SERVICE_UNAVAILABLE = 503, REST_PROXY_ERROR_HTTP_GATEWAY_TIMEOUT = 504, REST_PROXY_ERROR_HTTP_HTTP_VERSION_NOT_SUPPORTED = 505, } RestProxyError; GQuark rest_proxy_error_quark (void); GType rest_proxy_get_type (void); RestProxy *rest_proxy_new (const gchar *url_format, gboolean binding_required); void rest_proxy_add_soup_feature (RestProxy *proxy, SoupSessionFeature *feature); RestProxyCall *rest_proxy_new_call (RestProxy *proxy); G_END_DECLS #endif /* _REST_PROXY */ corebird-1.7.4/src/rest/rest/sha1.c000066400000000000000000000031131324604713000170330ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2008, 2009, Intel Corporation. * * Authors: Ross Burton * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #include #include #include "sha1.h" #define SHA1_LENGTH 20 /* * hmac_sha1: * @key: The key * @message: The message * * Given the key and message, compute the HMAC-SHA1 hash and return the base-64 * encoding of it. This is very geared towards OAuth, and as such both key and * message must be NULL-terminated strings, and the result is base-64 encoded. */ char * hmac_sha1 (const char *key, const char *message) { GHmac *hmac; gsize digest_length = SHA1_LENGTH; guchar digest[digest_length]; hmac = g_hmac_new (G_CHECKSUM_SHA1, (guchar *)key, strlen (key)); g_hmac_update (hmac, (guchar *)message, -1); g_hmac_get_digest (hmac, digest, &digest_length); g_hmac_unref (hmac); return g_base64_encode (digest, digest_length); } corebird-1.7.4/src/rest/rest/sha1.h000066400000000000000000000015761324604713000170530ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (c) 2008, 2009, Intel Corporation. * * Authors: Ross Burton * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ char * hmac_sha1 (const char *key, const char *message); corebird-1.7.4/src/rest/rest/test-runner.c000066400000000000000000000022371324604713000204730ustar00rootroot00000000000000/* * librest - RESTful web services access * Copyright (C) 2009 Intel Corporation. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #define test_add(unit_name, func) G_STMT_START { \ extern void func (void); \ g_test_add_func (unit_name, func); } G_STMT_END int main (int argc, char *argv[]) { g_test_init (&argc, &argv, NULL); test_add ("/oauth/param-encoding", test_param_encoding); return g_test_run (); } corebird-1.7.4/src/rest/tests/000077500000000000000000000000001324604713000162225ustar00rootroot00000000000000corebird-1.7.4/src/rest/tests/Makefile.am000066400000000000000000000012341324604713000202560ustar00rootroot00000000000000TESTS = proxy proxy-continuous threaded oauth oauth-async oauth2 flickr lastfm xml custom-serialize # TODO: fix this test case XFAIL_TESTS = xml AM_CPPFLAGS = $(SOUP_CFLAGS) -I$(top_srcdir) $(GCOV_CFLAGS) AM_LDFLAGS = $(SOUP_LIBS) $(GCOV_LDFLAGS) \ ../rest/librest-@API_VERSION@.la ../rest-extras/librest-extras-@API_VERSION@.la check_PROGRAMS = $(TESTS) proxy_SOURCES = proxy.c proxy_continuous_SOURCES = proxy-continuous.c threaded_SOURCES = threaded.c oauth_SOURCES = oauth.c oauth_async_SOURCES = oauth-async.c oauth2_SOURCES = oauth2.c flickr_SOURCES = flickr.c lastfm_SOURCES = lastfm.c xml_SOURCES = xml.c custom_serialize_SOURCES = custom-serialize.c corebird-1.7.4/src/sql/000077500000000000000000000000001324604713000147025ustar00rootroot00000000000000corebird-1.7.4/src/sql/Database.vala000066400000000000000000000061721324604713000172610ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ namespace Sql { public const int COREBIRD_SQL_VERSION = 2; public const string COREBIRD_INIT_FILE = "/org/baedert/corebird/sql/init/Create.%d.sql"; public const int ACCOUNTS_SQL_VERSION = 3; public const string ACCOUNTS_INIT_FILE = "/org/baedert/corebird/sql/accounts/Create.%d.sql"; private const int STOP = -1; private const int CONTINUE = 0; public class Database : GLib.Object { private Sqlite.Database db; public Database (string filename, string init_file, int max_version) { int err = Sqlite.Database.open (filename, out db); if (err != 0) { critical ("Error when opening the database '%s': %s", filename, db.errmsg ()); } this.exec ("PRAGMA journal_mode = MEMORY;"); int user_version = 0; this.exec ("pragma user_version;", (n_cols, vals) => {user_version = int.parse(vals[0]); return STOP;}); for (int cur_version = user_version + 1; cur_version <= max_version; cur_version ++) { try { var data = GLib.resources_lookup_data (init_file.printf (cur_version), 0); unowned string sql_str = (string) data.get_data (); debug ("Executing %s for %d", init_file, cur_version); db.exec (sql_str); } catch (GLib.Error e) { critical (e.message); break; } } } public void exec (string sql, Sqlite.Callback? callback = null) { #if DEBUG string err = ""; int val = db.exec (sql, callback, out err); if (val != Sqlite.OK && val != 4) critical ("SQL ERROR(%d): '%s' FOR QUERY '%s'", val, err, sql); #else db.exec (sql, callback); #endif } public Sql.InsertStatement insert (string table_name) { var stmt = new InsertStatement (table_name); stmt.db = db; return stmt; } public Sql.InsertStatement replace (string table_name) { var stmt = new InsertStatement (table_name, true); stmt.db = db; return stmt; } public Sql.SelectStatement select (string table_name) { var stmt = new SelectStatement (table_name); stmt.db = db; return stmt; } public Sql.UpdateStatement update (string table_name) { var stmt = new UpdateStatement (table_name); stmt.db = db; return stmt; } public void begin_transaction () { db.exec ("BEGIN TRANSACTION;"); } public void end_transaction () { db.exec ("END TRANSACTION;"); } public unowned Sqlite.Database get_sqlite_db () { return this.db; } } } corebird-1.7.4/src/sql/InsertStatement.vala000066400000000000000000000060271324604713000207050ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ namespace Sql { public delegate bool SelectCallback (string[] vals); public class InsertStatement : GLib.Object { public unowned Sqlite.Database db; private StringBuilder query_builder = new StringBuilder (); private GLib.GenericArray bindings = new GLib.GenericArray(); private bool ran = false; public InsertStatement (string table_name, bool replace = false) { if (replace) query_builder.append ("INSERT OR REPLACE INTO `"); else query_builder.append ("INSERT INTO `"); query_builder.append (table_name).append ("` ("); } public int64 run () { query_builder.append (") VALUES ("); query_builder.append ("?"); for (int i = 0; i < bindings.length -1; i++) query_builder.append (",?"); query_builder.append (");"); Sqlite.Statement stmt; int ok = db.prepare_v2 (query_builder.str, -1, out stmt); if (ok != Sqlite.OK) { critical (db.errmsg ()); return -1; } for (int i = 0; i < bindings.length; i++) { stmt.bind_text (i + 1, bindings.get (i)); } ok = stmt.step (); if (ok != Sqlite.DONE) { critical (db.errmsg ()); StringBuilder err_msg = new StringBuilder (); err_msg.append (stmt.sql ()).append (" --- "); for (int i = 0; i < bindings.length; i++) { err_msg.append (bindings.get (i)).append (", "); } critical (err_msg.str); } ran = true; return db.last_insert_rowid (); } public InsertStatement val (string col_name, string col_value) { if (bindings.length > 0) query_builder.append (", "); query_builder.append ("`").append (col_name).append ("`"); bindings.add (col_value); return this; } public InsertStatement vali (string col_name, int col_value) { return val (col_name, col_value.to_string ()); } public InsertStatement vali64 (string col_name, int64 col_value) { return val (col_name, col_value.to_string ()); } public InsertStatement valb (string col_name, bool col_value) { return val (col_name, col_value ? "1" : "0"); } #if DEBUG ~InsertStatement () { if (!ran) critical ("InsertStatement for %s did not run.", query_builder.str); } #endif } } corebird-1.7.4/src/sql/SelectStatement.vala000066400000000000000000000071551324604713000206630ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ namespace Sql { public class SelectStatement : GLib.Object { public unowned Sqlite.Database db; private StringBuilder query_builder = new StringBuilder (); private string table_name; public SelectStatement (string table_name) { this.table_name = table_name; } public SelectStatement cols (string first, ...) { var arg_list = va_list (); query_builder.append ("SELECT `").append (first).append ("`"); for (string? arg = arg_list.arg (); arg != null; arg = arg_list.arg ()) { query_builder.append (", `").append (arg).append ("`"); } query_builder.append (" FROM `").append (table_name).append ("`"); return this; } public SelectStatement where (string stmt) { query_builder.append (" WHERE ").append (stmt); return this; } public SelectStatement where_prefix (string field, string prefix) { query_builder.append (" WHERE `").append (field).append ("` LIKE '") .append (prefix).append ("%'"); return this; } public SelectStatement where_prefix2 (string field, string prefix) { query_builder.append ("`").append (field).append ("` LIKE '") .append (prefix).append ("%'"); return this; } public SelectStatement or () { query_builder.append (" OR "); return this; } public SelectStatement nocase () { query_builder.append (" COLLATE NOCASE"); return this; } public SelectStatement where_eqi (string w, int64 v) { query_builder.append (" WHERE `").append (w).append ("`='").append (v.to_string ()).append ("'"); return this; } public SelectStatement order (string order_by) { query_builder.append (" ORDER BY ").append (order_by); return this; } public SelectStatement limit (int limit) { query_builder.append (" LIMIT ").append (limit.to_string ()); return this; } public int run (SelectCallback callback) { Sqlite.Statement stmt; int ok = db.prepare_v2 (query_builder.str, -1, out stmt); if (ok != Sqlite.OK) { critical (db.errmsg ()); critical (query_builder.str); return 0; } bool next = true; int n_cols = stmt.column_count (); int n_rows = 0; while (stmt.step () == Sqlite.ROW && next) { string[] vals = new string[n_cols]; for (int i = 0; i < n_cols; i++) vals[i] = stmt.column_text (i); next = callback (vals); n_rows ++; } return n_rows; } public int64 once_i64 () { int64 back = -1; this.run ((vals) => { back = int64.parse (vals[0]); return false; }); return back; } public string? once_string () { string? back = null; this.run ((vals) => { back = vals[0]; return false; }); return back; } } } corebird-1.7.4/src/sql/UpdateStatement.vala000066400000000000000000000056261324604713000206670ustar00rootroot00000000000000 /* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ namespace Sql { public class UpdateStatement : GLib.Object { public unowned Sqlite.Database db; private StringBuilder query_builder = new StringBuilder (); private GLib.GenericArray bindings = new GLib.GenericArray (); private bool ran = false; public UpdateStatement (string table_name) { query_builder.append ("UPDATE `").append (table_name).append ("` SET "); } public int64 run () { Sqlite.Statement stmt; query_builder.append(";"); int ok = db.prepare_v2 (query_builder.str, -1, out stmt); if (ok != Sqlite.OK) { critical (db.errmsg ()); return -1; } for (int i = 0; i < bindings.length; i++) { stmt.bind_text (i + 1, bindings.get (i)); } ok = stmt.step (); if (ok == Sqlite.ERROR) { critical (db.errmsg ()); critical (stmt.sql ()); return -1; } ran = true; return db.last_insert_rowid (); } public UpdateStatement where (string where) { query_builder.append (" WHERE ").append (where); return this; } public UpdateStatement where_eq (string col, string value) { query_builder.append (" WHERE `").append (col).append ("`='").append (value).append ("'"); return this; } public UpdateStatement where_eqi (string col, int64 iv) { return where_eq (col, iv.to_string ()); } public UpdateStatement val (string col_name, string col_value) { if (bindings.length > 0) query_builder.append (", "); query_builder.append ("`").append (col_name).append ("` = ?"); bindings.add (col_value); return this; } public UpdateStatement vali (string col_name, int col_value) { return val (col_name, col_value.to_string ()); } public UpdateStatement vali64 (string col_name, int64 col_value) { return val (col_name, col_value.to_string ()); } public UpdateStatement valb (string col_name, bool col_value) { return val (col_name, col_value ? "1" : "0"); } #if DEBUG ~UpdateStatement () { if (!ran) critical ("UpdateStatement for %s did not run.", query_builder.str); } #endif } } corebird-1.7.4/src/util/000077500000000000000000000000001324604713000150605ustar00rootroot00000000000000corebird-1.7.4/src/util/Benchmark.vala000066400000000000000000000023061324604713000176200ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2016 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ namespace Benchmark { public class Bench { public string name; public GLib.DateTime first; public void stop () { #if DEBUG var ts = new GLib.DateTime.now_local ().difference (first); int64 ms = (ts / 1000); debug (@"$(this.name) took $ms ms ($ts us)"); #endif } } public Bench start (string name) { var b = new Bench (); #if DEBUG b.name = name; b.first = new GLib.DateTime.now_local (); #endif return b; } } corebird-1.7.4/src/util/Dirs.vala000066400000000000000000000032211324604713000166240ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ namespace Dirs { static string? config_dir = null; public void create_dirs () { create_folder (config ("")); create_folder (config ("accounts/")); create_folder (config ("image-favorites/")); } public string config (string path) { if (config_dir == null) { config_dir = GLib.Environment.get_home_dir () + "/.corebird/"; if (!GLib.FileUtils.test (config_dir, GLib.FileTest.EXISTS)) { config_dir = GLib.Environment.get_user_config_dir () + "/corebird/"; } } return config_dir + path; } private void create_folder (string path) { if (FileUtils.test (path, FileTest.EXISTS)) return; try { bool success = File.new_for_path (path) .make_directory (); if (!success) { critical("Couldn't create user folder %s", path); } } catch (GLib.Error e) { critical ("%s(%s)", e.message, path); } } } corebird-1.7.4/src/util/TweetUtils.vala000066400000000000000000000210511324604713000200350ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ namespace TweetUtils { /** * Deletes the given tweet. * * @param account The account to delete the tweet from * @param tweet the tweet to delete */ async void delete_tweet (Account account, Cb.Tweet tweet) { var call = account.proxy.new_call (); call.set_method ("POST"); call.set_function ("1.1/statuses/destroy/"+tweet.id.to_string ()+".json"); call.add_param ("id", tweet.id.to_string ()); call.invoke_async.begin (null, (obj, res) => { try { call.invoke_async.end (res);} catch (GLib.Error e) { critical (e.message);} delete_tweet.callback (); }); yield; } /** * (Un)favorites the given tweet. * * @param account The account to (un)favorite from * @param tweet The tweet to (un)favorite * @param status %true to favorite the tweet, %false to unfavorite it. */ async void set_favorite_status (Account account, Cb.Tweet tweet, bool status) { var call = account.proxy.new_call(); if (status) call.set_function ("1.1/favorites/create.json"); else call.set_function ("1.1/favorites/destroy.json"); call.set_method ("POST"); call.add_param ("id", tweet.id.to_string ()); call.invoke_async.begin (null, (obj, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), e.message, GLib.Log.LINE, GLib.Log.FILE); } if (status) tweet.set_flag (Cb.TweetState.FAVORITED); else tweet.unset_flag (Cb.TweetState.FAVORITED); set_favorite_status.callback (); }); yield; } /** * (Un)retweets the given tweet. * * @param account The account to (un)retweet from * @param tweet The tweet to (un)retweet * @param status %true to retweet it, false to unretweet it. */ async void set_retweet_status (Account account, Cb.Tweet tweet, bool status) { var call = account.proxy.new_call (); call.set_method ("POST"); if (status) call.set_function (@"1.1/statuses/retweet/$(tweet.id).json"); else call.set_function (@"1.1/statuses/destroy/$(tweet.my_retweet).json"); debug (Cb.Utils.rest_proxy_call_to_string (call)); call.invoke_async.begin (null, (obj, res) => { try{ call.invoke_async.end (res); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), e.message, GLib.Log.LINE, GLib.Log.FILE); } unowned string back = call.get_payload(); var parser = new Json.Parser (); try { parser.load_from_data (back); if (status) { int64 new_id = parser.get_root ().get_object ().get_int_member ("id"); tweet.my_retweet = new_id; } else { tweet.my_retweet = 0; } if (status) tweet.set_flag (Cb.TweetState.RETWEETED); else tweet.unset_flag (Cb.TweetState.RETWEETED); } catch (GLib.Error e) { critical (e.message); critical (back); } set_retweet_status.callback (); }); yield; } /** * Downloads the avatar from the given url. * * @param avatar_url The avatar url to download * * @return The loaded avatar. */ async Gdk.Pixbuf? download_avatar (string avatar_url, int size = 48, GLib.Cancellable? cancellable = null) throws GLib.Error { Gdk.Pixbuf? avatar = null; var msg = new Soup.Message ("GET", avatar_url); if (cancellable != null) cancellable.cancelled.connect (() => { SOUP_SESSION.cancel_message (msg, Soup.Status.CANCELLED); }); GLib.Error? err = null; SOUP_SESSION.queue_message (msg, (s, _msg) => { if (_msg.status_code != Soup.Status.OK) { avatar = null; download_avatar.callback (); return; } var memory_stream = new MemoryInputStream.from_data(_msg.response_body.data, GLib.g_free); try { avatar = new Gdk.Pixbuf.from_stream_at_scale (memory_stream, size, size, false); } catch (GLib.Error e) { err = e; } download_avatar.callback (); }); yield; if (err != null) { throw err; } return avatar; } bool activate_link (string uri, MainWindow window) { debug ("Activating '%s'", uri); uri = uri._strip (); string term = uri.substring (1); if (uri.has_prefix ("@")) { int slash_index = uri.index_of ("/"); var bundle = new Cb.Bundle (); if (slash_index == -1) { bundle.put_int64 (ProfilePage.KEY_USER_ID, int64.parse (term)); window.main_widget.switch_page (Page.PROFILE, bundle); } else { bundle.put_int64 (ProfilePage.KEY_USER_ID, int64.parse (term.substring (0, slash_index - 1))); bundle.put_string (ProfilePage.KEY_SCREEN_NAME, term.substring (slash_index + 1, term.length - slash_index - 1)); window.main_widget.switch_page (Page.PROFILE, bundle); } return true; } else if (uri.has_prefix ("#")) { var bundle = new Cb.Bundle (); bundle.put_string (SearchPage.KEY_QUERY, uri); window.main_widget.switch_page (Page.SEARCH, bundle); return true; } else if (uri.has_prefix ("https://twitter.com/")) { // https://twitter.com/baedert/status/321423423423 string[] parts = uri.split ("/"); if (parts[4] == "status") { /* Treat it as a tweet link and hope it'll work out */ int64 tweet_id = int64.parse (parts[5]); var bundle = new Cb.Bundle (); bundle.put_int (TweetInfoPage.KEY_MODE, TweetInfoPage.BY_ID); bundle.put_int64 (TweetInfoPage.KEY_TWEET_ID, tweet_id); bundle.put_string (TweetInfoPage.KEY_SCREEN_NAME, parts[3]); window.main_widget.switch_page (Page.TWEET_INFO, bundle); return true; } } return false; } void work_array (Json.Array json_array, TweetListBox tweet_list, Account account) { uint n_tweets = json_array.get_length (); /* If the request returned no results at all, we don't need to do all the later stuff */ if (n_tweets == 0) { return; } var now = new GLib.DateTime.now_local (); for (uint i = 0; i < n_tweets; i++) { var tweet = new Cb.Tweet (); tweet.load_from_json (json_array.get_element (i), account.id, now); if (account.user_counter == null || tweet_list == null || !(tweet_list.get_toplevel () is Gtk.Window)) break; account.user_counter.id_seen (ref tweet.source_tweet.author); if (tweet.retweeted_tweet != null) account.user_counter.id_seen (ref tweet.retweeted_tweet.author); if (account.filter_matches (tweet)) tweet.set_flag (Cb.TweetState.HIDDEN_FILTERED); tweet_list.model.add (tweet); } } public void handle_media_click (Cb.Tweet t, MainWindow window, int index, double px = 0.0, double py = 0.0) { MediaDialog media_dialog = new MediaDialog (t, index, px, py); media_dialog.set_transient_for (window); media_dialog.set_modal (true); media_dialog.show (); } public void sort_entities (ref Cb.TextEntity[] entities) { /* Just use bubblesort here. Our n is very small (< 15 maybe?) */ for (int i = 0; i < entities.length; i ++) { for (int k = 0; k < entities.length; k ++) { if (entities[i].from < entities[k].from) { Cb.TextEntity c = entities[i]; entities[i] = entities[k]; entities[k] = c; } } } } } corebird-1.7.4/src/util/UserCompletion.vala000066400000000000000000000035321324604713000207000ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class UserCompletion : GLib.Object { public signal void start_completion (); public signal void populate_completion (string screen_name, string name); private unowned GLib.Object obj; private unowned Account account; private string name_property_name; private int num_results; public UserCompletion (Account account, int num_results) { this.account = account; this.num_results = num_results; } public void connect_to (GLib.Object obj, string name_property_name) { this.obj = obj; this.name_property_name = name_property_name; obj.notify[name_property_name].connect (prop_changed); } private void prop_changed () { string name; obj.get (name_property_name, out name); if (name.has_prefix ("@")) name = name.substring (1); start_completion (); Cb.UserInfo[] names; account.user_counter.query_by_prefix (account.db.get_sqlite_db (), name, 10, out names); for (int i = 0; i < names.length; i++) populate_completion (names[i].screen_name, names[i].user_name); } } corebird-1.7.4/src/util/UserUtils.vala000066400000000000000000000121611324604713000176650ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ const uint FRIENDSHIP_FOLLOWED_BY = 1 << 0; const uint FRIENDSHIP_FOLLOWING = 1 << 1; const uint FRIENDSHIP_WANT_RETWEETS = 1 << 2; const uint FRIENDSHIP_BLOCKING = 1 << 3; const uint FRIENDSHIP_CAN_DM = 1 << 4; struct Cursor { int64 next_cursor; bool full; Json.Node? json_object; } namespace UserUtils { async uint load_friendship (Account account, int64 user_id, string screen_name) { var call = account.proxy.new_call (); call.set_function ("1.1/friendships/show.json"); call.set_method ("GET"); call.add_param ("source_id", account.id.to_string ()); if (user_id != 0) call.add_param ("target_id", user_id.to_string ()); else call.add_param ("target_screen_name", screen_name); Json.Node? root = null; try { root = yield Cb.Utils.load_threaded_async (call, null); } catch (GLib.Error e) { warning (e.message); return 0; } var relationship = root.get_object ().get_object_member ("relationship"); var target = relationship.get_object_member ("target"); var source = relationship.get_object_member ("source"); uint friendship = 0; if (target.get_boolean_member ("following")) friendship |= FRIENDSHIP_FOLLOWED_BY; if (target.get_boolean_member ("followed_by")) friendship |= FRIENDSHIP_FOLLOWING; if (source.get_boolean_member ("want_retweets")) friendship |= FRIENDSHIP_WANT_RETWEETS; if (source.get_boolean_member ("blocking")) friendship |= FRIENDSHIP_BLOCKING; if (source.get_boolean_member ("can_dm")) friendship |= FRIENDSHIP_CAN_DM; return friendship; } async Cursor? load_followers (Account account, int64 user_id, Cursor? old_cursor) { const int requested = 25; var call = account.proxy.new_call (); call.set_function ("1.1/followers/list.json"); call.set_method ("GET"); call.add_param ("user_id", user_id.to_string ()); call.add_param ("count", requested.to_string ()); call.add_param ("skip_status", "true"); call.add_param ("include_user_entities", "false"); if (old_cursor != null) call.add_param ("cursor", old_cursor.next_cursor.to_string ()); Json.Node? root = null; try { root = yield Cb.Utils.load_threaded_async (call, null); } catch (GLib.Error e) { warning (e.message); return null; } var root_obj = root.get_object (); var user_array = root_obj.get_array_member ("users"); Cursor cursor = Cursor (); cursor.next_cursor = root_obj.get_int_member ("next_cursor"); cursor.full = (user_array.get_length () < requested); cursor.json_object = root_obj.get_member ("users"); return cursor; } async Cursor? load_following (Account account, int64 user_id, Cursor? old_cursor) { const int requested = 25; var call = account.proxy.new_call (); call.set_function ("1.1/friends/list.json"); call.set_method ("GET"); call.add_param ("user_id", user_id.to_string ()); call.add_param ("count", requested.to_string ()); call.add_param ("skip_status", "true"); call.add_param ("include_user_entities", "false"); if (old_cursor != null) call.add_param ("cursor", old_cursor.next_cursor.to_string ()); Json.Node? root = null; try { root = yield Cb.Utils.load_threaded_async (call, null); } catch (GLib.Error e) { warning (e.message); return null; } var root_obj = root.get_object (); var user_array = root_obj.get_array_member ("users"); Cursor cursor = Cursor (); cursor.next_cursor = root_obj.get_int_member ("next_cursor"); cursor.full = (user_array.get_length () < requested); cursor.json_object = root_obj.get_member ("users"); return cursor; } async void mute_user (Account account, int64 to_block, bool setting) { var call = account.proxy.new_call (); call.set_method ("POST"); if (setting) call.set_function ("1.1/mutes/users/create.json"); else call.set_function ("1.1/mutes/users/destroy.json"); call.add_param ("user_id", to_block.to_string ()); try { yield call.invoke_async (null); } catch (GLib.Error e) { critical (e.message); } } } corebird-1.7.4/src/util/Utils.vala000066400000000000000000000274371324604713000170420ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ enum Page { STREAM = 0, MENTIONS, FAVORITES, DM_THREADS, LISTS, FILTERS, SEARCH, PROFILE, TWEET_INFO, DM, LIST_STATUSES, PREVIOUS = 1024, NEXT = 2048 } static Soup.Session SOUP_SESSION = null; const int TRANSITION_DURATION = 200 * 1000; #if DEBUG public unowned string __class_name (GLib.Object o) { return GLib.Type.from_instance (o).name (); } #endif void default_header_func (Gtk.ListBoxRow row, Gtk.ListBoxRow? row_before) { if (row_before == null) { row.set_header (null); return; } Gtk.Widget? header = row.get_header (); if (header != null) { return; } header = new Gtk.Separator (Gtk.Orientation.HORIZONTAL); header.show (); row.set_header (header); } int twitter_item_sort_func (Gtk.ListBoxRow a, Gtk.ListBoxRow b) { if(((Cb.TwitterItem)a).get_sort_factor () < ((Cb.TwitterItem)b).get_sort_factor ()) return 1; return -1; } int twitter_item_sort_func_inv (Gtk.ListBoxRow a, Gtk.ListBoxRow b) { if(((Cb.TwitterItem)a).get_sort_factor () < ((Cb.TwitterItem)b).get_sort_factor ()) return -1; return 1; } Cairo.Surface? load_surface (string path) { try { var p = new Gdk.Pixbuf.from_file (path); var s = Gdk.cairo_surface_create_from_pixbuf (p, 1, null); return s; } catch (GLib.Error e) { warning (e.message); return null; } } void write_surface (Cairo.Surface surface, string path) { var status = surface.write_to_png (path); if (status != Cairo.Status.SUCCESS) { warning ("Could not write surface to '%s': %s", path, status.to_string ()); } } Cairo.Surface scale_surface (Cairo.ImageSurface input, int output_width, int output_height) { int old_width = input.get_width (); int old_height = input.get_height (); if (old_width == output_width && old_height == output_height) return input; Cairo.Surface new_surface = new Cairo.Surface.similar_image (input, Cairo.Format.ARGB32, output_width, output_height); /* http://lists.cairographics.org/archives/cairo/2006-January/006178.html */ Cairo.Context ct = new Cairo.Context (new_surface); ct.scale ((double)output_width / old_width, (double)output_height / old_height); ct.set_source_surface (input, 0, 0); ct.get_source ().set_extend (Cairo.Extend.PAD); ct.set_operator (Cairo.Operator.SOURCE); ct.paint (); return new_surface; } inline double ease_out_cubic (double t) { double p = t - 1; return p * p * p +1; } namespace Utils { /** * Calculates an easily human-readable version of the time difference between * time and now. * Example: "5m" or "3h" or "26m" or "16 Nov" */ public string get_time_delta (GLib.DateTime time, GLib.DateTime now) { //diff is the time difference in microseconds GLib.TimeSpan diff = now.difference (time); int minutes = (int)(diff / 1000.0 / 1000.0 / 60.0); if (minutes == 0) return _("Now"); else if (minutes < 60) return _("%dm").printf (minutes); int hours = (int)(minutes / 60.0); if (hours < 24) return _("%dh").printf (hours); string month = time.format ("%b"); if (time.get_year () == now.get_year ()) { //If 'time' was over 24 hours ago, we just return that return "%d %s".printf (time.get_day_of_month (), month); } else { return "%d %s %d".printf (time.get_day_of_month (), month, time.get_year ()); } } /** * Shows an error dialog with the given error message * * @param message The error message to show */ void show_error_dialog (string message, Gtk.Window? transient_for) { var dialog = new Gtk.MessageDialog (transient_for, Gtk.DialogFlags.DESTROY_WITH_PARENT, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, "%s", message); /* Hacky way to get the label selectable */ ((Gtk.Label)(((Gtk.Container)dialog.get_message_area ()).get_children ().nth_data (0))).set_selectable (true); dialog.response.connect ((id) => { if (id == Gtk.ResponseType.OK) dialog.destroy (); }); dialog.show (); } /** * Shows the given json error object in an error dialog. * Example object data: * {"errors":[{"message":"Could not authenticate you","code":32}] * * @param json_data The json data to show * @param alternative If the given json data is not valid, * show this alternative error message. */ void show_error_object (string? json_data, string alternative, int line, string file, Gtk.Window? transient_for = null) { string error_message = "Exception: %s in %s:%d".printf (alternative, file, line); if (json_data == null) { show_error_dialog (error_message, transient_for); return; } var parser = new Json.Parser (); StringBuilder sb = new StringBuilder (); try { parser.load_from_data (json_data); } catch (GLib.Error e) { show_error_dialog (error_message, transient_for); return; } if (parser.get_root ().get_node_type () != Json.NodeType.OBJECT) { show_error_dialog (error_message, transient_for); return; } var root = parser.get_root ().get_object (); if (root.has_member ("error") && root.get_member ("error").get_node_type () == Json.NodeType.VALUE) { message (json_data); show_error_dialog (root.get_member ("error").get_string (), transient_for); return; } if (root.has_member ("errors") && root.get_member ("errors").get_node_type () == Json.NodeType.VALUE) { message (json_data); show_error_dialog (root.get_member ("errors").get_string (), transient_for); return; } if (!root.has_member ("errors")) { show_error_dialog (error_message, transient_for); return; } var errors = root.get_array_member ("errors"); if (errors.get_length () == 1) { var err = errors.get_object_element (0); sb.append (err.get_int_member ("code").to_string ()).append (": ") .append (err.get_string_member ("message")) .append ("(").append (file).append (":").append (line.to_string ()).append (")"); } else if (errors.get_length () > 1) { sb.append ("
    "); errors.foreach_element ((arr, index, node) => { var obj = node.get_object (); sb.append ("
  • ").append (obj.get_int_member ("code").to_string ()) .append (": ") .append (obj.get_string_member ("message")).append ("
  • "); }); sb.append ("
"); } error_message = sb.str; critical (json_data); show_error_dialog (error_message, transient_for); } async Gdk.Pixbuf? download_pixbuf (string url, GLib.Cancellable? cancellable = null) { Gdk.Pixbuf? result = null; var msg = new Soup.Message ("GET", url); GLib.SourceFunc cb = download_pixbuf.callback; SOUP_SESSION.queue_message (msg, (_s, _msg) => { if (cancellable.is_cancelled ()) { cb (); return; } try { var in_stream = new MemoryInputStream.from_data (_msg.response_body.data, GLib.g_free); result = new Gdk.Pixbuf.from_stream (in_stream, cancellable); } catch (GLib.Error e) { warning (e.message); } finally { cb (); } }); yield; return result; } string unescape_html (string input) { string back = input.replace ("<", "<"); back = back.replace (">", ">"); back = back.replace ("&", "&"); return back; } public void load_custom_icons () { var icon_theme = Gtk.IconTheme.get_default (); icon_theme.add_resource_path ("/org/baedert/corebird/data/"); } public void load_custom_css () { var provider = new Gtk.CssProvider (); provider.load_from_resource ("/org/baedert/corebird/ui/style.css"); Gtk.StyleContext.add_provider_for_screen ((!)Gdk.Screen.get_default (), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); } public void init_soup_session () { assert (SOUP_SESSION == null); SOUP_SESSION = new Soup.Session (); } string capitalize (string s) { string back = s; if (s.get_char (0).islower ()) { back = s.get_char (0).toupper ().to_string () + s.substring (1); } return back; } /** * Checks if @value is existing in @node and if it is, non-null. * * Returns TRUE if the @value does both exist and is non-null. */ public bool usable_json_value (Json.Object node, string value_name) { if (!node.has_member (value_name)) return false; return !node.get_null_member (value_name); } public void update_startup_account (string old_screen_name, string new_screen_name) { string[] startup_accounts = Settings.get ().get_strv ("startup-accounts"); string[] new_startup_accounts = new string[startup_accounts.length]; for (int i = 0; i < startup_accounts.length; i ++) { if (startup_accounts[i] != old_screen_name) new_startup_accounts[i] = startup_accounts[i]; else new_startup_accounts[i] = new_screen_name; } Settings.get ().set_strv ("startup-accounts", new_startup_accounts); } public Cb.Filter create_persistent_filter (string content, Account account) { int id = (int)account.db.insert ("filters") .val ("content", content) .run(); Cb.Filter f = new Cb.Filter (content); f.set_id (id); account.add_filter (f); return f; } public string get_media_display_name (Cb.Media media) { unowned string url = media.target_url ?? media.url; int last_slash_index = url.last_index_of_char ('/'); string filename = url.substring (last_slash_index + 1); filename = filename.replace (":orig", ""); int last_dot_index = filename.last_index_of_char ('.'); if (last_dot_index == -1) { // No file extension, guess! if (media.is_video ()) { filename += ".mp4"; } else { filename += ".jpg"; } } return filename; } public async void download_file (string url, GLib.OutputStream out_stream) { var msg = new Soup.Message ("GET", url); GLib.SourceFunc cb = download_file.callback; SOUP_SESSION.queue_message (msg, (_s, _msg) => { try { var in_stream = new MemoryInputStream.from_data (_msg.response_body.data, GLib.g_free); out_stream.splice (in_stream, GLib.OutputStreamSpliceFlags.CLOSE_SOURCE | GLib.OutputStreamSpliceFlags.CLOSE_TARGET, null); } catch (GLib.Error e) { warning (e.message); } finally { cb (); } }); yield; } } corebird-1.7.4/src/widgets/000077500000000000000000000000001324604713000155515ustar00rootroot00000000000000corebird-1.7.4/src/widgets/AccountCreateWidget.vala000066400000000000000000000124531324604713000223070ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/account-create-widget.ui")] class AccountCreateWidget : Gtk.Box { [GtkChild] private Gtk.Entry pin_entry; [GtkChild] private Gtk.Label error_label; [GtkChild] private Gtk.Button confirm_button; [GtkChild] private Gtk.Button request_pin_button; [GtkChild] private Gtk.Label info_label; [GtkChild] private Gtk.Stack content_stack; private unowned Account acc; private unowned Corebird corebird; private unowned MainWindow main_window; public signal void result_received (bool result, Account acc); public AccountCreateWidget (Account acc, Corebird corebird, MainWindow main_window) { this.acc = acc; this.corebird = corebird; this.main_window = main_window; info_label.label = "%s %s" .printf (_("Don’t have a Twitter account yet?"), _("Create one")); pin_entry.buffer.deleted_text.connect (pin_changed_cb); pin_entry.buffer.inserted_text.connect (pin_changed_cb); } public void open_pin_request_site () { acc.init_proxy (false, true); acc.proxy.request_token_async.begin ("oauth/request_token", "oob", null, (obj, res) => { try { acc.proxy.request_token_async.end (res); } catch (GLib.Error e) { if (e.message.down() == "unauthorized") { Utils.show_error_dialog (_("Unauthorized. Most of the time, this means that there’s something wrong with the Twitter servers and you should try again later"), this.main_window); } else { Utils.show_error_dialog (e.message, this.main_window); } critical (e.message); return; } string uri = "http://twitter.com/oauth/authorize?oauth_token=" + acc.proxy.get_token(); debug ("Trying to open %s", uri); try { GLib.AppInfo.launch_default_for_uri (uri, null); } catch (GLib.Error e) { this.show_error (_("Could not open %s").printf ("" + uri + "")); Utils.show_error_dialog (e.message, this.main_window); critical ("Could not open %s", uri); critical (e.message); } }); } [GtkCallback] private void request_pin_clicked_cb () { open_pin_request_site (); content_stack.visible_child_name = "pin"; } [GtkCallback] private async void confirm_button_clicked_cb () { pin_entry.sensitive = false; confirm_button.sensitive = false; request_pin_button.sensitive = false; this.do_confirm.begin (); } private async void do_confirm () { try { yield acc.proxy.access_token_async ("oauth/access_token", pin_entry.get_text (), null); } catch (GLib.Error e) { critical (e.message); // We just assume that it was the wrong code show_error (_("Wrong PIN")); pin_entry.sensitive = true; confirm_button.sensitive = true; request_pin_button.sensitive = true; return; } var call = acc.proxy.new_call (); call.set_function ("1.1/account/settings.json"); call.set_method ("GET"); Json.Node? root_node; try { root_node = yield Cb.Utils.load_threaded_async (call, null); } catch (GLib.Error e) { warning ("Could not get json data: %s", e.message); return; } Json.Object root = root_node.get_object (); string screen_name = root.get_string_member ("screen_name"); debug ("Checking for %s", screen_name); Account? existing_account = Account.query_account (screen_name); if (existing_account != null) { result_received (false, existing_account); critical ("Account is already in use"); show_error (_("Account already in use")); pin_entry.sensitive = true; pin_entry.text = ""; request_pin_button.sensitive = true; return; } yield acc.query_user_info_by_screen_name (screen_name); debug ("user info call"); acc.init_database (); acc.save_info(); acc.db.insert ("common") .val ("token", acc.proxy.token) .val ("token_secret", acc.proxy.token_secret) .run (); acc.init_proxy (true, true); corebird.account_added (acc); result_received (true, acc); } private void show_error (string err) { info_label.visible = false; error_label.visible = true; error_label.label = err; } private void pin_changed_cb () { string text = pin_entry.get_text (); bool confirm_possible = text.length > 0 && acc.proxy != null; confirm_button.sensitive = confirm_possible; } [GtkCallback] private bool delete_event_cb () { Account.remove_account (Account.DUMMY); return false; } } corebird-1.7.4/src/widgets/AddImageButton.vala000066400000000000000000000133571324604713000212560ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class AddImageButton : Gtk.Widget { private const int MIN_WIDTH = 40; private const int MAX_HEIGHT = 150; private const int MIN_HEIGHT = 100; private const int ICON_SIZE = 32; public string image_path; public Cairo.ImageSurface? surface; public signal void deleted (); private double delete_factor = 1.0; private uint64 delete_transition_start; construct { this.set_has_window (false); } public void get_draw_size (out int width, out int height, out double scale) { if (this.surface == null) { width = 0; height = 0; scale = 0.0; return; } width = this.get_allocated_width (); height = this.get_allocated_height (); double scale_x = (double)width / this.surface.get_width (); double scale_y = (double)height / this.surface.get_height (); scale = double.min (double.min (scale_x, scale_y), 1.0) * delete_factor; width = (int)(this.surface.get_width () * scale); height = (int)(this.surface.get_height () * scale); } public override bool draw (Cairo.Context ct) { int widget_width = get_allocated_width (); int widget_height = get_allocated_height (); var style_context = this.get_style_context (); /* Draw thumbnail */ if (this.surface != null) { ct.save (); ct.rectangle (0, 0, widget_width, widget_height); int draw_width, draw_height; double scale; this.get_draw_size (out draw_width, out draw_height, out scale); if (draw_width > 0 && draw_height > 0) { ct.scale (scale, scale); ct.set_source_surface (this.surface, 0, 0); ct.fill (); } ct.restore (); style_context.render_check (ct, (draw_width / 2.0) - (ICON_SIZE / 2.0), (draw_height / 2.0) - (ICON_SIZE / 2.0), ICON_SIZE, ICON_SIZE); } return Gdk.EVENT_PROPAGATE; } public override Gtk.SizeRequestMode get_request_mode () { return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH; } public override void get_preferred_height_for_width (int width, out int minimum, out int natural) { int media_width; int media_height; if (this.surface == null) { media_width = MIN_WIDTH; media_height = MAX_HEIGHT; } else { media_width = this.surface.get_width (); media_height = this.surface.get_height (); } double width_ratio = (double)width / (double) media_width; int height = int.min (media_height, (int)(media_height * width_ratio)); height = int.min (MAX_HEIGHT, height); minimum = MIN_HEIGHT; natural = int.max (minimum, (int)(height * this.delete_factor)); } public override void get_preferred_width_for_height (int height, out int minimum, out int natural) { int media_width; int media_height; if (this.surface == null) { media_width = MIN_WIDTH; media_height = MAX_HEIGHT; } else { media_width = this.surface.get_width (); media_height = this.surface.get_height (); } double height_ratio = (double)height / (double) media_height; int width = int.min (media_width, (int)(media_width * height_ratio)); width = int.max (MIN_WIDTH, width); minimum = natural = (int)(width * this.delete_factor); } public override void get_preferred_width (out int minimum, out int natural) { int media_width; if (this.surface == null) { media_width = 1; } else { media_width = this.surface.get_width (); } minimum = (int)(int.min (media_width, MIN_WIDTH) * delete_factor); natural = (int)(media_width * delete_factor); } public override void get_preferred_height (out int minimum, out int natural) { int media_height; if (this.surface == null) { media_height = 1; } else { media_height = this.surface.get_height (); } minimum = (int)(int.min (media_height, MIN_HEIGHT) * delete_factor); natural = (int)(media_height * delete_factor); } private bool delete_tick_cb (Gtk.Widget widget, Gdk.FrameClock frame_clock) { uint64 now = frame_clock.get_frame_time (); double t = (now - this.delete_transition_start) / (double)(TRANSITION_DURATION* 1); t = ease_out_cubic (t); this.delete_factor = 1.0 - t; this.queue_resize (); if (t >= 1.0) { this.delete_factor = 1.0; this.deleted (); return GLib.Source.REMOVE; } return GLib.Source.CONTINUE; } public void start_remove () { if (!this.get_realized ()) return; this.delete_transition_start = this.get_frame_clock ().get_frame_time (); this.add_tick_callback (delete_tick_cb); } } corebird-1.7.4/src/widgets/AspectImage.vala000066400000000000000000000075611324604713000206110ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class AspectImage : Gtk.Widget { public Gdk.Pixbuf pixbuf { set { if (value != null) { if (value != Twitter.no_banner) { start_animation (); } if (this.pixbuf_surface != null) this.old_surface = this.pixbuf_surface; this.pixbuf_surface = (Cairo.ImageSurface)Gdk.cairo_surface_create_from_pixbuf (value, 1, this.get_window ()); bg_color.alpha = 0.0; } this.queue_draw (); } } public string color_string { set { bg_color.parse (value); } } private Gdk.RGBA bg_color; private Cairo.Surface? old_surface; private Cairo.ImageSurface? pixbuf_surface = null; public AspectImage () {} construct { set_has_window (false); } private void start_animation () { if (!this.get_realized ()) return; alpha = 0.0; in_transition = true; this.start_time = this.get_frame_clock ().get_frame_time (); this.add_tick_callback (fade_in_cb); } private double alpha = 0.0; private int64 start_time; private bool in_transition = false; private bool fade_in_cb (Gtk.Widget widget, Gdk.FrameClock frame_clock) { int64 now = frame_clock.get_frame_time (); double t = (double)(now - start_time) / TRANSITION_DURATION; if (t >= 1.0) { t = 1.0; in_transition = false; } this.alpha = ease_out_cubic (t); this.queue_draw (); return t < 1.0; } public override void get_preferred_height_for_width (int width, out int min_height, out int nat_height) { if (pixbuf_surface == null) { min_height = 0; nat_height = 1; return; } min_height = pixbuf_surface.get_height (); nat_height = pixbuf_surface.get_height (); } public override Gtk.SizeRequestMode get_request_mode () { return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH; } public override bool draw (Cairo.Context ct) { int width = get_allocated_width (); int height = get_allocated_height (); double scale_x = 1.0; if (bg_color.alpha == 0.0) scale_x = width / (double)pixbuf_surface.get_width (); scale_x = double.max (scale_x, 1.0); ct.rectangle (0, 0, width, height); ct.scale (scale_x, 1.0); ct.push_group (); if (this.old_surface != null) { ct.set_source_surface (this.old_surface, 0, 0); ct.paint (); } else if (bg_color.alpha > 0.0) { ct.set_source_rgba (bg_color.red, bg_color.green, bg_color.blue, bg_color.alpha); ct.fill (); }else alpha = 1.0; if (bg_color.alpha == 0.0) { int x = (int)(width - (pixbuf_surface.get_width () * scale_x)) / 2; ct.set_source_surface (this.pixbuf_surface, x, 0); } else ct.set_source_rgba (bg_color.red, bg_color.green, bg_color.blue, bg_color.alpha); if (in_transition) ct.paint_with_alpha (alpha); else ct.paint (); ct.pop_group_to_source (); ct.set_operator (Cairo.Operator.OVER); ct.paint (); return Gdk.EVENT_PROPAGATE; } } corebird-1.7.4/src/widgets/AvatarBannerWidget.vala000066400000000000000000000120721324604713000221300ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public class AvatarBannerWidget : Gtk.Container { private const int MIN_HEIGHT = 200; private const int MAX_HEIGHT = 250; private const double BANNER_RATIO = 0.5; /* 320/640 */ private const int AVATAR_SIZE = 48; private unowned Account account; private PixbufButton set_banner_button; private PixbufButton set_avatar_button; public signal void avatar_changed (Gdk.Pixbuf new_avatar); public signal void banner_changed (Gdk.Pixbuf new_banner); public signal void avatar_clicked (); public signal void banner_clicked (); construct { this.set_has_window (false); get_style_context ().add_class ("avatar"); /* set_banner_button */ this.set_banner_button = new PixbufButton (); set_banner_button.show_all (); set_banner_button.clicked.connect (banner_clicked_cb); this.add (set_banner_button); /* set_avatar_button */ this.set_avatar_button = new PixbufButton (); set_avatar_button.show_all (); set_avatar_button.clicked.connect (avatar_clicked_cb); this.add (set_avatar_button); Settings.get ().bind ("round-avatars", set_avatar_button, "round", GLib.SettingsBindFlags.DEFAULT); } public void set_account (Account account) { this.account = account; fetch_banner.begin (); this.queue_draw (); set_avatar_button.set_bg ((Cairo.ImageSurface)account.avatar); } public override bool draw (Cairo.Context ct) { this.propagate_draw (set_banner_button, ct); this.propagate_draw (set_avatar_button, ct); return Gdk.EVENT_PROPAGATE; } private int get_avatar_x () { return (get_allocated_width () / 2) - (AVATAR_SIZE / 2); } private int get_avatar_y () { return get_allocated_height () - AVATAR_SIZE; } public override Gtk.SizeRequestMode get_request_mode () { return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH; } public override void get_preferred_width (out int min, out int nat) { min = AVATAR_SIZE + 40; // 20px margin on either side nat = (int)(MIN_HEIGHT * (1 / BANNER_RATIO)); } public override void get_preferred_height_for_width (int width, out int min, out int nat) { min = (AVATAR_SIZE / 3) + MIN_HEIGHT; nat = int.max (min, int.min (MAX_HEIGHT, (int)(width * BANNER_RATIO) + (AVATAR_SIZE / 3))); } private async void fetch_banner () { if (account.banner_url == null) { set_banner_button.set_pixbuf (Twitter.no_banner); return; } var pixbuf = yield Utils.download_pixbuf (account.banner_url + "/600x200"); this.set_banner_button.set_pixbuf (pixbuf); } public override void size_allocate (Gtk.Allocation allocation) { base.size_allocate (allocation); Gtk.Requisition child_requisition; Gtk.Allocation child_allocation = Gtk.Allocation(); /* set_banner_button */ set_banner_button.get_preferred_size (out child_requisition, null); child_allocation.x = allocation.x; child_allocation.y = allocation.y; child_allocation.width = int.max (allocation.width, child_requisition.width); child_allocation.height = (int)(allocation.width * BANNER_RATIO); child_allocation.height = int.max (allocation.height - (AVATAR_SIZE / 2), child_requisition.height); set_banner_button.size_allocate (child_allocation); /* set_avatar_button */ set_avatar_button.get_preferred_size (out child_requisition, null); child_allocation.x = get_avatar_x () + allocation.x; child_allocation.y = get_avatar_y () + allocation.y; child_allocation.width = AVATAR_SIZE; child_allocation.height = AVATAR_SIZE; set_avatar_button.size_allocate (child_allocation); } public override void add (Gtk.Widget w) { w.set_parent (this); } public override void remove (Gtk.Widget w) { w.unparent (); } public override void forall_internal (bool include_internals, Gtk.Callback cb) { cb (set_banner_button); cb (set_avatar_button); } private void banner_clicked_cb () { this.banner_clicked (); } private void avatar_clicked_cb () { this.avatar_clicked (); } public void set_avatar (Gdk.Pixbuf avatar) { set_avatar_button.set_pixbuf (avatar); } public void set_banner (Gdk.Pixbuf banner) { set_banner_button.set_pixbuf (banner); } } corebird-1.7.4/src/widgets/AvatarWidget.vala000066400000000000000000000145711324604713000210100ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public class AvatarWidget : Gtk.Widget { private const int SMALL = 0; private const int LARGE = 1; private const int OVERLAP_DIST = 40; private bool _round = true; public bool make_round { get { return _round; } set { if (value == _round) return; if (value) { this.get_style_context ().add_class ("avatar-round"); } else { this.get_style_context ().remove_class ("avatar-round"); } this._round = value; this.queue_draw (); } } public bool verified { get; set; default = false; } public bool overlap { get; set; default = false; } public int size { get; set; default = 48; } private Cairo.ImageSurface _surface; public Cairo.Surface surface { get { return _surface; } set { if (this._surface == value) return; bool animate = false; if (this._surface != null) Twitter.get ().unref_avatar (this._surface); else animate = true; this._surface = (Cairo.ImageSurface)value; if (this._surface != null) { Twitter.get ().ref_avatar (this._surface); if (animate) this.start_animation (); } this.queue_draw (); } } private double alpha = 1.0f; private int64 start_time; static Cairo.Surface[] verified_icons; const int[] VERIFIED_SIZES = {12, 25}; static construct { try { verified_icons = { Gdk.cairo_surface_create_from_pixbuf ( new Gdk.Pixbuf.from_resource ("/org/baedert/corebird/data/verified-small.png"), 1, null), Gdk.cairo_surface_create_from_pixbuf ( new Gdk.Pixbuf.from_resource ("/org/baedert/corebird/data/verified-large.png"), 1, null), Gdk.cairo_surface_create_from_pixbuf ( new Gdk.Pixbuf.from_resource ("/org/baedert/corebird/data/verified-small@2.png"), 2, null), Gdk.cairo_surface_create_from_pixbuf ( new Gdk.Pixbuf.from_resource ("/org/baedert/corebird/data/verified-large@2.png"), 2, null) }; } catch (GLib.Error e) { critical (e.message); } } construct { this.set_has_window (false); Settings.get ().bind ("round-avatars", this, "make_round", GLib.SettingsBindFlags.DEFAULT); this.get_style_context ().add_class ("avatar"); this.get_style_context ().add_class ("avatar-round"); // default is TRUE } ~AvatarWidget () { if (this._surface != null) Twitter.get ().unref_avatar (this._surface); } private void start_animation () { if (!this.get_realized ()) return; alpha = 0.0; this.start_time = this.get_frame_clock ().get_frame_time (); this.add_tick_callback (fade_in_cb); } private bool fade_in_cb (Gtk.Widget widget, Gdk.FrameClock frame_clock) { int64 now = frame_clock.get_frame_time (); double t = (now - start_time) / (double) TRANSITION_DURATION; if (t >= 1.0) { t = 1.0; } this.alpha = ease_out_cubic (t); this.queue_draw (); return t < 1.0; } public override bool draw (Cairo.Context ctx) { int width = this.size; int height = this.size; if (this._surface == null) { return Gdk.EVENT_PROPAGATE; } double surface_scale; this._surface.get_device_scale (out surface_scale, out surface_scale); if (width != height) { warning ("Avatar with mapped with width %d and height %d", width, height); } var surface = new Cairo.Surface.similar (ctx.get_target (), Cairo.Content.COLOR_ALPHA, width, height); var ct = new Cairo.Context (surface); double scale = (double)this.get_allocated_width () / (double) (this._surface.get_width () / surface_scale); ct.rectangle (0, 0, width, height); ct.scale (scale, scale); ct.set_source_surface (this._surface, 0, 0); ct.fill(); int y; if (overlap) y = - OVERLAP_DIST; else y = 0; if (_round) { ct.scale (1.0/scale, 1.0/scale); ct.set_operator (Cairo.Operator.DEST_IN); ct.arc ((width / 2.0), (height / 2.0), (width / 2.0) - 0.5, // Radius 0, //Angle from 2 * Math.PI); // Angle to ct.fill (); this.get_style_context ().render_frame (ctx, 0, y, width, height); } ctx.set_source_surface (surface, 0, y); ctx.paint_with_alpha (alpha); if (verified) { double verified_scale = 1.0; int index = SMALL; if (width > 48) index = LARGE; if (index == LARGE && this._size < 100) { verified_scale = (double)this._size / 100.0; } int scale_factor = this.get_scale_factor () - 1; Cairo.Surface verified_img = verified_icons[scale_factor * 2 + index]; ctx.scale (verified_scale, verified_scale); ctx.set_source_surface (verified_img, (width - (VERIFIED_SIZES[index] * verified_scale)) / verified_scale, y); ctx.paint_with_alpha (this.alpha); } return Gdk.EVENT_PROPAGATE; } public override void size_allocate (Gtk.Allocation alloc) { base.size_allocate (alloc); if (overlap) { alloc.y -= OVERLAP_DIST; alloc.height += OVERLAP_DIST; this.set_clip (alloc); } } public override void get_preferred_width (out int min, out int nat) { min = size; nat = size; } public override void get_preferred_height (out int min, out int nat) { if (overlap) { min = size - OVERLAP_DIST; nat = size - OVERLAP_DIST; } else { min = size; nat = size; } } } corebird-1.7.4/src/widgets/BadgeRadioButton.vala000066400000000000000000000045101324604713000215730ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public class BadgeRadioButton : Gtk.RadioButton { private const int BADGE_SIZE = 10; private bool _show_badge = false; public bool show_badge { set { debug ("New show_badge value: %s", value ? "true" : "false"); if (value != this._show_badge) { this._show_badge = value; this.queue_draw (); } } get { return this._show_badge; } } public BadgeRadioButton (Gtk.RadioButton group, string icon_name, string text="") { GLib.Object (group: group); this.get_style_context ().add_class ("image-button"); var i = new Gtk.Image.from_icon_name (icon_name, Gtk.IconSize.BUTTON); this.add (i); this.set_mode (false); this.focus_on_click = false; this.hexpand = true; if (text != "") { this.tooltip_text = text; Atk.Object accessible = this.get_accessible (); accessible.set_name (text); } } public override bool draw (Cairo.Context ct) { base.draw (ct); if (!show_badge || this.get_child () == null) return Gdk.EVENT_PROPAGATE; Gtk.Allocation child_allocation; Gtk.Allocation allocation; this.get_child ().get_allocation (out child_allocation); this.get_allocation (out allocation); var context = this.get_style_context (); int x = allocation.x - child_allocation.x + child_allocation.width - BADGE_SIZE; int y = 5; context.save (); context.add_class ("badge"); context.render_background (ct, x, y, BADGE_SIZE, BADGE_SIZE); context.render_frame (ct, x, y, BADGE_SIZE, BADGE_SIZE); context.restore (); return Gdk.EVENT_PROPAGATE; } } corebird-1.7.4/src/widgets/CompletionTextView.vala000066400000000000000000000420311324604713000222270ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class CompletionTextView : Gtk.TextView { private const string NO_SPELL_CHECK = "gtksourceview:context-classes:no-spell-check"; private const string[] TEXT_TAGS = { "link", "mention", "hashtag", "snippet" }; private Gtk.Window completion_window; private int current_match = 0; private string? current_word = null; private GLib.Cancellable? completion_cancellable = null; private bool _default_listbox = true; public Gtk.ListBox completion_listbox { set { _default_listbox = false; Cb.Utils.unbind_non_gobject_model (completion_list, completion_model); this.completion_list = value; Cb.Utils.bind_non_gobject_model (completion_list, completion_model, create_completion_row); } } private Gtk.ListBox completion_list; private Cb.UserCompletionModel completion_model; public signal void show_completion (); public signal void hide_completion (); public signal void update_completion (string query); private unowned Account account; construct { completion_window = new Gtk.Window (Gtk.WindowType.POPUP); completion_window.set_type_hint (Gdk.WindowTypeHint.COMBO); completion_window.focus_out_event.connect (completion_window_focus_out_cb); completion_window.set_screen (this.get_screen ()); completion_list = new Gtk.ListBox (); completion_model = new Cb.UserCompletionModel (); Cb.Utils.bind_non_gobject_model (completion_list, completion_model, create_completion_row); var placeholder_label = new Gtk.Label (_("No users found")); placeholder_label.get_style_context ().add_class ("dim-label"); placeholder_label.show (); completion_list.set_placeholder (placeholder_label); var scroller = new Gtk.ScrolledWindow (null, null); scroller.add (completion_list); var frame = new Gtk.Frame (null); frame.add (scroller); completion_window.add (frame); this.focus_out_event.connect (completion_window_focus_out_cb); /* Your theme uses a wildcard for :link, right? */ var style_context = this.get_style_context (); style_context.save (); style_context.set_state (Gtk.StateFlags.LINK); Gdk.RGBA link_color = style_context.get_color (style_context.get_state ()); style_context.restore (); if (link_color.red == 1.0 && link_color.green == 1.0 && link_color.blue == 1.0 && link_color.alpha == 1.0) { /* Unset, fall back to Adwaita's default */ link_color = { 0.16470, 0.462735, 0.77647, 1.0 }; } Gdk.RGBA snippet_color = { 0.0, 0.65, 0.0627, 1.0}; this.buffer.create_tag (TEXT_TAGS[0], "foreground_rgba", link_color, null); this.buffer.create_tag (TEXT_TAGS[1], "foreground_rgba", link_color, null); this.buffer.create_tag (TEXT_TAGS[2], "foreground_rgba", link_color, null); this.buffer.create_tag (TEXT_TAGS[3], "foreground_rgba", snippet_color, null); /* gspell marker */ this.buffer.create_tag (NO_SPELL_CHECK, null); this.buffer.notify["cursor-position"].connect (update_completion_listbox); this.buffer.changed.connect (buffer_changed_cb); this.key_press_event.connect (key_press_event_cb); /* Set them here so they are consistent everywhere */ this.right_margin = 6; this.left_margin = 6; this.top_margin = 6; this.bottom_margin = 6; #if SPELLCHECK var gspell_view = Gspell.TextView.get_from_gtk_text_view (this); gspell_view.set_inline_spell_checking (true); gspell_view.set_enable_language_menu (true); var gspell_buffer = Gspell.TextBuffer.get_from_gtk_text_buffer (this.buffer); var checker = new Gspell.Checker (Gspell.Language.get_default ()); gspell_buffer.set_spell_checker (checker); #endif } public void set_account (Account account) { this.account = account; } private bool insert_snippet () { Gtk.TextIter cursor_word_start; Gtk.TextIter cursor_word_end; string cursor_word = get_cursor_word (out cursor_word_start, out cursor_word_end); /* See the git log for an explanation */ if (cursor_word.get_char (0) == ' ' || cursor_word.get_char (0) == '\t' || cursor_word.get_char (0) == '\n') { cursor_word = cursor_word.substring (1); cursor_word_start.forward_char (); } string? snippet = Corebird.snippet_manager.get_snippet (cursor_word.strip ()); if (snippet == null) { debug ("No snippet for cursor_word '%s' found.", cursor_word); return false; } Gtk.TextIter start_word_iter; this.buffer.freeze_notify (); this.buffer.delete_range (cursor_word_start, cursor_word_end); Gtk.TextMark cursor_mark = this.buffer.get_insert (); this.buffer.get_iter_at_mark (out start_word_iter, cursor_mark); this.buffer.insert_text (ref start_word_iter, snippet, snippet.length); this.buffer.thaw_notify (); return true; } private inline bool snippets_configured () { return Corebird.snippet_manager.n_snippets () > 0; } private void select_completion_row (Gtk.ListBoxRow? row) { if (row == null) return; assert (row.get_parent () == completion_list); Gtk.Allocation alloc; row.get_allocation (out alloc); completion_list.select_row (row); Gtk.Viewport viewport = completion_list.get_parent () as Gtk.Viewport; if (viewport == null) return; Gtk.ScrolledWindow scroller = viewport.get_parent () as Gtk.ScrolledWindow; if (scroller != null) { Gtk.Adjustment adjustment = scroller.get_vadjustment (); adjustment.clamp_page (alloc.y, alloc.y + alloc.height); } } private bool key_press_event_cb (Gdk.EventKey evt) { if (evt.keyval == Gdk.Key.Tab && snippets_configured ()) { return insert_snippet (); } /* If we are not in 'completion mode' atm, just back out. */ if (!completion_list.get_mapped ()) return Gdk.EVENT_PROPAGATE; int n_results = (int)completion_model.get_n_items (); switch (evt.keyval) { case Gdk.Key.Down: if (n_results == 0) return Gdk.EVENT_PROPAGATE; this.current_match = (current_match + 1) % n_results; var row = completion_list.get_row_at_index (current_match); if (_default_listbox) { row.grab_focus (); } select_completion_row (row); return Gdk.EVENT_STOP; case Gdk.Key.Up: current_match --; if (current_match < 0) current_match = n_results - 1; var row = completion_list.get_row_at_index (current_match); if (_default_listbox) { row.grab_focus (); } select_completion_row (row); return Gdk.EVENT_STOP; case Gdk.Key.Return: if (n_results == 0) return Gdk.EVENT_PROPAGATE; if (current_match == -1) current_match = 0; var row = completion_list.get_row_at_index (current_match); assert (row is UserCompletionRow); string compl = ((UserCompletionRow)row).get_screen_name (); insert_completion (compl.substring (1)); current_match = -1; hide_completion_window (); return Gdk.EVENT_STOP; case Gdk.Key.Escape: hide_completion_window (); return Gdk.EVENT_STOP; default: return Gdk.EVENT_PROPAGATE; } } private void buffer_changed_cb () { Gtk.TextIter? start_iter; Gtk.TextIter? end_iter; this.buffer.get_start_iter (out start_iter); this.buffer.get_end_iter (out end_iter); var tag_table = this.buffer.get_tag_table (); /* We can't use gtk_text_buffer_remove_all_tags because that will also remove the ones added by gspell */ for (int i = 0; i < TEXT_TAGS.length; i ++) this.buffer.remove_tag (tag_table.lookup (TEXT_TAGS[i]), start_iter, end_iter); string text = this.buffer.get_text (start_iter, end_iter, true); size_t text_length; var entities = Tl.extract_entities_and_text (text, out text_length); foreach (unowned Tl.Entity e in entities) { Gtk.TextIter? e_start_iter; Gtk.TextIter? e_end_iter; this.buffer.get_iter_at_offset (out e_start_iter, (int)e.start_character_index); this.buffer.get_iter_at_offset (out e_end_iter, (int)(e.start_character_index + e.length_in_characters)); switch (e.type) { case Tl.EntityType.HASHTAG: buffer.apply_tag_by_name (NO_SPELL_CHECK, e_start_iter, e_end_iter); buffer.apply_tag_by_name ("hashtag", e_start_iter, e_end_iter); break; case Tl.EntityType.MENTION: buffer.apply_tag_by_name (NO_SPELL_CHECK, e_start_iter, e_end_iter); buffer.apply_tag_by_name ("mention", e_start_iter, e_end_iter); break; case Tl.EntityType.LINK: buffer.apply_tag_by_name (NO_SPELL_CHECK, e_start_iter, e_end_iter); buffer.apply_tag_by_name ("link", e_start_iter, e_end_iter); break; case Tl.EntityType.TEXT: if (Corebird.snippet_manager.has_snippet_n (e.start, e.length_in_bytes)) { buffer.apply_tag_by_name (NO_SPELL_CHECK, e_start_iter, e_end_iter); buffer.apply_tag_by_name ("snippet", e_start_iter, e_end_iter); } break; default: break; } } if (buffer.text.length == 0) hide_completion_window (); } private void show_completion_window () { if (!this.get_mapped ()) return; completion_model.clear (); if (!_default_listbox) { this.show_completion (); return; } debug ("show_completion_window"); int x, y; Gtk.Allocation alloc; this.get_allocation (out alloc); this.get_window (Gtk.TextWindowType.WIDGET).get_origin (out x, out y); y += alloc.height; /* +2 for the size and -1 for x since we account for the frame size around the text view */ completion_window.set_attached_to (this); completion_window.set_transient_for ((Gtk.Window) this.get_toplevel ()); completion_window.move (x - 1, y); completion_window.resize (alloc.width + 2, 50); completion_window.show_all (); } private void hide_completion_window () { this.current_word = null; if (!_default_listbox) { hide_completion (); return; } completion_window.hide (); } private bool completion_window_focus_out_cb () { if (_default_listbox) { hide_completion_window (); } return false; } private void update_completion_listbox () { string cur_word = get_cursor_word (null, null); int n_chars = cur_word.char_count (); if (n_chars == 0) { hide_completion_window (); return; } /* Check if the word ends with a 'special' character like ?!_ */ char end_char = cur_word.get (n_chars - 1); bool word_has_alpha_end = (end_char.isalpha () || end_char.isdigit ()) && end_char.isgraph () || end_char == '@'; if (!cur_word.has_prefix ("@") || !word_has_alpha_end || this.buffer.has_selection) { hide_completion_window (); return; } // Strip off the @ cur_word = cur_word.substring (1); if (cur_word == null || cur_word.length == 0) { hide_completion_window (); return; } if (cur_word != this.current_word) { if (this.completion_cancellable != null) { debug ("Cancelling earlier completion call..."); this.completion_cancellable.cancel (); } /* Clears the model */ show_completion_window (); /* Query users from local cache */ Cb.UserInfo[] corpus; account.user_counter.query_by_prefix (account.db.get_sqlite_db (), cur_word, 10, out corpus); completion_model.insert_infos (corpus); bool corpus_was_empty = (corpus.length == 0); if (corpus.length > 0) { select_completion_row (completion_list.get_row_at_index (0)); current_match = 0; } corpus = null; /* Make sure we won't use it again */ /* Now also query users from the Twitter server, in case our local cache doesn't have anything worthwhile */ this.completion_cancellable = new GLib.Cancellable (); Cb.Utils.query_users_async.begin (account.proxy, cur_word, completion_cancellable, (obj, res) => { Cb.UserIdentity[] users; try { users = Cb.Utils.query_users_async.end (res); } catch (GLib.Error e) { if (!(e is GLib.IOError.CANCELLED)) warning ("User completion call error: %s", e.message); return; } completion_model.insert_items (users); if (users.length > 0 && corpus_was_empty) { select_completion_row (completion_list.get_row_at_index (0)); current_match = 0; } }); this.current_word = cur_word; completion_list.show_all (); } } private string get_cursor_word (out Gtk.TextIter start_iter, out Gtk.TextIter end_iter) { Gtk.TextMark cursor_mark = this.buffer.get_insert (); Gtk.TextIter cursor_iter; this.buffer.get_iter_at_mark (out cursor_iter, cursor_mark); /* Check if the current "word" is just "@" */ var test_iter = Gtk.TextIter (); test_iter.assign (cursor_iter); for (;;) { Gtk.TextIter left_iter = test_iter; left_iter.assign (test_iter); left_iter.backward_char (); string s = this.buffer.get_text (left_iter, test_iter, false); unichar c = s.get_char (0); assert (s.char_count () == 1 || s.char_count () == 0); if (left_iter.is_start ()) test_iter.assign (left_iter); if (c.isspace() || left_iter.is_start ()) { break; } test_iter.assign (left_iter); } start_iter = test_iter; start_iter.assign (test_iter); end_iter = cursor_iter; end_iter.assign (cursor_iter); return this.buffer.get_text (test_iter, cursor_iter, false); } private void insert_completion (string compl) { this.buffer.freeze_notify (); Gtk.TextIter start_word_iter; Gtk.TextIter end_word_iter; string word_to_delete = get_cursor_word (out start_word_iter, out end_word_iter); debug ("Delete word: %s", word_to_delete); this.buffer.delete_range (start_word_iter, end_word_iter); Gtk.TextMark cursor_mark = this.buffer.get_insert (); this.buffer.get_iter_at_mark (out start_word_iter, cursor_mark); this.buffer.insert_text (ref start_word_iter, "@" + compl + " ", compl.length + 2); this.buffer.thaw_notify (); } private Gtk.Widget create_completion_row (void *id_ptr) { // *shrug* Cb.UserIdentity *id = (Cb.UserIdentity*) id_ptr; var row = new UserCompletionRow (id->id, id->user_name, id->screen_name, id->verified); row.show (); return row; } ~CompletionTextView () { Cb.Utils.unbind_non_gobject_model (completion_list, completion_model); } } class UserCompletionRow : Gtk.ListBoxRow { private static Cairo.Surface verified_surface; private Gtk.Label user_name_label; private Gtk.Label screen_name_label; static construct { try { verified_surface = Gdk.cairo_surface_create_from_pixbuf ( new Gdk.Pixbuf.from_resource ("/org/baedert/corebird/data/verified-small.png"), 1, null); } catch (GLib.Error e) { error (e.message); } } public UserCompletionRow (int64 id, string user_name, string screen_name, bool verified) { user_name_label = new Gtk.Label (user_name); screen_name_label = new Gtk.Label ("@" + screen_name); var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6); user_name_label.set_valign (Gtk.Align.BASELINE); user_name_label.set_use_markup (true); user_name_label.set_ellipsize (Pango.EllipsizeMode.END); box.add (user_name_label); screen_name_label.set_valign (Gtk.Align.BASELINE); screen_name_label.get_style_context ().add_class ("dim-label"); box.add (screen_name_label); if (verified) { var verified_image= new Gtk.Image.from_surface (verified_surface); box.add (verified_image); } box.margin = 2; this.add (box); this.show_all (); } public string get_screen_name () { return screen_name_label.get_label (); } } corebird-1.7.4/src/widgets/ComposeImageManager.vala000066400000000000000000000241141324604713000222630ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class ComposeImageManager : Gtk.Container { private const int BUTTON_DELTA = 10; private const int BUTTON_SPACING = 12; private GLib.GenericArray buttons; private GLib.GenericArray close_buttons; private GLib.GenericArray progress_bars; public int n_images { get { return this.buttons.length; } } public bool has_gif { get { for (int i = 0; i < buttons.length; i ++) { if (buttons.get (i).image_path.has_suffix (".gif")) { return true; } } return false; } } public bool full { get { return this.buttons.length == Twitter.max_media_per_upload || this.has_gif; } } public signal void image_removed (string image_path); construct { this.buttons = new GLib.GenericArray (); this.close_buttons = new GLib.GenericArray (); this.progress_bars = new GLib.GenericArray (); this.set_has_window (false); } private void remove_clicked_cb (Gtk.Button source) { int index = -1; for (int i = 0; i < this.close_buttons.length; i ++) { if (close_buttons.get (i) == source) { index = i; break; } } assert (index >= 0); this.close_buttons.get (index).hide (); this.progress_bars.get (index).hide (); AddImageButton aib = (AddImageButton) this.buttons.get (index); aib.deleted.connect (() => { this.buttons.remove_index (index); this.close_buttons.remove_index (index); this.progress_bars.remove_index (index); this.queue_draw (); this.image_removed (aib.image_path); }); aib.start_remove (); } // GtkContainer API {{{ public override void forall_internal (bool include_internals, Gtk.Callback cb) { assert (buttons.length == close_buttons.length); assert (buttons.length == progress_bars.length); for (int i = 0; i < this.close_buttons.length;) { int size_before = this.close_buttons.length; cb (close_buttons.get (i)); i += this.close_buttons.length - size_before + 1; } for (int i = 0; i < this.progress_bars.length;) { int size_before = this.progress_bars.length; cb (progress_bars.get (i)); i += this.progress_bars.length - size_before + 1; } for (int i = 0; i < this.buttons.length;) { int size_before = this.buttons.length; cb (buttons.get (i)); i += this.buttons.length - size_before + 1; } } public override void add (Gtk.Widget widget) { widget.set_parent (this); this.buttons.add ((AddImageButton)widget); var btn = new Gtk.Button.from_icon_name ("window-close-symbolic"); btn.set_parent (this); btn.get_style_context ().add_class ("image-button"); btn.get_style_context ().add_class ("close-button"); btn.clicked.connect (remove_clicked_cb); btn.show (); this.close_buttons.add (btn); var bar = new Gtk.ProgressBar (); bar.set_parent (this); bar.show_all (); this.progress_bars.add (bar); } public override void remove (Gtk.Widget widget) { widget.unparent (); if (widget is AddImageButton) this.buttons.remove ((AddImageButton)widget); else if (widget is Gtk.Button) this.close_buttons.remove ((Gtk.Button)widget); else this.progress_bars.remove ((Gtk.ProgressBar)widget); } // }}} // GtkWidget API {{{ public override Gtk.SizeRequestMode get_request_mode () { return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH; } public override void size_allocate (Gtk.Allocation allocation) { base.size_allocate (allocation); Gtk.Allocation child_allocation = {}; if (this.buttons.length == 0) return; int default_button_width = (allocation.width - (buttons.length * BUTTON_SPACING)) / buttons.length; child_allocation.x = allocation.x; child_allocation.y = allocation.y + BUTTON_DELTA; child_allocation.height = int.max (allocation.height - BUTTON_DELTA, 0); Gtk.Allocation close_allocation = {}; close_allocation.y = allocation.y; for (int i = 0, p = this.buttons.length; i < p; i ++) { int min, nat; /* Actual image button */ AddImageButton aib = this.buttons.get (i); aib.get_preferred_width_for_height (child_allocation.height, out min, out nat); child_allocation.width = int.min (default_button_width, nat); aib.size_allocate (child_allocation); /* Remove button */ int n; Gtk.Widget btn = this.close_buttons.get (i); btn.get_preferred_width (out close_allocation.width, out n); btn.get_preferred_height (out close_allocation.height, out n); close_allocation.x = child_allocation.x + child_allocation.width - close_allocation.width + BUTTON_DELTA; btn.size_allocate (close_allocation); /* Progress bar */ int button_width, button_height; double scale; aib.get_draw_size (out button_width, out button_height, out scale); Gtk.Widget bar = this.progress_bars.get (i); Gtk.Allocation bar_allocation = {0}; bar_allocation.x = child_allocation.x + 6; bar.get_preferred_width (out bar_allocation.width, out n); bar_allocation.width = int.max (button_width - 12, bar_allocation.width); bar.get_preferred_height (out bar_allocation.height, out n); bar_allocation.y = child_allocation.y + button_height - bar_allocation.height - 6; bar.size_allocate (bar_allocation); child_allocation.x += child_allocation.width + BUTTON_SPACING; } } public override void get_preferred_height_for_width (int width, out int minimum, out int natural) { int min = 0; int nat = 0; for (int i = 0; i < buttons.length; i ++) { var btn = buttons.get (i); int m, n; btn.get_preferred_height_for_width (width, out m, out n); min = int.max (m, min); nat = int.max (n, nat); } /* We subtract BUTTON_DELTA in size_allocate again */ minimum = min + BUTTON_DELTA; natural = nat + BUTTON_DELTA; } public override void get_preferred_height (out int minimum, out int natural) { int min = 0; int nat = 0; for (int i = 0; i < buttons.length; i ++) { var btn = buttons.get (i); int m, n; btn.get_preferred_height (out m, out n); min = int.max (m, min); nat = int.max (n, nat); } /* We subtract BUTTON_DELTA in size_allocate again */ minimum = min + BUTTON_DELTA; natural = nat + BUTTON_DELTA; } public override void get_preferred_width (out int minimum, out int natural) { int min = 0; int nat = 0; for (int i = 0; i < buttons.length; i ++) { var btn = buttons.get (i); int m, n; btn.get_preferred_width (out m, out n); min += m; nat += n; } minimum = min + (buttons.length * BUTTON_SPACING); natural = nat + (buttons.length * BUTTON_SPACING); } public override bool draw (Cairo.Context ct) { for (int i = 0, p = this.buttons.length; i < p; i ++) { Gtk.Widget btn = this.buttons.get (i); this.propagate_draw (btn, ct); } for (int i = 0, p = this.close_buttons.length; i < p; i ++) { var btn = this.close_buttons.get (i); this.propagate_draw (btn, ct); } for (int i = 0, p = this.progress_bars.length; i < p; i ++) { var bar = this.progress_bars.get (i); this.propagate_draw (bar, ct); } return Gdk.EVENT_PROPAGATE; } // }}} public void load_image (string path, Gdk.Pixbuf? image) { #if DEBUG assert (!this.full); #endif Cairo.ImageSurface surface; if (image == null) surface = (Cairo.ImageSurface) load_surface (path); else surface = (Cairo.ImageSurface) Gdk.cairo_surface_create_from_pixbuf (image, this.get_scale_factor (), this.get_window ()); var button = new AddImageButton (); button.surface = surface; button.image_path = path; button.hexpand = false; button.halign = Gtk.Align.START; button.show (); this.add (button); } public void set_image_progress (string image_path, double progress) { for (int i = 0; i < buttons.length; i ++) { var btn = buttons.get (i); if (btn.image_path == image_path) { var progress_bar = progress_bars.get (i); progress_bar.set_fraction (progress); if (progress == 1.0) { progress_bar.hide (); } break; } } } public void end_progress (string image_path, string? error_message) { for (int i = 0; i < buttons.length; i ++) { var btn = buttons.get (i); if (btn.image_path == image_path) { btn.get_style_context ().remove_class ("image-progress"); if (error_message == null) { btn.get_style_context ().add_class ("image-success"); } else { warning ("%s: %s", image_path, error_message); btn.get_style_context ().add_class ("image-error"); } break; } } } public void insensitivize_buttons () { for (int i = 0; i < close_buttons.length; i ++) { close_buttons.get (i).set_sensitive (false); } } } corebird-1.7.4/src/widgets/CropWidget.vala000066400000000000000000000314041324604713000204670ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class CropWidget : Gtk.DrawingArea { private const int MIN_SIZE = 48; private Gtk.GestureDrag drag_gesture; private Gdk.Pixbuf? image; private Gdk.Rectangle selection_rect; private Gdk.Rectangle image_rect; private Gdk.Cursor drag_cursor; private Gdk.Cursor default_cursor; private Gdk.Cursor resize_cursor; private bool selection_grabbed = false; private bool resize_area_grabbed = false; private int drag_diff_x = 0; private int drag_diff_y = 0; private int resize_diff_x = 0; private int resize_diff_y = 0; private bool resize_area_hovered = false; private double current_scale = 1.0; private int min_width = MIN_SIZE; private double drag_start_x; private double drag_start_y; /** * Ratio of the width to the height, i.e. (width/height) * => values >1.0 for landscape pictures */ public double desired_aspect_ratio { get; set; default = 0.8; } construct { this.set_events (this.get_events () | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON1_MOTION_MASK | Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK); this.motion_notify_event.connect (mouse_motion_cb); this.drag_cursor = new Gdk.Cursor.for_display (this.get_display (), Gdk.CursorType.FLEUR); this.default_cursor = new Gdk.Cursor.for_display (this.get_display (), Gdk.CursorType.ARROW); this.resize_cursor = new Gdk.Cursor.for_display (this.get_display (), Gdk.CursorType.BOTTOM_RIGHT_CORNER); this.image_rect = Gdk.Rectangle (); this.selection_rect = Gdk.Rectangle (); this.drag_gesture = new Gtk.GestureDrag (this); this.drag_gesture.set_button (Gdk.BUTTON_PRIMARY); this.drag_gesture.drag_begin.connect (drag_gesture_begin_cb); this.drag_gesture.drag_end.connect (drag_gesture_end_cb); this.drag_gesture.drag_update.connect (drag_gesture_update_cb); } private bool mouse_motion_cb (Gdk.EventMotion event) { /* Just check whether the cursor is over the drag or resize area (or not) and change the cursor accordingly */ if (over_resize_area (event.x, event.y)) { set_cursor (resize_cursor); resize_area_hovered = true; queue_draw (); return false; /* Don't check resize area */ } else if (resize_area_hovered) { resize_area_hovered = false; set_cursor (default_cursor); queue_draw (); } if (cursor_in_selection (event.x, event.y)) { set_cursor (drag_cursor); } else { set_cursor (default_cursor); } return false; } private void drag_gesture_update_cb (Gtk.GestureDrag gesture, double offset_x, double offset_y) { double x = drag_start_x + offset_x; // XXX start_x + offset_x ? double y = drag_start_y + offset_y; /* Resizing */ if (resize_area_grabbed) { resize_selection_rect (x, y); } /* Dragging the selection */ if (selection_grabbed) { selection_rect.x = (int) x - drag_diff_x; selection_rect.y = (int) y - drag_diff_y; /* Limit to image boundaries */ if (selection_rect.x < image_rect.x) selection_rect.x = image_rect.x; if (selection_rect.y < image_rect.y) selection_rect.y = image_rect.y; if (selection_rect.x + selection_rect.width > image_rect.x + image_rect.width) selection_rect.x = image_rect.x + image_rect.width - selection_rect.width; if (selection_rect.y + selection_rect.height > image_rect.y + image_rect.height) selection_rect.y = image_rect.y + image_rect.height - selection_rect.height; this.queue_draw (); return; } } private void drag_gesture_begin_cb (Gtk.GestureDrag gesture, double x, double y) { this.drag_start_x = x; this.drag_start_y = y; /* Check for the resize area(s) first */ if (over_resize_area (x, y)) { resize_area_grabbed = true; resize_diff_x = (int)x - (selection_rect.x + selection_rect.width); resize_diff_y = (int)y - (selection_rect.y + selection_rect.height); gesture.set_state (Gtk.EventSequenceState.CLAIMED); set_cursor (resize_cursor); return; } /* Now the selection rect */ if (cursor_in_selection (x, y)) { selection_grabbed = true; drag_diff_x = (int)(x - selection_rect.x); drag_diff_y = (int)(y - selection_rect.y); gesture.set_state (Gtk.EventSequenceState.CLAIMED); set_cursor (drag_cursor); return; } gesture.set_state (Gtk.EventSequenceState.DENIED); } private void drag_gesture_end_cb (Gtk.GestureDrag gesture, double offset_x, double offset_y) { if (selection_grabbed) { selection_grabbed = false; set_cursor (default_cursor); return; } if (resize_area_grabbed) { resize_area_grabbed = false; set_cursor (default_cursor); return; } } private inline void restrict_selection_size () { if (selection_rect.width > image_rect.width) selection_rect.width = image_rect.width; if (selection_rect.height > image_rect.height) selection_rect.height = image_rect.height; if (selection_rect.width < (min_width * current_scale)) { selection_rect.width = (int)(min_width * current_scale); selection_rect.height = (int)(min_width * current_scale / desired_aspect_ratio); } if (selection_rect.x < image_rect.x) selection_rect.x = image_rect.x; if (selection_rect.y < image_rect.y) selection_rect.y = image_rect.y; if (selection_rect.x + selection_rect.width > image_rect.x + image_rect.width) selection_rect.x = image_rect.x + image_rect.width - selection_rect.width; if (selection_rect.y + selection_rect.height > image_rect.y + image_rect.height) selection_rect.y = image_rect.y + image_rect.height - selection_rect.height; } private void resize_selection_rect (double x, double y) { if (!resize_area_grabbed) return; int max_width = int.min (image_rect.width, int.min ((int)(image_rect.width), (int)(image_rect.height * desired_aspect_ratio))); int new_width = (int)x - selection_rect.x - resize_diff_x; int new_height = (int)(new_width / desired_aspect_ratio); if (new_width <= max_width) { selection_rect.width = new_width; selection_rect.height = new_height; } else { selection_rect.width = max_width; //message ("%d", selection_rect.width); //message ("%f", image_rect.width / desired_aspect_ratio); selection_rect.height = (int)(max_width / desired_aspect_ratio); } restrict_selection_size (); this.queue_draw (); } public void set_image (Gdk.Pixbuf? image) { this.image = image; calculate_image_rect (); /* Place the selection rect initially, using the maximum size given the desired_aspect_ratio */ selection_rect.width = image_rect.width; selection_rect.height = (int)(selection_rect.width / desired_aspect_ratio); if (selection_rect.height > image_rect.height) { selection_rect.height = image_rect.height; selection_rect.width = (int)(selection_rect.height * desired_aspect_ratio); } selection_rect.x = image_rect.x + ((image_rect.width - selection_rect.width) / 2); selection_rect.y = image_rect.y + ((image_rect.height - selection_rect.height) / 2); restrict_selection_size (); this.queue_draw (); } public override bool draw (Cairo.Context ct) { if (image == null) return Gdk.EVENT_PROPAGATE; int widget_width = get_allocated_width (); int widget_height = get_allocated_height (); ct.set_line_width (1.0); /* Draw dark background */ ct.rectangle (0, 0, widget_width, widget_height); ct.set_source_rgba (0.3, 0.3, 0.3, 1.0); ct.fill (); /* Draw image */ ct.save (); ct.rectangle (image_rect.x, image_rect.y, image_rect.width, image_rect.height); ct.scale (current_scale, current_scale); Gdk.cairo_set_source_pixbuf (ct, image, image_rect.x / current_scale, image_rect.y / current_scale); ct.fill (); ct.restore (); /* Draw selection rectangle border */ ct.rectangle (selection_rect.x, selection_rect.y, selection_rect.width, selection_rect.height); ct.set_source_rgba (1.0, 1.0, 1.0, 1.0); ct.stroke (); /* Draw resize quad */ ct.rectangle (selection_rect.x + selection_rect.width - 15, selection_rect.y + selection_rect.height - 15, 14.5, 14.5); if (resize_area_hovered || resize_area_grabbed) ct.set_source_rgba (0.0, 0.0, 0.6, 0.7); else ct.set_source_rgba (1.0, 1.0, 1.0, 0.7); ct.fill (); return Gdk.EVENT_PROPAGATE; } private inline void set_cursor (Gdk.Cursor cursor) { this.get_window ().set_cursor (cursor); } private bool cursor_in_selection (double x, double y) { return x >= selection_rect.x && x <= selection_rect.x + selection_rect.width && y >= selection_rect.y && y <= selection_rect.y + selection_rect.height; } public override void size_allocate (Gtk.Allocation alloc) { base.size_allocate (alloc); calculate_image_rect (); restrict_selection_size (); } private void calculate_image_rect () { int widget_width = this.get_allocated_width (); int widget_height = this.get_allocated_height (); if (this.image == null) { return; } /* current_scale the image down */ if (image.get_width () > image.get_height ()) { current_scale = (double) widget_width / image.get_width (); } else { current_scale = (double) widget_height / image.get_height (); } if (image.get_width () * current_scale > widget_width) current_scale = (double) widget_width / image.get_width (); if (image.get_height () * current_scale > widget_height) current_scale = (double) widget_height / image.get_height (); /* Cap at 1.0 */ if (current_scale > 1.0) current_scale = 1.0; this.image_rect.width = (int)(this.image.get_width () * current_scale); this.image_rect.height = (int)(this.image.get_height () * current_scale); this.image_rect.x = (widget_width - image_rect.width) / 2; this.image_rect.y = (widget_height - image_rect.height) / 2; } private bool over_resize_area (double x, double y) { if (x > selection_rect.x + selection_rect.width - 15 && x < selection_rect.x + selection_rect.width + 5 && y > selection_rect.y + selection_rect.height - 15 && y < selection_rect.y + selection_rect.height + 5) { return true; } return false; } public Gdk.Pixbuf get_cropped_image () { int absolute_x = (int)((selection_rect.x - image_rect.x) / current_scale); int absolute_y = (int)((selection_rect.y - image_rect.y) / current_scale); int absolute_w = (int)(selection_rect.width / current_scale); int absolute_h = (int)(selection_rect.height / current_scale); Gdk.Pixbuf final_image = new Gdk.Pixbuf (Gdk.Colorspace.RGB, this.image.get_has_alpha (), 8, absolute_w, absolute_h); this.image.copy_area (absolute_x, absolute_y, absolute_w, absolute_h, final_image, 0, 0); return final_image; } public void set_min_size (int min_width) { this.min_width = min_width; } } corebird-1.7.4/src/widgets/DMPlaceholderBox.vala000066400000000000000000000036061324604713000215370ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class DMPlaceholderBox : Gtk.Box { private AvatarWidget avatar_image; private Gtk.Label name_label; private Gtk.Label screen_name_label; public int64 user_id; public new string name { set { name_label.label = value; } } public string screen_name { set { screen_name_label.label = "@" + value; } } public string avatar_url; public void load_avatar () { Twitter.get ().get_avatar.begin (user_id, avatar_url, avatar_image); } construct { this.set_opacity (0.8); this.set_margin_top (60); this.set_orientation (Gtk.Orientation.VERTICAL); this.set_spacing (4); this.avatar_image = new AvatarWidget (); avatar_image.size = 48; avatar_image.set_halign (Gtk.Align.CENTER); this.add (avatar_image); this.name_label = new Gtk.Label (""); var attrs = new Pango.AttrList (); attrs.insert (Pango.attr_weight_new (Pango.Weight.BOLD)); name_label.set_attributes (attrs); this.add (name_label); this.screen_name_label = new Gtk.Label (""); screen_name_label.get_style_context ().add_class ("dim-label"); this.add (screen_name_label); this.show_all (); } } corebird-1.7.4/src/widgets/DoubleTapButton.vala000066400000000000000000000020171324604713000214710ustar00rootroot00000000000000 /* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class DoubleTapButton : Gtk.ToggleButton { private bool first_step = false; public void reset () { first_step = false; } public void tap () { if (!first_step) { first_step = true; return; } this.active = !this.active; first_step = false; } } corebird-1.7.4/src/widgets/FavImageView.vala000066400000000000000000000167371324604713000207460ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2017 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class FavImageView : Gtk.Box { private Gtk.ScrolledWindow scrolled_window; private Gtk.FlowBox fav_image_list; private Gtk.Button new_fav_image_button; private bool gifs_enabled = true; public signal void image_selected (string image_path); construct { this.orientation = Gtk.Orientation.VERTICAL; this.scrolled_window = new Gtk.ScrolledWindow (null, null); this.fav_image_list = new Gtk.FlowBox (); fav_image_list.homogeneous = true; scrolled_window.set_vexpand (true); scrolled_window.add (this.fav_image_list); this.add (scrolled_window); this.add (new Gtk.Separator (Gtk.Orientation.HORIZONTAL)); this.new_fav_image_button = new Gtk.Button.from_icon_name ("list-add-symbolic", Gtk.IconSize.BUTTON); new_fav_image_button.set_halign (Gtk.Align.START); new_fav_image_button.set_margin_start (6); new_fav_image_button.set_margin_top (6); new_fav_image_button.set_margin_bottom (6); new_fav_image_button.clicked.connect (new_fav_image_button_clicked_cb); this.add (new_fav_image_button); fav_image_list.set_selection_mode (Gtk.SelectionMode.NONE); fav_image_list.get_style_context ().add_class ("view"); fav_image_list.get_style_context ().add_class ("fav-image-box"); fav_image_list.child_activated.connect (fav_image_list_child_activated_cb); fav_image_list.drag_data_received.connect (fav_image_list_drag_data_received_cb); var image_target_list = new Gtk.TargetList (null); image_target_list.add_text_targets (0); Gtk.drag_dest_set (this, Gtk.DestDefaults.ALL, null, Gdk.DragAction.COPY); Gtk.drag_dest_set_target_list (this, image_target_list); /* Fuck it */ this.show_all (); } public void load_images () { if (fav_image_list.get_children ().length () > 0) return; const int MAX_IMAGES = 50; string fav_image_dir = Dirs.config ("image-favorites/"); try { var dir = File.new_for_path (fav_image_dir); var iter = dir.enumerate_children ("standard::display-name,standard::content-type", GLib.FileQueryInfoFlags.NONE); int i = 0; FileInfo? info = null; while ((info = iter.next_file ()) != null) { var content_type = info.get_content_type (); if (content_type == "image/jpeg" || content_type == "image/png" || content_type == "image/gif") { var file = dir.get_child (info.get_name ()); var row = new FavImageRow (file.get_path ()); if (content_type == "image/gif") { row.is_gif = true; row.set_sensitive (gifs_enabled); } row.show_all (); fav_image_list.add (row); i ++; if (i >= MAX_IMAGES) break; } } } catch (GLib.Error e) { warning (e.message); } } private void fav_image_list_child_activated_cb (Gtk.FlowBoxChild _child) { FavImageRow child = (FavImageRow) _child; this.image_selected (child.get_image_path ()); } private void fav_image_list_drag_data_received_cb (Gdk.DragContext context, int x, int y, Gtk.SelectionData selection_data, uint info, uint time) { if (info == 0) { /* Text */ string?text = selection_data.get_text ().strip (); if (text.has_prefix ("file://")) { var file = GLib.File.new_for_uri (text); if (!file.query_exists ()) { debug ("File '%s' does not exist.", text); return; } try { var file_info = file.query_info ("standard::content-type", GLib.FileQueryInfoFlags.NONE); var row = new FavImageRow (GLib.File.new_for_uri (text).get_path ()); if (file_info.get_content_type () == "image/gif") { row.is_gif = true; row.set_sensitive (gifs_enabled); } row.show_all (); fav_image_list.add (row); } catch (GLib.Error e) { warning (e.message); } } else { debug ("Can't handle '%s'", text); } } else { warning ("Unknown drag data info %u", info); } } private void new_fav_image_button_clicked_cb () { var filechooser = new Gtk.FileChooserNative (_("Select Image"), this.get_toplevel () as Gtk.Window, Gtk.FileChooserAction.OPEN, _("Open"), _("Cancel")); var filter = new Gtk.FileFilter (); filter.add_mime_type ("image/png"); filter.add_mime_type ("image/jpeg"); filter.add_mime_type ("image/gif"); filechooser.set_filter (filter); if (filechooser.run () == Gtk.ResponseType.ACCEPT) { try { // First, take the selected file and copy it into the image-favorites folder var file = GLib.File.new_for_path (filechooser.get_filename ()); var file_info = file.query_info ("standard::name,standard::content-type", GLib.FileQueryInfoFlags.NONE); var dest_dir = GLib.File.new_for_path (Dirs.config ("image-favorites")); // Explicitly check whether the destination file already exists, and rename // it if it does */ var dest_file = dest_dir.get_child (file_info.get_name ()); if (GLib.FileUtils.test (dest_file.get_path (), GLib.FileTest.EXISTS)) { debug ("File '%s' already exists", dest_file.get_path ()); dest_file = dest_dir.get_child ("%s_%s".printf (GLib.get_monotonic_time ().to_string (), file_info.get_name ())); debug ("New name: '%s'", dest_file.get_path ()); } file.copy (dest_file, GLib.FileCopyFlags.NONE); var row = new FavImageRow (dest_file.get_path ()); if (file_info.get_content_type () == "image/gif") { row.is_gif = true; row.set_sensitive (gifs_enabled); } row.show_all (); fav_image_list.add (row); } catch (GLib.Error e) { warning (e.message); } } } public void set_gifs_enabled (bool enabled) { if (enabled == this.gifs_enabled) return; this.gifs_enabled = enabled; foreach (Gtk.Widget w in fav_image_list.get_children ()) { var child = (FavImageRow)w; if (child.get_image_path ().down ().has_suffix (".gif")) { child.set_sensitive (this.gifs_enabled); } } } } corebird-1.7.4/src/widgets/FollowButton.vala000066400000000000000000000036401324604713000210570ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class FollowButton : Gtk.Button { private bool _following = false; public bool following { get { return _following; } set { var sc = this.get_style_context (); if (value) { sc.remove_class ("suggested-action"); sc.add_class ("destructive-action"); this.stack.visible_child = unfollow_label; } else { sc.remove_class ("destructive-action"); sc.add_class ("suggested-action"); this.stack.visible_child = follow_label; } this._following = value; } } private Gtk.Stack stack; private Gtk.Label follow_label; private Gtk.Label unfollow_label; construct { this.stack = new Gtk.Stack (); this.follow_label = new Gtk.Label (_("Follow")); this.unfollow_label = new Gtk.Label (_("Unfollow")); stack.add (follow_label); stack.add (unfollow_label); stack.set_interpolate_size (true); stack.transition_type = Gtk.StackTransitionType.CROSSFADE; stack.hhomogeneous = false; stack.vhomogeneous = true; this.add (stack); this.get_style_context ().add_class ("text-button"); this.get_style_context ().add_class ("suggested-action"); /* Default is false */ } } corebird-1.7.4/src/widgets/ImpostorWidget.vala000066400000000000000000000032221324604713000213750ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class ImpostorWidget : Gtk.Image { private new Cairo.Surface? surface = null; public ImpostorWidget () { this.halign = Gtk.Align.FILL; this.valign = Gtk.Align.FILL; } public override bool draw (Cairo.Context ct) { if (this.surface == null) return false; ct.set_source_surface (this.surface, 0, 0); ct.rectangle (0, 0, this.get_allocated_width (), this.get_allocated_height ()); ct.fill (); return false; } public void clone (Gtk.Widget widget) { int widget_width = widget.get_allocated_width (); int widget_height = widget.get_allocated_height (); this.surface = widget.get_window ().create_similar_surface (Cairo.Content.COLOR_ALPHA, widget_width, widget_height); var ct = new Cairo.Context (surface); widget.draw (ct); } } corebird-1.7.4/src/widgets/LazyMenuButton.vala000066400000000000000000000020601324604713000213540ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public class LazyMenuButton : Gtk.ToggleButton { public GLib.Menu menu_model { get; set; } public override void clicked () { var popover = new Gtk.Popover.from_model (this, this.menu_model); popover.position = Gtk.PositionType.BOTTOM; #if GTK322 popover.popup (); #else popover.show (); #endif } } corebird-1.7.4/src/widgets/MaxSizeContainer.vala000066400000000000000000000106001324604713000216360ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class MaxSizeContainer : Gtk.Bin { private Gdk.Window? event_window = null; private int _max_size = 0; public int max_size { get { return this._max_size; } set { this._max_size = value; this.queue_resize (); } } public override void add (Gtk.Widget widget) { base.add (widget); if (this.event_window != null) widget.set_parent_window (this.event_window); } public override Gtk.SizeRequestMode get_request_mode () { return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH; } public override void get_preferred_height_for_width (int width, out int min_height, out int nat_height) { int min_child_height; int nat_child_height; get_child ().get_preferred_height_for_width (width, out min_child_height, out nat_child_height); if (max_size >= min_child_height) { min_height = min_child_height; nat_height = nat_child_height; } else { nat_height = max_size; min_height = max_size; } } public override void size_allocate (Gtk.Allocation alloc) { if (get_child () == null || !get_child ().visible) { if (this.event_window != null) event_window.move_resize (alloc.x, alloc.y, alloc.width, alloc.height); this.set_allocation (alloc); return; } Gtk.Allocation child_alloc = {}; child_alloc.x = alloc.x; child_alloc.width = alloc.width; if (max_size >= alloc.height) { // We don't cut away anything child_alloc.y = alloc.y; child_alloc.height = alloc.height; } else { child_alloc.y = alloc.y;// - (max_size - alloc.height); child_alloc.height = max_size; } if (this.event_window != null) this.event_window.move_resize (child_alloc.x, child_alloc.y, child_alloc.width, child_alloc.height); this.set_allocation (child_alloc); if (get_child () != null && get_child ().visible) { int min_height, nat_height; get_child ().get_preferred_height_for_width (alloc.width, out min_height, out nat_height); child_alloc.height = int.max (child_alloc.height, min_height); get_child ().size_allocate (child_alloc); if (this.get_realized ()) get_child ().show (); } } public override void realize () { base.realize (); Gtk.Allocation alloc; this.get_allocation (out alloc); Gdk.WindowAttr attr = {}; attr.x = alloc.x; attr.y = alloc.y; attr.width = alloc.width; attr.height = alloc.height; attr.window_type = Gdk.WindowType.CHILD; attr.visual = this.get_visual (); attr.wclass = Gdk.WindowWindowClass.INPUT_OUTPUT; attr.event_mask = this.get_events (); Gdk.WindowAttributesType attr_mask = Gdk.WindowAttributesType.X | Gdk.WindowAttributesType.Y; Gdk.Window window = this.get_parent_window (); this.set_window (window); window.ref (); this.event_window = new Gdk.Window (window, attr, attr_mask); this.register_window (this.event_window); if (this.get_child () != null) this.get_child ().set_parent_window (this.event_window); } public override void unrealize () { if (this.event_window != null) { this.unregister_window (this.event_window); this.event_window.destroy (); this.event_window = null; } base.unrealize (); } public override void map () { base.map (); if (this.event_window != null) this.event_window.show (); } public override void unmap () { if (this.event_window != null) this.event_window.hide (); base.unmap (); } } corebird-1.7.4/src/widgets/MediaButton.vala000066400000000000000000000402451324604713000206360ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ private class MediaButton : Gtk.Widget { private const int PLAY_ICON_SIZE = 32; private const int MAX_HEIGHT = 200; /* We use MIN_ constants in case the media has not yet been loaded */ private const int MIN_HEIGHT = 40; private const int MIN_WIDTH = 40; private Gdk.Window? event_window = null; private Cb.Media? _media = null; private static Cairo.Surface[] play_icons; public Cb.Media? media { get { return _media; } set { if (_media != null) { _media.progress.disconnect (media_progress_cb); } _media = value; if (value != null) { if (!media.loaded) { _media.progress.connect (media_progress_cb); } else { this.media_alpha = 1.0; } bool is_m3u8 = _media.url.has_suffix (".m3u8"); ((GLib.SimpleAction)actions.lookup_action ("save-as")).set_enabled (!is_m3u8); } if (value != null && (value.type == Cb.MediaType.IMAGE || value.type == Cb.MediaType.GIF)) { menu_model.append (_("Copy URL"), "media.copy-url"); } } } public unowned Gtk.Window window; private GLib.Menu menu_model; private Gtk.Menu? menu = null; private GLib.SimpleActionGroup actions; private const GLib.ActionEntry[] action_entries = { {"copy-url", copy_url_activated}, {"open-in-browser", open_in_browser_activated}, {"save-as", save_as_activated}, }; private Pango.Layout layout; private Gtk.GestureMultiPress press_gesture; private bool restrict_height = false; private int64 fade_start_time; private double media_alpha = 0.0; public signal void clicked (MediaButton source, double px, double py); static construct { try { play_icons = { Gdk.cairo_surface_create_from_pixbuf ( new Gdk.Pixbuf.from_resource ("/org/baedert/corebird/data/play.png"), 1, null), Gdk.cairo_surface_create_from_pixbuf ( new Gdk.Pixbuf.from_resource ("/org/baedert/corebird/data/play@2.png"), 2, null), }; } catch (GLib.Error e) { critical (e.message); } } construct { this.set_has_window (false); this.set_can_focus (true); } ~MediaButton () { if (_media != null) { _media.progress.disconnect (media_progress_cb); } } public MediaButton (Cb.Media? media, bool restrict_height = false) { this.media = media; this.restrict_height = restrict_height; this.get_style_context ().add_class ("inline-media"); actions = new GLib.SimpleActionGroup (); actions.add_action_entries (action_entries, this); this.insert_action_group ("media", actions); this.menu_model = new GLib.Menu (); menu_model.append (_("Open in Browser"), "media.open-in-browser"); menu_model.append (_("Save as…"), "media.save-as"); this.layout = this.create_pango_layout ("0%"); this.press_gesture = new Gtk.GestureMultiPress (this); this.press_gesture.set_exclusive (true); this.press_gesture.set_button (0); this.press_gesture.set_propagation_phase (Gtk.PropagationPhase.CAPTURE); this.press_gesture.released.connect (gesture_released_cb); this.press_gesture.pressed.connect (gesture_pressed_cb); } private void media_progress_cb () { this.queue_draw (); if (this._media.loaded) { if (!_media.invalid && _media.surface != null) { this.start_fade (); } else { /* Invalid media. */ this.hide (); this.set_sensitive (false); } this.queue_resize (); } } private bool fade_in_cb (Gtk.Widget widget, Gdk.FrameClock frame_clock) { if (!this.get_mapped ()) { this.media_alpha = 1.0; return GLib.Source.REMOVE; } int64 now = frame_clock.get_frame_time (); double t = 1.0; if (now < this.fade_start_time + TRANSITION_DURATION) t = (now - fade_start_time) / (double)(TRANSITION_DURATION ); t = ease_out_cubic (t); this.media_alpha = t; this.queue_draw (); if (t >= 1.0) { this.media_alpha = 1.0; return GLib.Source.REMOVE; } return GLib.Source.CONTINUE; } private void start_fade () { assert (this.media != null); assert (this.media.surface != null); if (!this.get_realized () || !this.get_mapped () || !Gtk.Settings.get_default ().gtk_enable_animations) { this.media_alpha = 1.0; return; } this.fade_start_time = this.get_frame_clock ().get_frame_time (); this.add_tick_callback (fade_in_cb); } private void get_draw_size (out int width, out int height, out double scale) { if (this._media.width == -1 && this._media.height == -1) { width = 0; height = 0; scale = 0.0; return; } width = this.get_allocated_width (); height = this.get_allocated_height (); double scale_x = (double)width / this._media.width; double scale_y = (double)height / this._media.height; scale = double.min (double.min (scale_x, scale_y), 1.0); width = (int)(this._media.width * scale); height = (int)(this._media.height * scale); } public override bool draw (Cairo.Context ct) { int widget_width = get_allocated_width (); int widget_height = get_allocated_height (); /* Draw thumbnail */ if (_media != null && _media.surface != null && _media.loaded) { int draw_width, draw_height; double scale; this.get_draw_size (out draw_width, out draw_height, out scale); int draw_x = (widget_width / 2) - (draw_width / 2); ct.save (); ct.rectangle (0, 0, widget_width, widget_height); ct.scale (scale, scale); ct.set_source_surface (media.surface, draw_x / scale, 0); ct.paint_with_alpha (this.media_alpha); ct.restore (); ct.new_path (); /* Draw play indicator */ if (_media.is_video ()) { int x = (widget_width / 2) - (PLAY_ICON_SIZE / 2); int y = (widget_height / 2) - (PLAY_ICON_SIZE / 2); ct.save (); ct.rectangle (x, y, PLAY_ICON_SIZE, PLAY_ICON_SIZE); ct.set_source_surface (play_icons[this.get_scale_factor () - 1], x, y); ct.paint_with_alpha (this.media_alpha); ct.restore (); ct.new_path (); } var sc = this.get_style_context (); sc.render_background (ct, draw_x, 0, draw_width, draw_height); sc.render_frame (ct, draw_x, 0, draw_width, draw_height); if (this.has_visible_focus ()) { sc.render_focus (ct, draw_x + 2, 2, draw_width - 4, draw_height - 4); } } else { var sc = this.get_style_context (); double layout_x, layout_y; int layout_w, layout_h; layout.set_text ("%d%%".printf ((int)(_media.percent_loaded * 100)), -1); layout.get_size (out layout_w, out layout_h); layout_x = (widget_width / 2.0) - (layout_w / Pango.SCALE / 2.0); layout_y = (widget_height / 2.0) - (layout_h / Pango.SCALE / 2.0); sc.render_layout (ct, layout_x, layout_y, layout); } return Gdk.EVENT_PROPAGATE; } private void copy_url_activated (GLib.SimpleAction a, GLib.Variant? v) { Gtk.Clipboard clipboard = Gtk.Clipboard.get_for_display (Gdk.Display.get_default (), Gdk.SELECTION_CLIPBOARD); clipboard.set_text (media.url, -1); } private void open_in_browser_activated (GLib.SimpleAction a, GLib.Variant? v) { try { Gtk.show_uri (Gdk.Screen.get_default (), media.target_url ?? media.url, Gtk.get_current_event_time ()); } catch (GLib.Error e) { critical (e.message); } } private void save_as_activated (GLib.SimpleAction a, GLib.Variant? v) { string title; if (_media.is_video ()) title = _("Save Video"); else title = _("Save Image"); var filechooser = new Gtk.FileChooserNative (title, this.window, Gtk.FileChooserAction.SAVE, _("Save"), _("Cancel")); filechooser.set_current_name (Utils.get_media_display_name (_media)); if (filechooser.run () == Gtk.ResponseType.ACCEPT) { var file = GLib.File.new_for_path (filechooser.get_filename ()); // Download the file string url = _media.target_url ?? _media.url; debug ("Downloading %s to %s", url, filechooser.get_filename ()); GLib.OutputStream? out_stream = null; try { out_stream = file.create (0, null); } catch (GLib.Error e) { Utils.show_error_dialog (e.message, this.window); warning (e.message); } if (out_stream != null) { Utils.download_file.begin (url, out_stream, () => { debug ("Download of %s finished", url); }); } } } public override Gtk.SizeRequestMode get_request_mode () { return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH; } public override void get_preferred_height (out int minimum, out int natural) { int media_height; if (this._media == null || this._media.height == -1) { media_height = 1; } else { media_height = this._media.height; } minimum = int.min (media_height, MAX_HEIGHT); natural = media_height; } public override void get_preferred_height_for_width (int width, out int minimum, out int natural) { int media_width; int media_height; if (this._media == null || this._media.width == -1 || this._media.height == -1) { media_width = MIN_WIDTH; media_height = MAX_HEIGHT; } else { media_width = this._media.width; media_height = this._media.height; } double width_ratio = (double)width / (double) media_width; int height = int.min (media_height, (int)(media_height * width_ratio)); if (restrict_height) { minimum = int.min (media_height, MAX_HEIGHT); natural = minimum; } else { minimum = height; natural = height; } } public override void get_preferred_width_for_height (int height, out int minimum, out int natural) { int media_width; int media_height; if (this._media == null || this._media.width == -1 || this._media.height == -1) { media_width = MIN_WIDTH; media_height = MAX_HEIGHT; } else { media_width = this._media.width; media_height = this._media.height; } double height_ratio = (double)height / (double)media_height; int width = int.min (media_width, (int)(media_width * height_ratio)); minimum = int.min (media_width, MIN_WIDTH); natural = width; } public override void get_preferred_width (out int minimum, out int natural) { int media_width; if (this._media == null || this._media.width == -1) { media_width = 1; } else { media_width = this._media.width; } minimum = int.min (media_width, MIN_WIDTH); natural = media_width; } public override void realize () { this.set_realized (true); int draw_width; int draw_height; double scale; this.get_draw_size (out draw_width, out draw_height, out scale); Gdk.WindowAttr attr = {}; attr.x = 0; attr.y = 0; attr.width = draw_width; attr.height = draw_height; attr.window_type = Gdk.WindowType.CHILD; attr.visual = this.get_visual (); attr.wclass = Gdk.WindowWindowClass.INPUT_ONLY; attr.event_mask = this.get_events () | Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.TOUCH_MASK | Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK; Gdk.WindowAttributesType attr_mask = Gdk.WindowAttributesType.X | Gdk.WindowAttributesType.Y; Gdk.Window window = this.get_parent_window (); this.set_window (window); window.ref (); this.event_window = new Gdk.Window (window, attr, attr_mask); this.register_window (this.event_window); } public override void unrealize () { if (this.event_window != null) { this.unregister_window (this.event_window); this.event_window.destroy (); this.event_window = null; } base.unrealize (); } public override void map () { base.map (); if (this.event_window != null) this.event_window.show (); } public override void unmap () { if (this.event_window != null) this.event_window.hide (); base.unmap (); } public override void size_allocate (Gtk.Allocation alloc) { base.size_allocate (alloc); int draw_width; int draw_height; double scale; if (this.get_realized ()) { this.get_draw_size (out draw_width, out draw_height, out scale); int draw_x = (alloc.width / 2) - (draw_width / 2); this.event_window.move_resize (alloc.x + draw_x, alloc.y, draw_width, draw_height); } } public override bool enter_notify_event (Gdk.EventCrossing evt) { if (evt.window == this.event_window && evt.detail != Gdk.NotifyType.INFERIOR) { this.set_state_flags (this.get_state_flags () | Gtk.StateFlags.PRELIGHT, true); } return Gdk.EVENT_PROPAGATE; } public override bool leave_notify_event (Gdk.EventCrossing evt) { if (evt.window == this.event_window && evt.detail != Gdk.NotifyType.INFERIOR) { this.set_state_flags (this.get_state_flags () & ~Gtk.StateFlags.PRELIGHT, true); } return Gdk.EVENT_PROPAGATE; } private void gesture_pressed_cb (int n_press, double x, double y) { Gdk.EventSequence sequence = this.press_gesture.get_current_sequence (); Gdk.Event event = this.press_gesture.get_last_event (sequence); uint button = this.press_gesture.get_current_button (); if (this._media == null) return; if (event.triggers_context_menu ()) { this.press_gesture.set_state (Gtk.EventSequenceState.CLAIMED); if (this.menu == null) { this.menu = new Gtk.Menu.from_model (menu_model); this.menu.attach_to_widget (this, null); } menu.show_all (); menu.popup (null, null, null, button, Gtk.get_current_event_time ()); } } private void gesture_released_cb (int n_press, double x, double y) { Gdk.EventSequence sequence = this.press_gesture.get_current_sequence (); Gdk.Event event = this.press_gesture.get_last_event (sequence); uint button = this.press_gesture.get_current_button (); if (this._media == null || event == null) return; if (button == Gdk.BUTTON_PRIMARY) { this.press_gesture.set_state (Gtk.EventSequenceState.CLAIMED); double px = x / (double)this.get_allocated_width (); double py = y / (double)this.get_allocated_height (); this.clicked (this, px, py); } } public override bool key_press_event (Gdk.EventKey event) { if (event.keyval == Gdk.Key.Return || event.keyval == Gdk.Key.KP_Enter) { this.clicked (this, 0.5, 0.5); return Gdk.EVENT_STOP; } return Gdk.EVENT_PROPAGATE; } } corebird-1.7.4/src/widgets/MultiMediaWidget.vala000066400000000000000000000062031324604713000216150ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public class MultiMediaWidget : Gtk.Box { public const int MAX_HEIGHT = 180; public bool restrict_height = false; public unowned Gtk.Window window; private MediaButton[] media_buttons; private int media_count = 0; public signal void media_clicked (Cb.Media m, int index, double px, double py); private bool media_invalid_fired = false; public signal void media_invalid (); construct { this.orientation = Gtk.Orientation.HORIZONTAL; this.homogeneous = true; } public void set_all_media (Cb.Media[] medias) { this.remove_all (); this.media_buttons = new MediaButton[medias.length]; this.media_count = medias.length; for (int i = 0; i < medias.length; i++) { assert (medias[i] != null); set_media (i, medias[i]); } } private void remove_all () { this.get_children ().foreach ((w) => { this.remove (w); }); } public void set_media (int index, Cb.Media media) { assert (index < media_count); if (media.loaded && media.invalid) return; var button = new MediaButton (null, this.restrict_height); button.set_data ("pos", index); button.window = this.window; media_buttons[index] = button; if (media.loaded) { media_buttons[index].media = media; } else { media_buttons[index].media = media; media.progress.connect (media_loaded_cb); } button.visible = true; button.clicked.connect (button_clicked_cb); this.pack_start (button, true, true); this.queue_draw (); } private void button_clicked_cb (MediaButton source, double px, double py) { if (source.media != null && source.media.loaded) { int index = source.get_data ("pos"); media_clicked (source.media, index, px, py); } } private void media_loaded_cb (Cb.Media source) { if (source.percent_loaded < 100) return; if (source.invalid) { for (int i = 0; i < media_count; i ++) { if (media_buttons[i] != null && media_buttons[i].media == source) { this.remove (media_buttons[i]); media_buttons[i] = null; if (!media_invalid_fired) { media_invalid (); media_invalid_fired = true; } return; } } } for (int i = 0; i < media_count; i ++) { if (media_buttons[i] != null && media_buttons[i].media == source) { media_buttons[i].queue_draw (); break; } } } } corebird-1.7.4/src/widgets/PixbufButton.vala000066400000000000000000000056301324604713000210530ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class PixbufButton : Gtk.Button { private Cairo.ImageSurface bg; private bool _round = false; public bool round { get { return _round; } set { if (value) { this.get_style_context ().add_class ("pixbuf-button-round"); } else { this.get_style_context ().remove_class ("pixbuf-button-round"); } _round = value; } } construct { this.border_width = 0; get_style_context ().add_class ("pixbuf-button"); } public PixbufButton () {} public override bool draw (Cairo.Context ct) { var sc = this.get_style_context (); int widget_width = this.get_allocated_width (); int widget_height = this.get_allocated_height (); if (bg != null) { var surface = new Cairo.Surface.similar (ct.get_target (), Cairo.Content.COLOR_ALPHA, widget_width, widget_height); var ctx = new Cairo.Context (surface); ctx.rectangle (0, 0, widget_width, widget_height); double scale_x = (double)widget_width / bg.get_width (); double scale_y = (double)widget_height / bg.get_height (); ctx.save (); ctx.scale (scale_x, scale_y); ctx.set_source_surface (bg, 0, 0); ctx.fill (); ctx.restore (); if (_round) { // make it round ctx.set_operator (Cairo.Operator.DEST_IN); ctx.translate (widget_width / 2, widget_height / 2); ctx.arc (0, 0, widget_width / 2, 0, 2 * Math.PI); ctx.fill (); // draw outline sc.render_frame (ct, 0, 0, widget_width, widget_height); } ct.rectangle (0, 0, widget_width, widget_height); ct.set_source_surface (surface, 0, 0); ct.fill (); } // The css-styled background should be transparent. base.draw (ct); return GLib.Source.CONTINUE; } public void set_bg (Cairo.ImageSurface bg) { this.bg = bg; this.set_size_request (bg.get_width(), bg.get_height()); this.queue_draw (); } public void set_pixbuf (Gdk.Pixbuf pixbuf) { this.bg = (Cairo.ImageSurface)Gdk.cairo_surface_create_from_pixbuf (pixbuf, 1, null); this.queue_draw (); } } corebird-1.7.4/src/widgets/ReplyEntry.vala000066400000000000000000000021261324604713000205340ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ class ReplyEntry : Gtk.Entry { [Signal (action = true)] public signal void cancelled (); public ReplyEntry () {} static construct { unowned Gtk.BindingSet binding_set = Gtk.BindingSet.by_class ((GLib.ObjectClass)typeof (ReplyEntry).class_ref ()); Gtk.BindingEntry.add_signal (binding_set, Gdk.Key.Escape, 0, "cancelled", 0, null); } } corebird-1.7.4/src/widgets/ReplyIndicator.vala000066400000000000000000000052621324604713000213530ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public class ReplyIndicator : Gtk.Widget { private const int FINAL_HEIGHT = 5; private bool replies = false; public bool replies_available { set { this.replies = value; this.on_replies_available (); } get { return replies; } } private int64 start_time; private double show_factor = 0.0; construct { set_has_window (false); } static construct { set_css_name ("replyindicator"); } public override Gtk.SizeRequestMode get_request_mode () { return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH; } public override void get_preferred_height_for_width (int width, out int min_height, out int nat_height) { min_height = (int)(FINAL_HEIGHT * show_factor); nat_height = (int)(FINAL_HEIGHT * show_factor); } private void on_replies_available () { if (!replies) { show_factor = 0.0; queue_resize (); return; } start_time = this.get_frame_clock ().get_frame_time (); this.add_tick_callback (tick_callback); } private bool tick_callback (Gtk.Widget widget, Gdk.FrameClock frame_clock) { if (!this.get_mapped ()) { this.queue_resize (); return GLib.Source.REMOVE; } int64 now = frame_clock.get_frame_time (); int64 end_time = this.start_time + TRANSITION_DURATION; double t = 1.0; if (now < end_time) t = (now - start_time) / (double)(end_time - start_time); t = ease_out_cubic (t); this.show_factor = t; this.queue_resize (); if (t >= 1.0) { return GLib.Source.REMOVE; } return GLib.Source.CONTINUE; } public override bool draw (Cairo.Context ct) { if (!replies) { return Gdk.EVENT_PROPAGATE; } var style_context = this.get_style_context (); style_context.render_background (ct, 0, 0, get_allocated_width(), get_allocated_height ()); return Gdk.EVENT_PROPAGATE; } } corebird-1.7.4/src/widgets/ScrollWidget.vala000066400000000000000000000146671324604713000210360ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ const int TOP = 1; const int BOTTOM = 2; const int NONE = 0; public class ScrollWidget : Gtk.ScrolledWindow { public signal void scrolled_to_start(double value); public signal void scrolled_to_end(); private double upper_cache; private double value_cache; private int balance = NONE; public double end_diff {get; set; default = 200;} private ulong scroll_down_id; private ulong scroll_up_id; public bool scrolled_down { get { return vadjustment.value >= vadjustment.upper - vadjustment.page_size - 5; } } public bool scrolled_up { get { return vadjustment.value <= 5; } } //Transition times private int64 start_time; private int64 end_time; private double transition_diff; private double transition_start_value; construct { vadjustment.notify["upper"].connect (keep_upper_func); vadjustment.notify["value"].connect (keep_value_func); } private void keep_upper_func() { // {{{ double upper = vadjustment.upper; if (balance == TOP){ double inc = (upper - upper_cache); this.vadjustment.value += inc; balance = NONE; } this.upper_cache = vadjustment.upper; this.value_cache = vadjustment.value; } // }}} private void keep_value_func () { // {{{ // Call the scrolled_to_top signal if necessary if(vadjustment.value < 10.0) { scrolled_to_start(vadjustment.value); } double max = vadjustment.upper - vadjustment.page_size; if (vadjustment.value >= max - end_diff) scrolled_to_end (); double upper = vadjustment.upper; if (balance == BOTTOM) { double inc = (upper - upper_cache); this.vadjustment.value -= inc; balance = NONE; } this.upper_cache = vadjustment.upper; this.value_cache = vadjustment.value; } // }}} public void balance_next_upper_change (int mode) { balance = mode; } /** * TODO: Update scroll_down_next * Scroll to the very top of the scrolled window once the next * size_allocate occurs. * This will use a transition if the correct Gtk+ settings is set * to true. * * @param animate Whether to animate/transition the change or not (default: true) */ public void scroll_up_next (bool animate = true, bool force_start = false) { // {{{ if (!this.get_mapped ()) { this.vadjustment.value = 0; return; } // TODO: I really can't stand the duplication here if (force_start) { if (Gtk.Settings.get_default ().gtk_enable_animations && animate) { this.start_time = this.get_frame_clock ().get_frame_time (); this.end_time = start_time + TRANSITION_DURATION; this.transition_diff = - this.vadjustment.value; this.transition_start_value = vadjustment.value; this.add_tick_callback (scroll_up_tick_cb); } else { this.vadjustment.value = 0; } } else { if (scroll_up_id != 0) { this.transition_diff = -this.vadjustment.value; this.transition_start_value = this.vadjustment.value; return; } scroll_up_id = this.vadjustment.notify["upper"].connect (() => { if (Gtk.Settings.get_default ().gtk_enable_animations && animate) { this.start_time = this.get_frame_clock ().get_frame_time (); this.end_time = start_time + TRANSITION_DURATION; this.transition_diff = - this.vadjustment.value; this.transition_start_value = vadjustment.value; this.add_tick_callback (scroll_up_tick_cb); } else { this.vadjustment.value = 0; } this.vadjustment.disconnect (scroll_up_id); this.scroll_up_id = 0; }); } } // }}} /** * Scroll to the very end of the scrolled window once the next * size_alocate occurs. * This will use a transition if the correct Gtk+ settings is set * to true * * @param animate Whether to animate/transition the change or not (default: true) * @param force_wait If this is set to true, we will wait for the next size_allocate * event, even if the widget is unmapped (default: false). */ public void scroll_down_next (bool animate = true, bool force_wait = false) { // {{{ if (!this.get_mapped () && !force_wait) { this.vadjustment.value = this.vadjustment.upper - this.vadjustment.page_size; return; } if (this.scroll_down_id != 0) return; scroll_down_id = this.size_allocate.connect (() => { if (Gtk.Settings.get_default ().gtk_enable_animations && animate) { this.start_time = this.get_frame_clock ().get_frame_time (); this.end_time = start_time + TRANSITION_DURATION; this.transition_diff = (vadjustment.upper - vadjustment.page_size - vadjustment.value); this.transition_start_value = this.vadjustment.value; this.add_tick_callback (scroll_up_tick_cb); } else { this.vadjustment.value = this.vadjustment.upper - this.vadjustment.page_size; } this.disconnect (scroll_down_id); this.scroll_down_id = 0; }); } // }}} /* This is essentially a straight-up vala port of the transition code in GtkStack/GtkRevealer */ private bool scroll_up_tick_cb (Gtk.Widget widget, Gdk.FrameClock frame_clock) { if (!this.get_mapped ()) { vadjustment.value = transition_start_value + transition_diff; return false; } int64 now = frame_clock.get_frame_time (); double t = 1.0; if (now < this.end_time) t = (now - start_time) / (double)(end_time - start_time); t = ease_out_cubic (t); this.vadjustment.value = transition_start_value + (t * transition_diff); if (this.vadjustment.value <= 0 || now >= end_time) { this.queue_draw (); return false; } return true; } } corebird-1.7.4/src/widgets/TextButton.vala000066400000000000000000000034721324604713000205440ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public class TextButton : Gtk.Button { public TextButton () {} construct { this.get_style_context ().add_class ("text-only-button"); } /** * Adds a GtkLabel to the Button using the given text as markup. * If the button already contains another child, that will either be replaced if it's * no instance of GtkLabel, or - if it's a GtkLabel already - be reused. * * @param text The markup to use(see pango markup) */ public void set_markup (string text) { Gtk.Label label = null; Gtk.Widget child = get_child (); if (child != null) { if (child is Gtk.Label) { label = (Gtk.Label)child; label.set_markup (text); } else { this.remove (child); label = new Gtk.Label (text); } } else { label = new Gtk.Label (text); } label.set_use_markup (true); label.set_justify (Gtk.Justification.CENTER); label.valign = Gtk.Align.BASELINE; label.ellipsize = Pango.EllipsizeMode.END; label.xalign = 0; label.visible = true; if(label.parent == null) this.add (label); } } corebird-1.7.4/src/widgets/TweetListBox.vala000066400000000000000000000142521324604713000210170ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ public class TweetListBox : Gtk.ListBox { private Gtk.Stack? placeholder = null; private Gtk.Label no_entries_label; private Gtk.Box error_box; private Gtk.Label error_label; private Gtk.Button retry_button; private TweetListEntry? _action_entry; public TweetListEntry? action_entry { get { return _action_entry; } } public signal void retry_button_clicked (); public Cb.DeltaUpdater delta_updater; public unowned Account account; public Cb.TweetModel model = new Cb.TweetModel (); private Gtk.GestureMultiPress press_gesture; public TweetListBox () { } construct { add_placeholder (); this.set_selection_mode (Gtk.SelectionMode.NONE); this.press_gesture = new Gtk.GestureMultiPress (this); this.press_gesture.set_button (0); this.press_gesture.set_propagation_phase (Gtk.PropagationPhase.BUBBLE); this.press_gesture.pressed.connect (gesture_pressed_cb); this.delta_updater = new Cb.DeltaUpdater (this); Settings.get ().bind ("double-click-activation", this, "activate-on-single-click", GLib.SettingsBindFlags.INVERT_BOOLEAN); Cb.Utils.bind_model (this, this.model, widget_create_func); } private Gtk.Widget widget_create_func (GLib.Object obj) { assert (obj is Cb.Tweet); var row = new TweetListEntry ((Cb.Tweet) obj, (MainWindow) get_toplevel (), this.account); row.fade_in (); return row; } private void gesture_pressed_cb (int n_press, double x, double y) { Gdk.EventSequence sequence = this.press_gesture.get_current_sequence (); Gdk.EventButton event = (Gdk.EventButton)this.press_gesture.get_last_event (sequence); if (event.triggers_context_menu ()) { /* From gtklistbox.c */ Gdk.Window? event_window = event.window; Gdk.Window window = this.get_window (); double relative_y = event.y; double parent_y; while ((event_window != null) && (event_window != window)) { event_window.coords_to_parent (0, relative_y, null, out parent_y); relative_y = parent_y; event_window = event_window.get_effective_parent (); } Gtk.Widget row = this.get_row_at_y ((int)relative_y); if (row is TweetListEntry && row.sensitive) { var tle = (TweetListEntry) row; if (tle != this._action_entry && this._action_entry != null && this._action_entry.shows_actions) { this._action_entry.toggle_mode (); } tle.toggle_mode (); if (tle.shows_actions) set_action_entry (tle); else set_action_entry (null); this.press_gesture.set_state (Gtk.EventSequenceState.CLAIMED); } } } private void set_action_entry (TweetListEntry? entry) { if (this._action_entry != null) { this._action_entry.destroy.disconnect (action_entry_destroyed_cb); this._action_entry = null; } if (entry != null) { this._action_entry = entry; this._action_entry.destroy.connect (action_entry_destroyed_cb); } } private void action_entry_destroyed_cb () { this._action_entry = null; } private void add_placeholder () { placeholder = new Gtk.Stack (); placeholder.transition_type = Gtk.StackTransitionType.CROSSFADE; var loading_label = new Gtk.Label (_("Loading…")); loading_label.get_style_context ().add_class ("dim-label"); placeholder.add_named (loading_label, "spinner"); no_entries_label = new Gtk.Label (_("No entries found")); no_entries_label.get_style_context ().add_class ("dim-label"); no_entries_label.wrap_mode = Pango.WrapMode.WORD_CHAR; placeholder.add_named (no_entries_label, "no-entries"); error_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 12); error_label = new Gtk.Label (""); error_label.get_style_context ().add_class ("dim-label"); error_label.margin = 12; error_label.selectable = true; error_label.wrap = true; retry_button = new Gtk.Button.with_label (_("Retry")); retry_button.set_halign (Gtk.Align.CENTER); retry_button.clicked.connect (() => { placeholder.visible_child_name = "spinner"; retry_button_clicked (); }); error_box.add (error_label); error_box.add (retry_button); placeholder.add_named (error_box, "error"); placeholder.visible_child_name = "spinner"; placeholder.show_all (); placeholder.set_valign (Gtk.Align.CENTER); placeholder.set_halign (Gtk.Align.CENTER); this.set_placeholder (placeholder); } public void set_empty () { placeholder.visible_child_name = "no-entries"; } public void set_unempty () { placeholder.visible_child_name = "spinner"; } public void set_error (string err_msg) { error_label.label = err_msg; placeholder.visible_child_name = "error"; } public Gtk.Stack? get_placeholder () { return placeholder; } public void set_placeholder_text (string text) { no_entries_label.label = text; } public void reset_placeholder_text () { no_entries_label.label = _("No entries found"); } public void remove_all () { this.foreach ((w) => { remove (w); }); } public Gtk.Widget? get_first_visible_row () { int i = 0; Gtk.Widget? row = this.get_row_at_index (0); while (row != null && !row.visible) { i ++; row = this.get_row_at_index (i); } return row; } } corebird-1.7.4/src/widgets/UserListsWidget.vala000066400000000000000000000275601324604713000215310ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/user-lists-widget.ui")] class UserListsWidget : Gtk.Box { [GtkChild] private Gtk.Label user_list_label; [GtkChild] private Gtk.ListBox user_list_box; [GtkChild] private Gtk.Frame user_list_frame; [GtkChild] private Gtk.Label subscribed_list_label; [GtkChild] private Gtk.ListBox subscribed_list_box; [GtkChild] private Gtk.Frame subscribed_list_frame; [GtkChild] private NewListEntry new_list_entry; [GtkChild] private Gtk.Revealer user_lists_revealer; [GtkChild] private Gtk.Separator upper_separator; [GtkChild] private Gtk.ListBox new_list_box; public unowned MainWindow main_window { get; set; } public unowned Account account { get; set; } private bool show_create_entry = true; construct { user_list_box.set_header_func (default_header_func); user_list_box.set_sort_func (ListListEntry.sort_func); subscribed_list_box.set_header_func (default_header_func); subscribed_list_box.set_sort_func (ListListEntry.sort_func); } public void hide_user_list_entry () { new_list_entry.hide (); new_list_entry.no_show_all = true; user_list_label.visible = true; //user_list_frame.margin_top = 24; show_create_entry = false; upper_separator.visible = false; upper_separator.no_show_all = true; } [GtkCallback] private void row_activated (Gtk.ListBoxRow row) { if (row is NewListEntry) { ((NewListEntry)row).reveal (); } else { var entry = (ListListEntry) row; var bundle = new Cb.Bundle (); bundle.put_int64 (ListStatusesPage.KEY_LIST_ID, entry.id); bundle.put_string (ListStatusesPage.KEY_NAME, entry.name); bundle.put_bool (ListStatusesPage.KEY_USER_LIST, entry.user_list); bundle.put_string (ListStatusesPage.KEY_DESCRIPTION, entry.description); bundle.put_string (ListStatusesPage.KEY_CREATOR, entry.creator_screen_name); bundle.put_int (ListStatusesPage.KEY_N_SUBSCRIBERS, entry.n_subscribers); bundle.put_int (ListStatusesPage.KEY_N_MEMBERS, entry.n_members); bundle.put_int64 (ListStatusesPage.KEY_CREATED_AT, entry.created_at); bundle.put_string (ListStatusesPage.KEY_MODE, entry.mode); main_window.main_widget.switch_page (Page.LIST_STATUSES, bundle); } } public async void load_lists (int64 user_id) { // {{{ if (user_id == 0) user_id = account.id; var collect_obj = new Collect (2); collect_obj.finished.connect (() => { load_lists.callback (); }); var call = account.proxy.new_call (); call.set_function ("1.1/lists/subscriptions.json"); call.set_method ("GET"); call.add_param ("count", "200"); call.add_param ("user_id", user_id.to_string ()); Cb.Utils.load_threaded_async.begin (call, null, (_, res) => { Json.Node? root = null; try { root = Cb.Utils.load_threaded_async.end (res); } catch (GLib.Error e) { warning (e.message); } uint n_subscribed_list = lists_received_cb (root, subscribed_list_box); if (n_subscribed_list == 0) { subscribed_list_box.hide (); subscribed_list_frame.hide (); subscribed_list_label.hide (); } else { subscribed_list_box.show (); subscribed_list_frame.show (); subscribed_list_label.show (); } collect_obj.emit (); }); var user_call = account.proxy.new_call (); user_call.set_function ("1.1/lists/ownerships.json"); user_call.set_method ("GET"); user_call.add_param ("user_id", user_id.to_string ()); user_call.add_param ("count", "200"); Cb.Utils.load_threaded_async.begin (user_call, null, (_, res) => { Json.Node? root = null; try { root = Cb.Utils.load_threaded_async.end (res); } catch (GLib.Error e) { warning (e.message); } uint n_user_list = lists_received_cb (root, user_list_box); if (n_user_list == 0 && !show_create_entry) { user_list_label.hide (); user_list_box.hide (); user_list_frame.hide (); user_list_frame.margin_top = 0; } else { user_list_label.visible = !show_create_entry; user_list_frame.margin_top = show_create_entry ? 24 : 0; user_list_box.show (); user_list_frame.show (); user_lists_revealer.reveal_child = n_user_list > 0; } collect_obj.emit (); }); yield; } // }}} private uint lists_received_cb (Json.Node? root, Gtk.ListBox list_box) { // {{{ if (root == null) return 0; var arr = root.get_object ().get_array_member ("lists"); arr.foreach_element ((array, index, node) => { var obj = node.get_object (); var entry = new ListListEntry.from_json_data (obj, account); list_box.add (entry); }); return arr.get_length (); } // }}} public void remove_list (int64 list_id) { uint n_user_lists = user_list_box.get_children ().length (); user_list_box.foreach ((w) => { if (!(w is ListListEntry)) return; if (((ListListEntry)w).id == list_id) { user_list_box.remove (w); if (n_user_lists - 1 == 0) user_lists_revealer.reveal_child = false; } }); subscribed_list_box.foreach ((w) => { if (!(w is ListListEntry)) return; if (((ListListEntry)w).id == list_id) { subscribed_list_box.remove (w); } }); if (subscribed_list_box.get_children ().length () == 0) { subscribed_list_label.hide (); subscribed_list_frame.hide (); } } public void add_list (ListListEntry entry) { if (entry.user_list) { // Avoid duplicates var user_lists = user_list_box.get_children (); foreach (Gtk.Widget w in user_lists) { if (!(w is ListListEntry)) continue; if (((ListListEntry)w).id == entry.id) return; } user_list_box.add (entry); user_lists_revealer.reveal_child = true; } else { // Avoid duplicates var subscribed_lists = subscribed_list_box.get_children (); foreach (Gtk.Widget w in subscribed_lists) { if (!(w is ListListEntry)) continue; if (((ListListEntry)w).id == entry.id) return; } subscribed_list_box.add (entry); subscribed_list_frame.show (); subscribed_list_box.show (); subscribed_list_label.show (); } } public void update_list (int64 list_id, string name, string description, string mode) { user_list_box.foreach ((w) => { if (!(w is ListListEntry)) return; var lle = (ListListEntry) w; if (lle.id == list_id) { lle.name = name; lle.description = description; lle.mode = mode; lle.queue_draw (); } }); } public void update_member_count (int64 list_id, int increase) { var lists = user_list_box.get_children (); foreach (var list in lists) { if (!(list is ListListEntry)) continue; var lle = (ListListEntry) list; if (lle.id == list_id) { lle.n_members += increase; break; } } } public TwitterList[] get_user_lists () { GLib.List children = user_list_box.get_children (); TwitterList[] lists = new TwitterList[children.length ()]; int i = 0; foreach (Gtk.Widget w in children) { assert (w is ListListEntry); var lle = (ListListEntry) w; lists[i].id = lle.id; lists[i].name = lle.name; lists[i].description = lle.description; lists[i].mode = lle.mode; lists[i].n_members = lle.n_members; i ++; } return lists; } public void clear_lists () { user_list_box.foreach ((w) => { user_list_box.remove (w);}); subscribed_list_box.foreach ((w) => {subscribed_list_box.remove (w);}); } [GtkCallback] private void new_list_create_activated_cb (string list_name) { // {{{ if (list_name.strip ().length <= 0) return; new_list_entry.sensitive = false; var call = account.proxy.new_call (); call.set_function ("1.1/lists/create.json"); call.set_method ("POST"); call.add_param ("name", list_name); call.invoke_async.begin (null, (o, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), e.message, GLib.Log.LINE, GLib.Log.FILE, this.main_window); new_list_entry.sensitive = true; return; } var parser = new Json.Parser (); try { parser.load_from_data (call.get_payload ()); } catch (GLib.Error e) { critical (e.message); return; } var root = parser.get_root ().get_object (); var entry = new ListListEntry.from_json_data (root, account); add_list (entry); var bundle = new Cb.Bundle (); bundle.put_int64 (ListStatusesPage.KEY_LIST_ID, entry.id); bundle.put_string (ListStatusesPage.KEY_NAME, entry.name); bundle.put_bool (ListStatusesPage.KEY_USER_LIST, true); bundle.put_string (ListStatusesPage.KEY_DESCRIPTION, entry.description); bundle.put_string (ListStatusesPage.KEY_CREATOR, entry.creator_screen_name); bundle.put_int (ListStatusesPage.KEY_N_SUBSCRIBERS, entry.n_subscribers); bundle.put_int (ListStatusesPage.KEY_N_MEMBERS, entry.n_members); bundle.put_int64 (ListStatusesPage.KEY_CREATED_AT, entry.created_at); bundle.put_string (ListStatusesPage.KEY_MODE, entry.mode); main_window.main_widget.switch_page (Page.LIST_STATUSES, bundle); new_list_entry.sensitive = true; }); } // }}} public void unreveal () { new_list_entry.unreveal (); } [GtkCallback] private bool new_list_box_keynav_failed_cb (Gtk.DirectionType direction) { if (direction == Gtk.DirectionType.DOWN) { if (user_list_box.visible) { user_list_box.child_focus (direction); return Gdk.EVENT_STOP; } else if (subscribed_list_box.visible) { subscribed_list_box.child_focus (direction); return Gdk.EVENT_STOP; } } return Gdk.EVENT_PROPAGATE; } [GtkCallback] private bool user_list_box_keynav_failed_cb (Gtk.DirectionType direction) { if (direction == Gtk.DirectionType.UP) { if (new_list_box.visible) { new_list_box.child_focus (direction); return Gdk.EVENT_STOP; } } else if (direction == Gtk.DirectionType.DOWN) { if (subscribed_list_box.visible) { subscribed_list_box.child_focus (direction); return Gdk.EVENT_STOP; } } return Gdk.EVENT_PROPAGATE; } [GtkCallback] private bool subscribed_list_box_keynav_failed_cb (Gtk.DirectionType direction) { if (direction == Gtk.DirectionType.UP) { if (user_list_box.visible) { user_list_box.child_focus (direction); return Gdk.EVENT_STOP; } else if (new_list_box.visible) { new_list_box.child_focus (direction); return Gdk.EVENT_STOP; } } return Gdk.EVENT_PROPAGATE; } [GtkCallback] private void revealer_child_revealed_cb (GLib.Object source, GLib.ParamSpec spec) { Gtk.Revealer revealer = (Gtk.Revealer) source; if (revealer.child_revealed) revealer.show (); else revealer.hide (); } } corebird-1.7.4/src/window/000077500000000000000000000000001324604713000154125ustar00rootroot00000000000000corebird-1.7.4/src/window/AboutDialog.vala000066400000000000000000000020171324604713000204510ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/about-dialog.ui")] class AboutDialog : Gtk.AboutDialog { [GtkCallback] private void response_cb (int id) { if (id == Gtk.ResponseType.DELETE_EVENT) { this.close (); } else warning ("Unhandled response: %d", id); } } corebird-1.7.4/src/window/AccountDialog.vala000066400000000000000000000351621324604713000210020ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/account-dialog.ui")] public class AccountDialog : Gtk.Window { private const int MAX_DESCRIPTION_LENGTH = 160; private const string PAGE_NORMAL = "normal"; private const string PAGE_DELETE = "delete"; [GtkChild] private Gtk.Entry name_entry; [GtkChild] private AvatarBannerWidget avatar_banner_widget; [GtkChild] private Gtk.Stack delete_stack; [GtkChild] private Gtk.Switch autostart_switch; [GtkChild] private Gtk.Entry website_entry; [GtkChild] private CompletionTextView description_text_view; [GtkChild] private CropWidget crop_widget; [GtkChild] private Gtk.Stack content_stack; [GtkChild] private Gtk.Box info_box; [GtkChild] private Gtk.Label error_label; [GtkChild] private Gtk.Button save_button; [GtkChild] private Gtk.Label description_length_label; private unowned Account account; private string old_user_name; private string old_description; private string old_website; private Gdk.Pixbuf? new_avatar = null; private Gdk.Pixbuf? new_banner = null; private int old_width = 0; private int old_height = 0; private bool account_was_not_initied = false; public AccountDialog (Account account) { this.account = account; name_entry.text = account.name; avatar_banner_widget.set_account (account); description_text_view.set_account (account); set_transient_data (account.website, account.description); autostart_switch.freeze_notify (); string[] startup_accounts = Settings.get ().get_strv ("startup-accounts"); foreach (unowned string acc in startup_accounts) { if (acc == this.account.screen_name) { autostart_switch.active = true; break; } } autostart_switch.thaw_notify (); avatar_banner_widget.avatar_changed.connect ((p) => { new_avatar = p; }); avatar_banner_widget.banner_changed.connect ((b) => { new_banner = b; }); if (account.proxy == null) { account_was_not_initied = true; account.init_proxy (); account.query_user_info_by_screen_name.begin (null, (obj, res) => { set_transient_data (account.website, account.description); }); } Gtk.AccelGroup ag = new Gtk.AccelGroup (); ag.connect (Gdk.Key.Escape, 0, Gtk.AccelFlags.LOCKED, escape_pressed_cb); description_text_view.buffer.notify["text"].connect (update_description_length); this.add_accel_group (ag); this.update_description_length (); } private void update_description_length () { int length = description_text_view.buffer.text.length; description_length_label.label = "%d/160".printf (length); if (length > MAX_DESCRIPTION_LENGTH) { save_button.sensitive = false; } else { save_button.sensitive = true; } } public override void destroy () { if (account != null) { if (account_was_not_initied) { account.uninit (); } account = null; } base.destroy (); } private bool escape_pressed_cb () { this.destroy (); return Gdk.EVENT_STOP; } private void set_transient_data (string? website, string? description) { website_entry.text = account.website ?? ""; old_user_name = account.name; old_website = account.website ?? ""; old_description = account.description ?? ""; description_text_view.get_buffer ().set_text (account.description ?? ""); } [GtkCallback] private void delete_button_clicked_cb () { delete_stack.visible_child_name = PAGE_DELETE; } private void save_data () { bool needs_save = (old_user_name != name_entry.text) || (old_description != description_text_view.buffer.text) || (old_website != website_entry.text); bool needs_init = needs_save || (new_avatar != null) || (new_banner != null); if (needs_init && account.proxy == null) { account.init_proxy (); } if (needs_save) { debug ("Saving data..."); var call = account.proxy.new_call (); call.set_function ("1.1/account/update_profile.json"); call.set_method ("POST"); call.add_param ("url", website_entry.text); call.add_param ("name", name_entry.text); call.add_param ("description", description_text_view.buffer.text); call.invoke_async.begin (null, (obj, res) => { try { call.invoke_async.end (res); debug ("Profile successfully updated"); } catch (GLib.Error e) { warning (e.message); Utils.show_error_object (call.get_payload (), "Could not update profile", GLib.Log.LINE, GLib.Log.FILE, this); } }); /* Update local user data */ account.name = name_entry.text; account.description = description_text_view.buffer.text; account.website = website_entry.text; } if (new_avatar != null) { debug ("Updating avatar..."); uint8[] buffer; try { new_avatar.save_to_buffer (out buffer, "png", null); } catch (GLib.Error e) { warning (e.message); return; } string b64 = GLib.Base64.encode (buffer); var call = account.proxy.new_call (); call.set_function ("1.1/account/update_profile_image.json"); call.set_method ("POST"); call.add_param ("skip_status", "true"); call.add_param ("include_entities", "false"); call.add_param ("image", b64); call.invoke_async.begin (null, (obj, res) => { try { call.invoke_async.end (res); debug ("Avatar successfully updated"); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), "Could not update your avatar", GLib.Log.LINE, GLib.Log.FILE, this); return; } /* Locally set new avatar */ var s = Gdk.cairo_surface_create_from_pixbuf (new_avatar, 1, null); account.set_new_avatar (s); }); } if (new_banner != null) { debug ("Updating banner..."); uint8[] buffer; // XXX With large banners, this can be too slow... try { new_banner.save_to_buffer (out buffer, "png", null); } catch (GLib.Error e) { warning (e.message); return; } string b64 = GLib.Base64.encode (buffer); var call = account.proxy.new_call (); call.set_function ("1.1/account/update_profile_banner.json"); call.set_method ("POST"); call.add_param ("banner", b64); call.invoke_async.begin (null, (obj, res) => { try { call.invoke_async.end (res); debug ("Banner successfully updated"); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), "Could not update your banner", GLib.Log.LINE, GLib.Log.FILE, this); } }); } } [GtkCallback] private void delete_confirm_button_clicked_cb () { /* - Close open window of that account - Remove the account from the db, disk, etc. - Remove the account from the app menu - If this would close the last opened window, set the account of that window to NULL */ int64 acc_id = account.id; FileUtils.remove (Dirs.config (@"accounts/$(acc_id).db")); FileUtils.remove (Dirs.config (@"accounts/$(acc_id).png")); FileUtils.remove (Dirs.config (@"accounts/$(acc_id)_small.png")); Corebird.db.exec (@"DELETE FROM `accounts` WHERE `id`='$(acc_id)';"); /* Remove account from startup accounts, if it's in there */ string[] startup_accounts = Settings.get ().get_strv ("startup-accounts"); for (int i = 0; i < startup_accounts.length; i++) if (startup_accounts[i] == account.screen_name) { string[] sa_new = new string[startup_accounts.length - 1]; for (int x = 0; x < i; i++) sa_new[x] = startup_accounts[x]; for (int x = i+1; x < startup_accounts.length; x++) sa_new[x] = startup_accounts[x]; Settings.get ().set_strv ("startup-accounts", sa_new); } Corebird cb = (Corebird) GLib.Application.get_default (); /* Handle windows, i.e. if this MainWindow is the last open one, we want to use it to show the "new account" UI, otherwise we just close it. */ unowned GLib.List windows = cb.get_windows (); Gtk.Window? account_window = null; int n_main_windows = 0; foreach (Gtk.Window win in windows) { if (win is MainWindow) { n_main_windows ++; if (((MainWindow)win).account.id == this.account.id) { account_window = win; } } } debug ("Open main windows: %d", n_main_windows); if (account_window != null) { if (n_main_windows > 1) account_window.destroy (); else ((MainWindow)account_window).change_account (null); } /* Remove the account from the global list of accounts */ Account acc_to_remove = Account.query_account_by_id (account.id); cb.account_removed (acc_to_remove); Account.remove_account (account.screen_name); /* Close this dialog */ this.destroy (); } [GtkCallback] private void delete_cancel_button_clicked_cb () { delete_stack.visible_child_name = PAGE_NORMAL; } [GtkCallback] private void autostart_switch_activate_cb () { bool active = autostart_switch.active; string[] startup_accounts = Settings.get ().get_strv ("startup-accounts"); if (active) { foreach (unowned string acc in startup_accounts) { if (acc == this.account.screen_name) { return; } } string[] new_startup_accounts = new string[startup_accounts.length + 1]; int i = 0; foreach (unowned string s in startup_accounts) { new_startup_accounts[i] = s; i ++; } new_startup_accounts[new_startup_accounts.length - 1] = this.account.screen_name; Settings.get ().set_strv ("startup-accounts", new_startup_accounts); } else { string[] new_startup_accounts = new string[startup_accounts.length - 1]; int i = 0; foreach (unowned string acc in startup_accounts) { if (acc != this.account.screen_name) { new_startup_accounts[i] = acc; i ++; } } Settings.get ().set_strv ("startup-accounts", new_startup_accounts); } } private void show_crop_image_selector () { var filechooser = new Gtk.FileChooserNative (_("Select Banner Image"), this, Gtk.FileChooserAction.OPEN, _("Open"), _("Cancel")); var filter = new Gtk.FileFilter (); filter.add_mime_type ("image/png"); filter.add_mime_type ("image/jpeg"); filechooser.set_filter (filter); if (filechooser.run () == Gtk.ResponseType.ACCEPT) { string selected_file = filechooser.get_filename (); Gdk.Pixbuf? image = null; try { image = new Gdk.Pixbuf.from_file (selected_file); } catch (GLib.Error e) { warning (e.message); return; } /* Values for banner */ int min_width = 200; int min_height = 100; if (crop_widget.desired_aspect_ratio == 1.0) { /* Avatar */ min_width = 48; min_height = 48; } if (image.get_width () >= min_width && image.get_height () >= min_height) { crop_widget.set_image (image); save_button.sensitive = true; } else { string error_str = ""; error_str += _("Image does not meet minimum size requirements:") + "\n"; error_str += ngettext ("Minimum width: %d pixel", "Minimum width: %d pixels", min_width) .printf (min_width) + "\n"; error_str += ngettext ("Minimum height: %d pixel", "Minimum height: %d pixels", min_height) .printf (min_height); error_label.label = error_str; content_stack.visible_child = error_label; save_button.sensitive = false; } } else { content_stack.visible_child = info_box; } } [GtkCallback] private void avatar_clicked_cb () { this.get_size (out old_width, out old_height); this.resize (400, 400); crop_widget.set_image (null); crop_widget.set_size_request (-1, 400); crop_widget.desired_aspect_ratio = 1.0; crop_widget.set_min_size (48); content_stack.visible_child = crop_widget; show_crop_image_selector (); save_button.label = _("Pick"); } [GtkCallback] private void banner_clicked_cb () { this.get_size (out old_width, out old_height); this.resize (700, 350); crop_widget.set_size_request (700, 350); crop_widget.set_image (null); crop_widget.desired_aspect_ratio = 2.0; crop_widget.set_min_size (200); content_stack.visible_child = crop_widget; show_crop_image_selector (); save_button.label = _("Pick"); } [GtkCallback] private void cancel_button_clicked_cb () { if (content_stack.visible_child == crop_widget || content_stack.visible_child == error_label) { this.resize (old_width, old_height); old_width = 0; old_height = 0; /* Just go back */ content_stack.visible_child = info_box; save_button.label = _("Save"); } else { this.destroy (); } } [GtkCallback] private void save_button_clicked_cb () { if (content_stack.visible_child == crop_widget) { Gdk.Pixbuf new_pixbuf = crop_widget.get_cropped_image (); if (crop_widget.desired_aspect_ratio == 1.0) { /* Avatar */ avatar_banner_widget.set_avatar (new_pixbuf); new_avatar = new_pixbuf; } else if (crop_widget.desired_aspect_ratio == 2.0) { /* Banner */ avatar_banner_widget.set_banner (new_pixbuf); new_banner = new_pixbuf; } else { GLib.assert_not_reached (); } save_button.label = _("Save"); content_stack.visible_child = info_box; } else { save_data (); this.destroy (); } } } corebird-1.7.4/src/window/ComposeTweetWindow.vala000066400000000000000000000341221324604713000220670ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/compose-window.ui")] class ComposeTweetWindow : Gtk.ApplicationWindow { const int DEFAULT_WIDTH = 450; public enum Mode { NORMAL, REPLY, QUOTE } [GtkChild] private AvatarWidget avatar_image; [GtkChild] private Gtk.Grid content_grid; [GtkChild] private CompletionTextView tweet_text; [GtkChild] private Gtk.Label length_label; [GtkChild] private Gtk.Button send_button; [GtkChild] private Gtk.Spinner title_spinner; [GtkChild] private Gtk.Label title_label; [GtkChild] private Gtk.Stack title_stack; [GtkChild] private ComposeImageManager compose_image_manager; [GtkChild] private Gtk.Button add_image_button; [GtkChild] private Gtk.Stack stack; [GtkChild] private Gtk.Grid image_error_grid; [GtkChild] private Gtk.Label image_error_label; [GtkChild] private Gtk.Button cancel_button; [GtkChild] private FavImageView fav_image_view; [GtkChild] private Gtk.Button fav_image_button; [GtkChild] private Gtk.Revealer completion_revealer; [GtkChild] private Gtk.ListBox completion_list; [GtkChild] private Gtk.Box add_button_box; private Cb.EmojiChooser? emoji_chooser = null; private Gtk.Button? emoji_button = null; private unowned Account account; private unowned Cb.Tweet reply_to; private Mode mode; private GLib.Cancellable? cancellable; private Gtk.ListBox? reply_list = null; private Cb.ComposeJob compose_job; public ComposeTweetWindow (MainWindow? parent, Account acc, Cb.Tweet? reply_to = null, Mode mode = Mode.NORMAL) { this.set_show_menubar (false); this.account = acc; this.reply_to = reply_to; this.mode = mode; this.tweet_text.set_account (acc); this.application = (Gtk.Application)GLib.Application.get_default (); this.cancellable = new GLib.Cancellable (); var upload_proxy = new Rest.OAuthProxy (Settings.get_consumer_key (), Settings.get_consumer_secret (), "https://upload.twitter.com/", false); upload_proxy.token = account.proxy.token; upload_proxy.token_secret = account.proxy.token_secret; this.compose_job = new Cb.ComposeJob (account.proxy, upload_proxy, this.cancellable); this.compose_job.image_upload_progress.connect ((path, progress) => { this.compose_image_manager.set_image_progress (path, progress); }); this.compose_job.image_upload_finished.connect ((path, error_msg) => { debug ("%s Finished!", path); this.compose_image_manager.end_progress (path, error_msg); }); if (this.mode == Mode.REPLY) this.compose_job.set_reply_id (this.reply_to.id); else if (this.mode == Mode.QUOTE) this.compose_job.set_quoted_tweet (this.reply_to); avatar_image.surface = acc.avatar; acc.notify["avatar"].connect (() => { avatar_image.surface = account.avatar; }); GLib.NetworkMonitor.get_default ().notify["network-available"].connect (update_send_button_sensitivity); length_label.label = Cb.Tweet.MAX_LENGTH.to_string (); tweet_text.buffer.changed.connect (update_send_button_sensitivity); if (parent != null) { this.set_transient_for (parent); this.set_modal (true); } if (mode != Mode.NORMAL) { reply_list = new Gtk.ListBox (); reply_list.selection_mode = Gtk.SelectionMode.NONE; TweetListEntry reply_entry = new TweetListEntry (reply_to, parent, acc, true); reply_entry.activatable = false; reply_entry.read_only = true; reply_entry.show (); reply_list.add (reply_entry); reply_list.show (); content_grid.attach (reply_list, 0, 0, 2, 1); } if (mode == Mode.QUOTE) { assert (reply_to != null); this.title_label.label = _("Quote tweet"); add_image_button.sensitive = false; fav_image_button.sensitive = false; } /* Let the text view immediately grab the keyboard focus */ tweet_text.grab_focus (); tweet_text.completion_listbox = this.completion_list; tweet_text.show_completion.connect (() => { completion_revealer.reveal_child = true; }); tweet_text.hide_completion.connect (() => { completion_revealer.reveal_child = false; }); Gtk.AccelGroup ag = new Gtk.AccelGroup (); ag.connect (Gdk.Key.Escape, 0, Gtk.AccelFlags.LOCKED, escape_pressed_cb); ag.connect (Gdk.Key.Return, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.LOCKED, () => {start_send_tweet (); return true;}); ag.connect (Gdk.Key.E, Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.LOCKED, () => {show_emoji_chooser (); return true;}); this.compose_image_manager.image_removed.connect ((path) => { this.compose_job.abort_image_upload (path); if (!this.compose_image_manager.full) { this.add_image_button.sensitive = true; this.fav_image_button.sensitive = true; } if (path.down ().has_suffix (".gif")) { fav_image_view.set_gifs_enabled (true); this.add_image_button.sensitive = true; this.fav_image_button.sensitive = true; } if (this.compose_image_manager.n_images == 0) { this.compose_image_manager.hide (); fav_image_view.set_gifs_enabled (true); } update_send_button_sensitivity (); }); this.add_accel_group (ag); string? last_tweet = account.db.select ("info").cols ("last_tweet").once_string (); if (last_tweet != null && last_tweet.length > 0 && tweet_text.get_buffer ().text.length == 0) { this.tweet_text.get_buffer ().text = last_tweet; } var image_target_list = new Gtk.TargetList (null); image_target_list.add_text_targets (0); /* The GTK+ version might not have this emoji data variant */ try { if (GLib.resources_get_info ("/org/gtk/libgtk/emoji/emoji.data", GLib.ResourceLookupFlags.NONE, null, null)) { setup_emoji_chooser (); } } catch (GLib.Error e) { // Ignore, just don't show the emoji chooser } this.set_default_size (DEFAULT_WIDTH, (int)(DEFAULT_WIDTH / 2.5)); } private void update_send_button_sensitivity () { Gtk.TextIter start, end; tweet_text.buffer.get_bounds (out start, out end); string text = tweet_text.buffer.get_text (start, end, true); int length = (int)Tl.count_characters (text); length_label.label = (Cb.Tweet.MAX_LENGTH - length).to_string (); if (length > 0 && length <= Cb.Tweet.MAX_LENGTH || (length == 0 && compose_image_manager.n_images > 0)) { bool network_reachable = GLib.NetworkMonitor.get_default ().network_available; send_button.sensitive = network_reachable; } else { send_button.sensitive = false; } } [GtkCallback] private void start_send_tweet () { if (!send_button.sensitive) return; title_stack.visible_child = title_spinner; title_spinner.start (); send_button.sensitive = false; tweet_text.sensitive = false; fav_image_button.sensitive = false; add_image_button.sensitive = false; compose_image_manager.insensitivize_buttons (); Gtk.TextIter start, end; tweet_text.buffer.get_start_iter (out start); tweet_text.buffer.get_end_iter (out end); this.compose_job.set_text (tweet_text.buffer.get_text (start, end, true)); this.compose_job.send_async.begin (this.cancellable, (obj, res) => { bool success = false; try { success = this.compose_job.send_async.end (res); } catch (GLib.Error e) { warning (e.message); } debug ("Tweet sent."); if (success) { /* Reset last_tweet */ account.db.update ("info").val ("last_tweet", "").run (); } else { /* Better save this tweet */ this.save_last_tweet (); } this.destroy (); }); } private void save_last_tweet () { if (this.reply_to == null) { string text = tweet_text.buffer.text; account.db.update ("info").val ("last_tweet", text).run (); } } [GtkCallback] private void cancel_clicked () { if (stack.visible_child == image_error_grid || stack.visible_child == emoji_chooser || stack.visible_child_name == "fav-images") { stack.visible_child = content_grid; cancel_button.label = _("Cancel"); /* Use this instead of just setting send_button.sensitive to true to avoid sending tweets with 0 length */ this.update_send_button_sensitivity (); } else { if (this.cancellable != null) { this.cancellable.cancel (); } this.save_last_tweet (); destroy (); } } private bool escape_pressed_cb () { this.cancel_clicked (); return Gdk.EVENT_STOP; } public void set_text (string text) { tweet_text.buffer.text = text; } [GtkCallback] private void add_image_clicked_cb (Gtk.Button source) { var filechooser = new Gtk.FileChooserNative (_("Select Image"), this, Gtk.FileChooserAction.OPEN, _("Open"), _("Cancel")); var filter = new Gtk.FileFilter (); filter.add_mime_type ("image/png"); filter.add_mime_type ("image/jpeg"); filter.add_mime_type ("image/gif"); filechooser.set_filter (filter); if (filechooser.run () == Gtk.ResponseType.ACCEPT) { var filename = filechooser.get_filename (); debug ("Loading %s", filename); /* Get file size */ var file = GLib.File.new_for_path (filename); GLib.FileInfo info; try { info = file.query_info (GLib.FileAttribute.STANDARD_TYPE + "," + GLib.FileAttribute.STANDARD_CONTENT_TYPE + "," + GLib.FileAttribute.STANDARD_SIZE, 0); } catch (GLib.Error e) { warning ("%s (%s)", e.message, filename); // TODO: Proper error checking return; } if (!info.get_content_type ().has_prefix ("image/")) { stack.visible_child = image_error_grid; image_error_label.label = _("Selected file is not an image."); cancel_button.label = _("Back"); send_button.sensitive = false; } else if (info.get_size () > Twitter.MAX_BYTES_PER_IMAGE) { stack.visible_child = image_error_grid; image_error_label.label = _("The selected image is too big. The maximum file size per image is %'d MB") .printf (Twitter.MAX_BYTES_PER_IMAGE / 1024 / 1024); cancel_button.label = _("Back"); send_button.sensitive = false; } else if (filename.has_suffix (".gif") && this.compose_image_manager.n_images > 0) { stack.visible_child = image_error_grid; image_error_label.label = _("Only one GIF file per tweet is allowed."); cancel_button.label = _("Back"); send_button.sensitive = false; } else { this.compose_image_manager.show (); this.compose_image_manager.load_image (filename, null); this.compose_job.upload_image_async (filename); if (this.compose_image_manager.n_images > 0) { fav_image_view.set_gifs_enabled (false); } if (this.compose_image_manager.full) { this.add_image_button.sensitive = false; this.fav_image_button.sensitive = false; } } } update_send_button_sensitivity (); } [GtkCallback] public void fav_image_button_clicked_cb () { cancel_button.label = _("Back"); stack.visible_child_name = "fav-images"; this.fav_image_view.load_images (); } [GtkCallback] public void favorite_image_selected_cb (string path) { cancel_clicked (); this.compose_image_manager.show (); this.compose_image_manager.load_image (path, null); this.compose_job.upload_image_async (path); if (this.compose_image_manager.full) { this.add_image_button.sensitive = false; this.fav_image_button.sensitive = false; } if (this.compose_image_manager.n_images > 0) fav_image_view.set_gifs_enabled (false); update_send_button_sensitivity (); } [GtkCallback] public void tweet_text_populate_popup_cb (Gtk.Menu popup) { if (this.emoji_chooser == null) return; var menuitem = new Gtk.MenuItem.with_label (_("Insert Emoji")); menuitem.activate.connect (show_emoji_chooser); menuitem.show (); popup.add (menuitem); } private void show_emoji_chooser () { if (this.emoji_chooser == null) return; this.emoji_button.clicked (); } private void setup_emoji_chooser () { this.emoji_chooser = new Cb.EmojiChooser (); if (!emoji_chooser.try_init ()) { this.emoji_chooser = null; return; } emoji_chooser.emoji_picked.connect ((text) => { this.tweet_text.insert_at_cursor (text); cancel_clicked (); }); emoji_chooser.show_all (); stack.add (emoji_chooser); this.emoji_button = new Gtk.Button.with_label ("🐧"); emoji_button.clicked.connect (() => { this.emoji_chooser.populate (); this.stack.visible_child = this.emoji_chooser; cancel_button.label = _("Back"); }); emoji_button.show_all (); add_button_box.add (emoji_button); } } corebird-1.7.4/src/window/MediaDialog.vala000066400000000000000000000111141324604713000204140ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/media-dialog.ui")] class MediaDialog : Gtk.Window { [GtkChild] private Gtk.Frame frame; [GtkChild] private Gtk.Revealer next_revealer; [GtkChild] private Gtk.Revealer previous_revealer; private unowned Cb.Tweet tweet; private int cur_index = 0; private Gtk.GestureMultiPress button_gesture; private double initial_px; private double initial_py; public MediaDialog (Cb.Tweet tweet, int start_media_index, double px = 0.0, double py = 0.0) { Cb.Media cur_media = tweet.get_medias()[start_media_index]; this.tweet = tweet; this.cur_index = start_media_index; this.button_gesture = new Gtk.GestureMultiPress (this); this.button_gesture.set_button (0); this.button_gesture.set_propagation_phase (Gtk.PropagationPhase.BUBBLE); this.button_gesture.released.connect (button_released_cb); this.initial_px = px; this.initial_py = py; if (tweet.get_medias ().length == 1) { next_revealer.hide (); previous_revealer.hide (); } change_media (cur_media); } private void button_released_cb (int n_press, double x, double y) { this.destroy (); button_gesture.set_state (Gtk.EventSequenceState.CLAIMED); } private void change_media (Cb.Media media) { /* Remove the current child */ var cur_child = frame.get_child (); int cur_width = 0, cur_height = 0, new_width, new_height; if (frame.get_child () != null) { frame.remove (cur_child); cur_child.get_size_request (out cur_width, out cur_height); } Gtk.Widget new_widget = null; if (media.is_video ()) { new_widget = new Cb.MediaVideoWidget (media); frame.add (new_widget); ((Cb.MediaVideoWidget)new_widget).start (); } else { new_widget = new Cb.MediaImageWidget (media); ((Cb.MediaImageWidget)new_widget).scroll_to (this.initial_px, this.initial_py); frame.add (new_widget); /* Reset to default values */ this.initial_px = 0.5; this.initial_py = 0.0; } new_widget.show_all (); new_widget.get_size_request (out new_width, out new_height); if ((new_width != cur_width || new_height != cur_height) && new_width > 0 && new_height > 0) { this.resize (new_width, new_height); } this.queue_resize (); next_revealer.set_visible (cur_index != tweet.get_medias ().length - 1); previous_revealer.set_visible (cur_index != 0); } private void next_media () { if (cur_index < tweet.get_medias ().length - 1) { cur_index ++; change_media (tweet.get_medias ()[cur_index]); } } private void previous_media () { if (cur_index > 0) { cur_index --; change_media (tweet.get_medias ()[cur_index]); } } [GtkCallback] private bool key_press_event_cb (Gdk.EventKey evt) { if (evt.keyval == Gdk.Key.Left) previous_media (); else if (evt.keyval == Gdk.Key.Right) next_media (); else this.destroy (); return Gdk.EVENT_PROPAGATE; } [GtkCallback] private void next_button_clicked_cb () { next_media (); } [GtkCallback] private void previous_button_clicked_cb () { previous_media (); } public override bool enter_notify_event (Gdk.EventCrossing event) { if (event.window == this.get_window () && event.detail != Gdk.NotifyType.INFERIOR) { next_revealer.reveal_child = true; previous_revealer.reveal_child = true; } return false; } public override bool leave_notify_event (Gdk.EventCrossing event) { if (event.window == this.get_window () && event.detail != Gdk.NotifyType.INFERIOR) { next_revealer.reveal_child = false; previous_revealer.reveal_child = false; } return false; } } corebird-1.7.4/src/window/ModifyFilterDialog.vala000066400000000000000000000067111324604713000220010ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/modify-filter-dialog.ui")] class ModifyFilterDialog : Gtk.Dialog { [GtkChild] private Gtk.Entry regex_entry; [GtkChild] private Gtk.Label regex_status_label; [GtkChild] private Gtk.TextView regex_test_text; [GtkChild] private Gtk.Button save_button; private GLib.Regex regex; private unowned Account account; private unowned Cb.Filter filter; private unowned MainWindow main_window; /** created will be true if the filter has just been created by the user(i.e. not modified) */ public signal void filter_added (Cb.Filter filter, bool created); public ModifyFilterDialog (MainWindow parent, Account account, Cb.Filter? filter = null) { GLib.Object (use_header_bar: Gtk.Settings.get_default ().gtk_dialogs_use_header ? 1 : 0); this.set_transient_for (parent); this.application = parent.get_application (); this.account = account; if (filter != null) { regex_entry.text = filter.get_contents (); this.title = _("Modify Filter"); } this.filter = filter; this.main_window = parent; } construct { regex_test_text.buffer.changed.connect (regex_entry_changed_cb); } public override void response (int response_id) { if (response_id == Gtk.ResponseType.CANCEL) { this.destroy (); } else if (response_id == Gtk.ResponseType.OK) { save_filter (); this.destroy (); } } [GtkCallback] private void regex_entry_changed_cb () { try { regex = new GLib.Regex (regex_entry.text); } catch (GLib.RegexError e) { regex_status_label.label = e.message; save_button.sensitive = false; return; } bool matches = regex.match (regex_test_text.buffer.text); if (matches) { regex_status_label.label = _("Matches"); } else { regex_status_label.label = _("Doesn’t match"); } save_button.sensitive = (regex_entry.text.length != 0); } private void save_filter () { string content = regex_entry.text; if (this.filter == null) { Cb.Filter f = Utils.create_persistent_filter (content, account); filter_added (f, true); } else { /* We update the existing filter */ account.db.update ("filters").val ("content", content) .where_eq ("id", filter.get_id ().to_string ()) .run (); for (int i = 0; i < account.filters.length; i ++) { var f = account.filters.get (i); if (f.get_id () == this.filter.get_id ()) { f.reset (content); filter_added (f, false); break; } } } /* Update timelines */ main_window.rerun_filters (); } } corebird-1.7.4/src/window/ModifySnippetDialog.vala000066400000000000000000000074471324604713000222050ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/modify-snippet-dialog.ui")] class ModifySnippetDialog : Gtk.Dialog { [GtkChild] private Gtk.Entry key_entry; [GtkChild] private Gtk.Entry value_entry; [GtkChild] private Gtk.Label error_label; [GtkChild] private Gtk.Button save_button; [GtkChild] private Gtk.Button delete_button; private string? old_key = null; public signal void snippet_updated (string? old_key, string? key, string? value); public ModifySnippetDialog (string? key = null, string? value = null) { GLib.Object (use_header_bar: Gtk.Settings.get_default ().gtk_dialogs_use_header ? 1 : 0); if (key != null) { assert (value != null); this.old_key = key; this.key_entry.text = key; this.value_entry.text = value; this.delete_button.show (); this.title = _("Modify Snippet"); } key_entry.buffer.inserted_text.connect (validate_input); key_entry.buffer.deleted_text.connect (validate_input); value_entry.buffer.inserted_text.connect (validate_input); value_entry.buffer.deleted_text.connect (validate_input); } private void validate_input () { string key = key_entry.text.strip (); string value = value_entry.text.strip (); key_entry.get_style_context ().remove_class ("error"); value_entry.get_style_context ().remove_class ("error"); error_label.label = ""; save_button.sensitive = true; if (key == "") { error_label.label = _("Snippet can’t be empty"); key_entry.get_style_context ().add_class ("error"); save_button.sensitive = false; return; } if (value == "") { error_label.label = _("Replacement can’t be empty"); value_entry.get_style_context ().add_class ("error"); save_button.sensitive = false; return; } if (key.contains (" ") || key.contains ("\t")) { error_label.label = _("Snippet may not contain whitespace"); key_entry.get_style_context ().add_class ("error"); save_button.sensitive = false; return; } if (Corebird.snippet_manager.get_snippet (key) != null && this.old_key != key) { error_label.label = _("Snippet already exists"); save_button.sensitive = false; return; } } private void save_snippet () { string new_value = this.value_entry.text; string new_key = this.key_entry.text; if (this.old_key != null) { Corebird.snippet_manager.set_snippet (old_key, new_key, new_value); } else { Corebird.snippet_manager.insert_snippet (new_key, new_value); } this.snippet_updated (old_key, new_key, new_value); } [GtkCallback] private void delete_button_clicked_cb () { assert (this.old_key != null); Corebird.snippet_manager.remove_snippet (this.old_key); this.snippet_updated (this.old_key, null, null); this.destroy (); } public override void response (int response_id) { if (response_id == Gtk.ResponseType.CANCEL) { this.destroy (); } else if (response_id == Gtk.ResponseType.OK) { save_snippet (); this.destroy (); } } } corebird-1.7.4/src/window/SettingsDialog.vala000066400000000000000000000233641324604713000212070ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ [GtkTemplate (ui = "/org/baedert/corebird/ui/settings-dialog.ui")] class SettingsDialog : Gtk.Window { [GtkChild] private Gtk.Switch on_new_mentions_switch; [GtkChild] private Gtk.Switch round_avatar_switch; [GtkChild] private Gtk.Switch on_new_dms_switch; [GtkChild] private Gtk.ComboBoxText on_new_tweets_combobox; [GtkChild] private Gtk.Switch auto_scroll_on_new_tweets_switch; [GtkChild] private Gtk.Stack main_stack; [GtkChild] private Gtk.Switch double_click_activation_switch; [GtkChild] private Gtk.ListBox sample_tweet_list; [GtkChild] private Gtk.Switch remove_trailing_hashtags_switch; [GtkChild] private Gtk.Switch remove_media_links_switch; [GtkChild] private Gtk.Switch hide_nsfw_content_switch; [GtkChild] private Gtk.ListBox snippet_list_box; [GtkChild] private Gtk.ComboBoxText media_visibility_combobox; private TweetListEntry sample_tweet_entry; private bool block_flag_emission = false; public SettingsDialog (Corebird application) { this.application = application; // Notifications Page Settings.get ().bind ("round-avatars", round_avatar_switch, "active", SettingsBindFlags.DEFAULT); Settings.get ().bind ("new-tweets-notify", on_new_tweets_combobox, "active-id", SettingsBindFlags.DEFAULT); Settings.get ().bind ("new-mentions-notify", on_new_mentions_switch, "active", SettingsBindFlags.DEFAULT); Settings.get ().bind ("new-dms-notify", on_new_dms_switch, "active", SettingsBindFlags.DEFAULT); // Interface page auto_scroll_on_new_tweets_switch.notify["active"].connect (() => { on_new_tweets_combobox.sensitive = !auto_scroll_on_new_tweets_switch.active; }); Settings.get ().bind ("auto-scroll-on-new-tweets", auto_scroll_on_new_tweets_switch, "active", SettingsBindFlags.DEFAULT); Settings.get ().bind ("double-click-activation", double_click_activation_switch, "active", SettingsBindFlags.DEFAULT); Settings.get ().bind ("media-visibility", media_visibility_combobox, "active-id", SettingsBindFlags.DEFAULT); // Tweets page // Set up sample tweet {{{ var sample_tweet = new Cb.Tweet (); sample_tweet.source_tweet = Cb.MiniTweet(); sample_tweet.source_tweet.author = Cb.UserIdentity() { id = 12, screen_name = "corebirdclient", user_name = "Corebird" }; string sample_text = _("Hey, check out this new #Corebird version! \\ (•◡•) / #cool #newisalwaysbetter"); Cairo.Surface? avatar_surface = null; try { var a = Gtk.IconTheme.get_default ().load_icon ("corebird", 48 * this.get_scale_factor (), Gtk.IconLookupFlags.FORCE_SIZE); avatar_surface = Gdk.cairo_surface_create_from_pixbuf (a, this.get_scale_factor (), this.get_window ()); } catch (GLib.Error e) { warning (e.message); } sample_tweet.source_tweet.text = sample_text; try { var regex = new GLib.Regex ("#\\w+"); GLib.MatchInfo match_info; bool matched = regex.match (sample_text, 0, out match_info); assert (matched); sample_tweet.source_tweet.entities = new Cb.TextEntity[3]; int i = 0; while (match_info.matches ()) { assert (match_info.get_match_count () == 1); int from, to; match_info.fetch_pos (0, out from, out to); string match = match_info.fetch (0); sample_tweet.source_tweet.entities[i] = Cb.TextEntity () { from = sample_text.char_count (from), to = sample_text.char_count (to), display_text = match, target = "foobar" }; match_info.next (); i ++; } } catch (GLib.RegexError e) { critical (e.message); } // Just to be sure TweetUtils.sort_entities (ref sample_tweet.source_tweet.entities); this.sample_tweet_entry = new TweetListEntry (sample_tweet, null, new Account (10, "", "")); sample_tweet_entry.set_avatar (avatar_surface); sample_tweet_entry.activatable = false; sample_tweet_entry.read_only = true; sample_tweet_entry.show (); this.sample_tweet_list.add (sample_tweet_entry); // }}} var text_transform_flags = Settings.get_text_transform_flags (); block_flag_emission = true; remove_trailing_hashtags_switch.active = (Cb.TransformFlags.REMOVE_TRAILING_HASHTAGS in text_transform_flags); remove_media_links_switch.active = (Cb.TransformFlags.REMOVE_MEDIA_LINKS in text_transform_flags); block_flag_emission = false; Settings.get ().bind ("hide-nsfw-content", hide_nsfw_content_switch, "active", SettingsBindFlags.DEFAULT); // Fill snippet list box Corebird.snippet_manager.query_snippets ((key, value) => { var e = new SnippetListEntry ((string)key, (string)value); e.show_all (); snippet_list_box.add (e); }); add_accels (); load_geometry (); } [GtkCallback] private bool window_destroy_cb () { save_geometry (); return Gdk.EVENT_PROPAGATE; } [GtkCallback] private void snippet_entry_activated_cb (Gtk.ListBoxRow row) { var snippet_row = (SnippetListEntry) row; var d = new ModifySnippetDialog (snippet_row.key, snippet_row.value); d.snippet_updated.connect (snippet_updated_func); d.set_transient_for (this); d.modal = true; d.show (); } [GtkCallback] private void add_snippet_button_clicked_cb () { var d = new ModifySnippetDialog (); d.snippet_updated.connect (snippet_updated_func); d.set_transient_for (this); d.modal = true; d.show (); } private void snippet_updated_func (string? old_key, string? key, string? value) { if (old_key != null && key == null && value == null) { foreach (var _row in snippet_list_box.get_children ()) { var srow = (SnippetListEntry) _row; if (srow.key == old_key) { srow.reveal (); break; } } return; } if (old_key == null) { var e = new SnippetListEntry (key, value); e.show_all (); snippet_list_box.add (e); } else { foreach (var _row in snippet_list_box.get_children ()) { var srow = (SnippetListEntry) _row; if (srow.key == old_key) { srow.key = key; srow.value = value; break; } } } } private void load_geometry () { GLib.Variant geom = Settings.get ().get_value ("settings-geometry"); int x = 0, y = 0, w = 0, h = 0; x = geom.get_child_value (0).get_int32 (); y = geom.get_child_value (1).get_int32 (); w = geom.get_child_value (2).get_int32 (); h = geom.get_child_value (3).get_int32 (); if (w == 0 || h == 0) return; this.move (x, y); this.set_default_size (w, h); } private void save_geometry () { var builder = new GLib.VariantBuilder (GLib.VariantType.TUPLE); int x = 0, y = 0, w = 0, h = 0; this.get_position (out x, out y); this.get_size (out w, out h); builder.add_value (new GLib.Variant.int32(x)); builder.add_value (new GLib.Variant.int32(y)); builder.add_value (new GLib.Variant.int32(w)); builder.add_value (new GLib.Variant.int32(h)); Settings.get ().set_value ("settings-geometry", builder.end ()); } private void add_accels () { Gtk.AccelGroup ag = new Gtk.AccelGroup(); ag.connect (Gdk.Key.Escape, 0, Gtk.AccelFlags.LOCKED, () => {this.close (); return true;}); ag.connect (Gdk.Key.@1, Gdk.ModifierType.MOD1_MASK, Gtk.AccelFlags.LOCKED, () => {main_stack.visible_child_name = "interface"; return true;}); ag.connect (Gdk.Key.@2, Gdk.ModifierType.MOD1_MASK, Gtk.AccelFlags.LOCKED, () => {main_stack.visible_child_name = "notifications"; return true;}); ag.connect (Gdk.Key.@3, Gdk.ModifierType.MOD1_MASK, Gtk.AccelFlags.LOCKED, () => {main_stack.visible_child_name = "tweet"; return true;}); ag.connect (Gdk.Key.@4, Gdk.ModifierType.MOD1_MASK, Gtk.AccelFlags.LOCKED, () => {main_stack.visible_child_name = "snippets"; return true;}); this.add_accel_group(ag); } [GtkCallback] private void remove_trailing_hashtags_cb () { if (block_flag_emission) return; if (remove_trailing_hashtags_switch.active) { Settings.add_text_transform_flag (Cb.TransformFlags.REMOVE_TRAILING_HASHTAGS); } else { Settings.remove_text_transform_flag (Cb.TransformFlags.REMOVE_TRAILING_HASHTAGS); } } [GtkCallback] private void remove_media_links_cb () { if (block_flag_emission) return; if (remove_media_links_switch.active) { Settings.add_text_transform_flag (Cb.TransformFlags.REMOVE_MEDIA_LINKS); } else { Settings.remove_text_transform_flag (Cb.TransformFlags.REMOVE_MEDIA_LINKS); } } } corebird-1.7.4/src/window/UserListDialog.vala000066400000000000000000000162411324604713000211550ustar00rootroot00000000000000/* This file is part of corebird, a Gtk+ linux Twitter client. * Copyright (C) 2013 Timm Bäder * * corebird is free software: you can 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. * * corebird is distributed in the hope that it will be useful, * but WITHOUT 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 corebird. If not, see . */ struct TwitterList { int64 id; string name; string description; string mode; uint n_members; } class UserListDialog : Gtk.Dialog { private unowned Account account; private unowned MainWindow main_window; private Gtk.ListBox list_list_box = new Gtk.ListBox (); private Gtk.Label placeholder_label; private int64 user_id; public UserListDialog (MainWindow parent, Account account, int64 user_id) { GLib.Object (use_header_bar: Gtk.Settings.get_default ().gtk_dialogs_use_header ? 1 : 0); this.title = _("Add to or Remove User From List"); this.main_window = parent; this.user_id = user_id; this.account = account; set_modal (true); set_transient_for (parent); set_default_size (250, 200); add_button (_("Cancel"), Gtk.ResponseType.CANCEL); add_button (_("Save"), Gtk.ResponseType.OK); set_default_response (Gtk.ResponseType.OK); var content_box = get_content_area (); content_box.border_width = 0; var scroller = new Gtk.ScrolledWindow (null, null); list_list_box.selection_mode = Gtk.SelectionMode.NONE; list_list_box.set_header_func (default_header_func); list_list_box.row_activated.connect ((row) => { if (!(row is ListUserEntry)) { warning ("Row != ListUserEntry!"); return; } ((ListUserEntry)row).toggle (); }); scroller.add (list_list_box); content_box.pack_start (scroller, true, true); placeholder_label = new Gtk.Label (_("You have no lists.")); placeholder_label.get_style_context ().add_class ("dim-label"); placeholder_label.show (); list_list_box.set_placeholder (placeholder_label); this.set_default_size (400, 200); } public void load_lists () { var lists_page = (ListsPage)main_window.get_page (Page.LISTS); lists_page.get_user_lists.begin ((obj, res) => { TwitterList[] lists = lists_page.get_user_lists.end (res); foreach (unowned TwitterList list in lists) { var l = new ListUserEntry (list.name, list.description); l.id = list.id; if (list.n_members >= 500) l.disable (); list_list_box.add (l); } this.show_all (); }); var call = account.proxy.new_call (); call.set_function ("1.1/lists/memberships.json"); call.add_param ("user_id", user_id.to_string ()); call.add_param ("filter_to_owned_lists", "true"); call.invoke_async.begin (null, (o, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), e.message, GLib.Log.LINE, GLib.Log.FILE, this); return; } var parser = new Json.Parser (); try { parser.load_from_data (call.get_payload ()); } catch (GLib.Error e) { critical (e.message); return; } var root = parser.get_root ().get_object (); var list_arr = root.get_array_member ("lists"); list_arr.foreach_element ((arr, index, node) => { int64 id = node.get_object ().get_int_member ("id"); list_list_box.@foreach ((w) => { var lue = (ListUserEntry) w; if (lue.id == id) { lue.check (); lue.enable (); } }); }); }); } public override void response (int response_id) { if (response_id == Gtk.ResponseType.CANCEL) { this.destroy (); } else if (response_id == Gtk.ResponseType.OK) { var list_entries = list_list_box.get_children (); foreach (Gtk.Widget w in list_entries) { var lue = (ListUserEntry) w; if (lue.changed) { debug ("VALUE CHANGED"); if (lue.active) { // Add user to the list add_user (lue.id); } else { // Remove user from the list remove_user (lue.id); } } } this.destroy (); } } private void add_user (int64 list_id) { var call = account.proxy.new_call (); call.set_function ("1.1/lists/members/create.json"); call.set_method ("POST"); call.add_param ("list_id", list_id.to_string ()); call.add_param ("user_id", user_id.to_string ()); call.invoke_async.begin (null, (o, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), e.message, GLib.Log.LINE, GLib.Log.FILE, this); } }); } private void remove_user (int64 list_id) { var call = account.proxy.new_call (); call.set_function ("1.1/lists/members/destroy.json"); call.set_method ("POST"); call.add_param ("list_id", list_id.to_string ()); call.add_param ("user_id", user_id.to_string ()); call.invoke_async.begin (null, (o, res) => { try { call.invoke_async.end (res); } catch (GLib.Error e) { Utils.show_error_object (call.get_payload (), e.message, GLib.Log.LINE, GLib.Log.FILE, this); } }); } } class ListUserEntry : Gtk.ListBoxRow { public int64 id; public new bool changed = false; private Gtk.CheckButton added_checkbox = new Gtk.CheckButton (); public bool active { get { return added_checkbox.active; } } public ListUserEntry (string list_name, string description) { var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 12); box.margin = 6; added_checkbox.valign = Gtk.Align.CENTER; added_checkbox.margin_start = 6; box.pack_start (added_checkbox, false, false); var box2 = new Gtk.Box (Gtk.Orientation.VERTICAL, 3); var label = new Gtk.Label ("" + list_name + ""); label.use_markup = true; label.halign = Gtk.Align.START; box2.pack_start (label, true, false); var desc_label = new Gtk.Label (description); desc_label.get_style_context ().add_class ("dim-label"); desc_label.halign = Gtk.Align.START; desc_label.ellipsize = Pango.EllipsizeMode.END; box2.pack_start (desc_label, true, false); box.pack_start (box2, true, true); add (box); added_checkbox.toggled.connect (() => { changed = !changed; }); } public void check () { added_checkbox.active = true; changed = false; } public void toggle () { added_checkbox.active = !added_checkbox.active; } public void disable () { this.added_checkbox.sensitive = false; } public void enable () { this.added_checkbox.sensitive = true; } } corebird-1.7.4/tests/000077500000000000000000000000001324604713000144565ustar00rootroot00000000000000corebird-1.7.4/tests/.gitignore000066400000000000000000000003251324604713000164460ustar00rootroot00000000000000*.log *.trs *.stamp-t _test.db utils bundlehistory usercounter filters sql tweetparsing multimedia friends tweetmodel texttransform avatardownload inlinemediadownloader avatarcache dmmanager usercompletionmodel corebird-1.7.4/tests/Makefile.am000066400000000000000000000063441324604713000165210ustar00rootroot00000000000000AM_CPPFLAGS = \ $(CB_CFLAGS) \ -I$(top_builddir)/src \ -I$(top_srcdir)/src \ -I$(top_srcdir)/src/rest/ \ -include $(CONFIG_HEADER) \ -DDATADIR=\"$(datadir)\" \ -DPKGDATADIR=\"$(pkgdatadir)\" \ -O0 \ -Wno-inline \ -D DEBUG AM_VALAFLAGS = \ --enable-checking \ --enable-experimental \ --vapidir $(top_builddir)/src \ --vapidir $(top_srcdir)/vapi \ --pkg corebird-internal \ --pkg rest-0.7 \ --gresources $(top_srcdir)/corebird.gresource.xml \ -C TESTS_ENVIRONMENT = GSETTINGS_SCHEMA_DIR=$(abs_top_builddir)/data TESTS = \ tweetmodel \ utils \ bundlehistory \ usercounter \ inlinemediadownloader \ tweetparsing \ filters \ texttransform \ avatardownload \ friends \ avatarcache \ usercompletionmodel \ dmmanager check_PROGRAMS = $(TESTS) tests_VALASOURCES = \ tweetmodel.vala \ utils.vala \ bundlehistory.vala \ usercounter.vala \ inlinemediadownloader.vala \ tweetparsing.vala \ filters.vala \ texttransform.vala \ avatardownload.vala \ friends.vala \ avatarcache.vala \ usercompletionmodel.vala \ dmmanager.vala $(tests_VALASOURCES:.vala=.c): tests_vala.stamp tests_vala.stamp: $(tests_VALASOURCES) $(top_builddir)/src/libcorebird.la $(top_builddir)/src/corebird.vapi Makefile for s in $(filter %.vala,$^); do \ $(VALAC) \ $(AM_VALAFLAGS) $(CB_VALA_FLAGS) \ --pkg corebird \ --enable-deprecated \ $$s; \ done; \ touch $@ resource_deps = $(shell $(GLIB_COMPILE_RESOURCES) --generate-dependencies --sourcedir=$(top_srcdir) $(top_srcdir)/corebird.gresource.xml) corebird-resources.c: $(top_srcdir)/corebird.gresource.xml $(resource_deps) Makefile XMLLINT=$(XMLLINT) $(GLIB_COMPILE_RESOURCES) --target $@ --generate --sourcedir=$(top_srcdir) --c-name corebird $< nodist_tweetmodel_SOURCES = tweetmodel.c tweetmodel_LDADD = $(top_builddir)/src/libcorebird.la nodist_utils_SOURCES = utils.c utils_LDADD = $(top_builddir)/src/libcorebird.la nodist_bundlehistory_SOURCES = bundlehistory.c bundlehistory_LDADD = $(top_builddir)/src/libcorebird.la nodist_usercounter_SOURCES = usercounter.c corebird-resources.c usercounter_LDADD = $(top_builddir)/src/libcorebird.la nodist_inlinemediadownloader_SOURCES = inlinemediadownloader.c inlinemediadownloader_LDADD = $(top_builddir)/src/libcorebird.la nodist_tweetparsing_SOURCES = tweetparsing.c corebird-resources.c tweetparsing_LDADD = $(top_builddir)/src/libcorebird.la nodist_filters_SOURCES = filters.c corebird-resources.c filters_LDADD = $(top_builddir)/src/libcorebird.la nodist_friends_SOURCES = friends.c corebird-resources.c friends_LDADD = $(top_builddir)/src/libcorebird.la nodist_texttransform_SOURCES = texttransform.c texttransform_LDADD = $(top_builddir)/src/libcorebird.la nodist_avatardownload_SOURCES = avatardownload.c corebird-resources.c avatardownload_LDADD = $(top_builddir)/src/libcorebird.la nodist_avatarcache_SOURCES = avatarcache.c avatarcache_LDADD = $(top_builddir)/src/libcorebird.la nodist_usercompletionmodel_SOURCES = usercompletionmodel.c usercompletionmodel_LDADD = $(top_builddir)/src/libcorebird.la nodist_dmmanager_SOURCES = dmmanager.c corebird-resources.c dmmanager_LDADD = $(top_builddir)/src/libcorebird.la CLEANFILES = \ tests_vala.stamp \ corebird-resources.c \ $(tests_VALASOURCES:.vala=.c) EXTRA_DIST = \ $(tests_VALASOURCES) corebird-1.7.4/tests/avatarcache.vala000066400000000000000000000026511324604713000175710ustar00rootroot00000000000000void simple () { var surface = new Cairo.ImageSurface (Cairo.Format.A8, 2, 2); var cache = new Cb.AvatarCache (); assert (cache.get_n_entries () == 0); cache.add (1337, surface, "some_url"); assert (cache.get_n_entries () == 1); cache.increase_refcount_for_surface (surface); assert (cache.get_n_entries () == 1); cache.decrease_refcount_for_surface (surface); assert (cache.get_n_entries () == 0); } void deferred_surface () { var surface = new Cairo.ImageSurface (Cairo.Format.A8, 2, 2); var cache = new Cb.AvatarCache (); cache.add (1337, null, "some_url"); assert (cache.get_n_entries () == 1); bool found; var cached_surface = cache.get_surface_for_id (1337, out found); assert (cached_surface == null); assert (found); cache.set_avatar (1337, surface, "some_url"); assert (cache.get_n_entries () == 1); cache.increase_refcount_for_surface (surface); cached_surface = cache.get_surface_for_id (1337, out found); assert (cached_surface == surface); assert (found); cache.decrease_refcount_for_surface (surface); assert (cache.get_n_entries () == 0); cached_surface = cache.get_surface_for_id (1337, out found); assert (cached_surface == null); assert (!found); } int main (string[] args) { GLib.Test.init (ref args); GLib.Test.add_func ("/avatarcache/simple", simple); GLib.Test.add_func ("/avatarcache/deferred_surface", deferred_surface); return GLib.Test.run (); } corebird-1.7.4/tests/avatardownload.vala000066400000000000000000000040601324604713000203310ustar00rootroot00000000000000 void simple () { var loop = new GLib.MainLoop (); Twitter.get ().init (); var avatar_widget = new AvatarWidget (); Twitter.get ().get_avatar.begin (10, "http://i.imgur.com/GzdoOMu.jpg", avatar_widget, 48, false, () => { assert (avatar_widget.surface != null); loop.quit (); }); loop.run (); } void cached () { var loop = new GLib.MainLoop (); Twitter.get ().init (); var avatar_widget = new AvatarWidget (); var avatar_widget2 = new AvatarWidget (); Twitter.get ().get_avatar.begin (10, "http://i.imgur.com/GzdoOMu.jpg", avatar_widget, 48, false, () => { assert (avatar_widget.surface != null); Twitter.get ().get_avatar.begin (10, "http://i.imgur.com/GzdoOMu.jpg", avatar_widget2, 48, false, () => { assert (avatar_widget2.surface != null); assert (avatar_widget2.surface == avatar_widget.surface); loop.quit (); }); }); loop.run (); } void double_download () { var loop = new GLib.MainLoop (); Twitter.get ().init (); var avatar_widget = new AvatarWidget (); var avatar_widget2 = new AvatarWidget (); Twitter.get ().get_avatar.begin (10, "http://i.imgur.com/GzdoOMu.jpg", avatar_widget, 48, false, () => { assert (avatar_widget.surface != null); loop.quit (); }); Twitter.get ().get_avatar.begin (10, "http://i.imgur.com/GzdoOMu.jpg", avatar_widget2, 48, false, () => { assert (avatar_widget2.surface != null); }); loop.run (); } int main (string[] args) { GLib.Test.init (ref args); Gtk.init (ref args); Settings.init (); Utils.init_soup_session (); GLib.Test.add_func ("/avatar-download/simple", simple); GLib.Test.add_func ("/avatar-download/cached", cached); GLib.Test.add_func ("/avatar-download/double_download", double_download); /* We can't test load_avatar_for_user_id here since we can't properly use Accounts and their proxies... */ return GLib.Test.run (); } corebird-1.7.4/tests/bundlehistory.vala000066400000000000000000000063741324604713000202300ustar00rootroot00000000000000 void all () { var history = new Cb.BundleHistory (); history.push (1, null); history.push (2, null); history.push (3, null); history.push (4, null); history.push (5, null); assert (history.get_current () == 5); history.back (); assert (history.get_current () == 4); history.back (); assert (history.get_current () == 3); history.forward (); assert (history.get_current () == 4); history.push (10, null); assert (history.get_current () == 10); //history.forward (); //assert (history.get_current () == 10); //history.forward (); //assert (history.get_current () == 10); } void end () { var history = new Cb.BundleHistory (); assert (history.at_end ()); history.push (1, null); assert (history.at_end ()); history.push (2, null); assert (history.at_end ()); history.back (); assert (!history.at_end ()); } void equals () { var bundle1 = new Cb.Bundle (); bundle1.put_string (0, "1"); bundle1.put_string (1, "3"); var bundle2 = new Cb.Bundle (); bundle2.put_string (1, "3"); bundle2.put_string (0, "1"); assert (bundle1.equals (bundle2)); assert (!bundle1.equals (null)); var bundle3 = new Cb.Bundle (); assert (!bundle3.equals (bundle1)); } void remove_current () { var history = new Cb.BundleHistory (); var bundle1 = new Cb.Bundle (); bundle1.put_string (0, "a"); bundle1.put_string (1, "b"); history.push (1, bundle1); var bundle2 = new Cb.Bundle (); bundle2.put_string (3, "c"); bundle2.put_string (4, "d"); history.push (2, bundle2); // push advances assert (history.at_end()); assert (history.get_current_bundle () == bundle2); assert (history.get_current () == 2); // remove_current deletes the current bundle but doesn't // to back history.remove_current (); assert (history.get_current_bundle () == null); assert (history.get_current () == -1); // This should bring us to bundle1 history.back (); assert (history.get_current_bundle () == bundle1); assert (history.get_current () == 1); history.remove_current (); assert (history.get_current_bundle () == null); assert (history.get_current () == -1); history.back (); assert (history.at_start ()); // Shouldn't do anything significant even if at_start() == true history.remove_current (); history.remove_current (); // Empty! assert (history.at_end ()); assert (history.at_start ()); var bundle3 = new Cb.Bundle (); bundle3.put_string (0, "_"); var bundle4 = new Cb.Bundle (); bundle4.put_string (7, "__"); var bundle5 = new Cb.Bundle (); bundle5.put_string (10, "___"); history.push (3, bundle3); history.push (4, bundle4); history.push (5, bundle5); assert (history.get_current () == 5); history.back (); assert (history.get_current () == 4); history.remove_current (); assert (history.get_current () == 5); // everything after 4 was moved one to the front assert (history.get_current_bundle () == bundle5); assert (history.at_end ()); } int main (string[] args) { GLib.Test.init (ref args); GLib.Test.add_func ("/bundlehistory/all", all); GLib.Test.add_func ("/bundlehistory/end", end); GLib.Test.add_func ("/bundlehistory/equals", equals); GLib.Test.add_func ("/bundlehistory/remove-current", remove_current); return GLib.Test.run (); } corebird-1.7.4/tests/dmmanager.vala000066400000000000000000000456731324604713000172750ustar00rootroot00000000000000 /// These are the original ids with two zeroes appended! const int64 RECIPIENT_ID = 11805587900; const int64 SENDER_ID = 146929708900; // Some utils Json.Object get_dm_object (string json_input) { var parser = new Json.Parser (); try { parser.load_from_data (json_input); } catch (GLib.Error e) { message ("%s", e.message); assert (false); } var root_node = parser.get_root (); var root_object = root_node.get_object (); assert (root_node != null); assert (root_object != null); var dm_object = root_object.get_object_member ("direct_message"); assert (dm_object != null); return dm_object; } void clear_account (Account acc) { FileUtils.remove (Dirs.config ("accounts/%s.db".printf (acc.id.to_string ()))); } void simple () { var account = new Account (10, "baedert", "BAEDERT"); var manager = new DMManager.for_account (account); var threads_model = manager.get_threads_model (); assert (threads_model != null); assert (threads_model.get_n_items () == 0); assert (manager.empty); } void simple_insert () { var account = new Account (RECIPIENT_ID, "baedert", "BAEDERT"); clear_account (account); account.init_database (); var manager = new DMManager.for_account (account); var threads_model = manager.get_threads_model (); var dm_obj = get_dm_object (DM_DATA1); manager.insert_message (dm_obj); // This should create a new thread. assert (!manager.empty); assert (threads_model.get_n_items () == 1); // ...with the correct info var thread = threads_model.get_item (0) as DMThread; assert (thread != null); assert (thread.user.id == SENDER_ID); assert (thread.user.user_name == "Hans Wurst"); assert (thread.user.screen_name == "wurst_hw"); // Same DM, but with different ID. // Should NOT create a new thread. var dm_obj2 = get_dm_object (DM_DATA2); manager.insert_message (dm_obj2); assert (threads_model.get_n_items () == 1); } void one_thread () { var account = new Account (RECIPIENT_ID, "baedert", "BAEDERT"); clear_account (account); account.init_database (); var manager = new DMManager.for_account (account); var threads_model = manager.get_threads_model (); var dm_obj = get_dm_object (DM_DATA1); manager.insert_message (dm_obj); // This should create a new thread. assert (!manager.empty); assert (threads_model.get_n_items () == 1); // ...with the correct info var thread = threads_model.get_item (0) as DMThread; assert (thread != null); assert (thread.user.id == SENDER_ID); assert (thread.user.user_name == "Hans Wurst"); assert (thread.user.screen_name == "wurst_hw"); // DM_DATA3 is the same DM, but with sender/recipient interchanged. dm_obj = get_dm_object (DM_DATA3); manager.insert_message (dm_obj); // ... so this should keep using the same thread. assert (threads_model.get_n_items () == 1); thread = threads_model.get_item (0) as DMThread; assert (thread != null); assert (thread.user.id == SENDER_ID); assert (thread.user.user_name == "Hans Wurst"); assert (thread.user.screen_name == "wurst_hw"); } void cached () { var account = new Account (RECIPIENT_ID, "baedert", "BAEDERT"); clear_account (account); account.init_database (); var manager = new DMManager.for_account (account); var threads_model = manager.get_threads_model (); var dm_obj = get_dm_object (DM_DATA1); manager.insert_message (dm_obj); // This should create a new thread. assert (!manager.empty); assert (threads_model.get_n_items () == 1); // ...with the correct info var thread = threads_model.get_item (0) as DMThread; assert (thread != null); assert (thread.user.id == SENDER_ID); assert (thread.user.user_name == "Hans Wurst"); assert (thread.user.screen_name == "wurst_hw"); // Get a new DMManager manager = new DMManager.for_account (account); threads_model = manager.get_threads_model (); manager.load_cached_threads (); // Same assertions as above... assert (!manager.empty); assert (threads_model.get_n_items () == 1); thread = threads_model.get_item (0) as DMThread; assert (thread != null); assert (thread.user.id == SENDER_ID); assert (thread.user.user_name == "Hans Wurst"); assert (thread.user.screen_name == "wurst_hw"); // Now insert a second DM var dm_obj2 = get_dm_object (DM_DATA2); manager.insert_message (dm_obj2); assert (threads_model.get_n_items () == 1); } void self () { var account = new Account (RECIPIENT_ID, "baedert", "BAEDERT"); clear_account (account); account.init_database (); var manager = new DMManager.for_account (account); var threads_model = manager.get_threads_model (); var dm_obj = get_dm_object (DM_DATA4); manager.insert_message (dm_obj); // DM_DATA4 contains a DM to the user themselves. assert (!manager.empty); assert (threads_model.get_n_items () == 1); var thread = threads_model.get_item (0) as DMThread; assert (thread != null); assert (thread.user.id == RECIPIENT_ID); assert (thread.user.screen_name == "baedert"); dm_obj = get_dm_object (DM_DATA1); manager.insert_message (dm_obj); assert (threads_model.get_n_items () == 2); dm_obj = get_dm_object (DM_DATA2); manager.insert_message (dm_obj); assert (threads_model.get_n_items () == 2); manager = new DMManager.for_account (account); threads_model = manager.get_threads_model (); assert (manager.empty); assert (threads_model.get_n_items () == 0); manager.load_cached_threads (); assert (threads_model.get_n_items () == 2); } void main (string[] args) { GLib.Test.init (ref args); GLib.Test.add_func ("/dmmanager/simple", simple); GLib.Test.add_func ("/dmmanager/simple-insert", simple_insert); GLib.Test.add_func ("/dmmanager/one_thread", one_thread); GLib.Test.add_func ("/dmmanager/cached", cached); GLib.Test.add_func ("/dmmanager/self", self); GLib.Test.run (); } // {{{ const string DM_DATA1= """ {"direct_message":{"id":905788395095457796,"id_str":"905788395095457796","text":"SUP FEGET","sender":{"id":11805587900,"id_str":"11805587900","name":"Schupp & Wupp","screen_name":"baedert","location":null,"url":"http:\/\/corebird.baedert.org","description":"Corebird developer by night, silently judging people on the train by day.","protected":false,"followers_count":221,"friends_count":70,"listed_count":4,"created_at":"Sat Feb 27 13:13:34 +0000 2010","favourites_count":159,"utc_offset":7200,"time_zone":"Bern","geo_enabled":false,"verified":false,"statuses_count":2142,"lang":"en","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C6E2EE","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme2\/bg.gif","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme2\/bg.gif","profile_background_tile":false,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/810507927941476352\/cSydClet_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/810507927941476352\/cSydClet_normal.jpg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/118055879\/1488791887","profile_link_color":"1F98C7","profile_sidebar_border_color":"C6E2EE","profile_sidebar_fill_color":"DAECF4","profile_text_color":"663B12","profile_use_background_image":true,"default_profile":false,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false,"translator_type":"none"},"sender_id":11805587900,"sender_id_str":"11805587900","sender_screen_name":"baedert","recipient":{"id":146929708900,"id_str":"146929708900","name":"Hans Wurst","screen_name":"wurst_hw","location":null,"url":null,"description":null,"protected":false,"followers_count":1,"friends_count":2,"listed_count":0,"created_at":"Thu May 30 08:47:22 +0000 2013","favourites_count":0,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":false,"statuses_count":31,"lang":"en","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_tile":false,"profile_image_url":"http:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png","profile_image_url_https":"https:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png","profile_link_color":"1DA1F2","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"default_profile":true,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false,"translator_type":"none"},"recipient_id":146929708900,"recipient_id_str":"146929708900","recipient_screen_name":"wurst_hw","created_at":"Thu Sep 07 13:42:36 +0000 2017","entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]} }}"""; const string DM_DATA2= """ {"direct_message":{"id":905788395095457797,"id_str":"905788395095457797","text":"Foo", "sender":{"id":11805587900,"id_str":"11805587900","name":"Schupp & Wupp","screen_name":"baedert","location":null,"url":"http:\/\/corebird.baedert.org","description":"Corebird developer by night, silently judging people on the train by day.","protected":false,"followers_count":221,"friends_count":70,"listed_count":4,"created_at":"Sat Feb 27 13:13:34 +0000 2010","favourites_count":159,"utc_offset":7200,"time_zone":"Bern","geo_enabled":false,"verified":false,"statuses_count":2142,"lang":"en","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C6E2EE","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme2\/bg.gif","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme2\/bg.gif","profile_background_tile":false,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/810507927941476352\/cSydClet_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/810507927941476352\/cSydClet_normal.jpg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/118055879\/1488791887","profile_link_color":"1F98C7","profile_sidebar_border_color":"C6E2EE","profile_sidebar_fill_color":"DAECF4","profile_text_color":"663B12","profile_use_background_image":true,"default_profile":false,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false,"translator_type":"none"},"sender_id":11805587900,"sender_id_str":"11805587900","sender_screen_name":"baedert","recipient":{"id":146929708900,"id_str":"146929708900","name":"Hans Wurst","screen_name":"wurst_hw","location":null,"url":null,"description":null,"protected":false,"followers_count":1,"friends_count":2,"listed_count":0,"created_at":"Thu May 30 08:47:22 +0000 2013","favourites_count":0,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":false,"statuses_count":31,"lang":"en","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_tile":false,"profile_image_url":"http:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png","profile_image_url_https":"https:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png","profile_link_color":"1DA1F2","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"default_profile":true,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false,"translator_type":"none"},"recipient_id":146929708900,"recipient_id_str":"146929708900","recipient_screen_name":"wurst_hw","created_at":"Thu Sep 07 13:42:36 +0000 2017","entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]} }}"""; const string DM_DATA3 = """ {"direct_message":{"id":905788395095457797,"id_str":"905788395095457798","text":"Foo2", "sender":{"id":146929708900,"id_str":"11805587900","name":"Schupp & Wupp","screen_name":"baedert","location":null,"url":"http:\/\/corebird.baedert.org","description":"Corebird developer by night, silently judging people on the train by day.","protected":false,"followers_count":221,"friends_count":70,"listed_count":4,"created_at":"Sat Feb 27 13:13:34 +0000 2010","favourites_count":159,"utc_offset":7200,"time_zone":"Bern","geo_enabled":false,"verified":false,"statuses_count":2142,"lang":"en","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C6E2EE","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme2\/bg.gif","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme2\/bg.gif","profile_background_tile":false,"profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/810507927941476352\/cSydClet_normal.jpg","profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/810507927941476352\/cSydClet_normal.jpg","profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/118055879\/1488791887","profile_link_color":"1F98C7","profile_sidebar_border_color":"C6E2EE","profile_sidebar_fill_color":"DAECF4","profile_text_color":"663B12","profile_use_background_image":true,"default_profile":false,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false,"translator_type":"none"},"sender_id":146929708900,"sender_id_str":"146929708900","sender_screen_name":"wurst_hw","recipient":{"id":11805587900,"id_str":"146929708900","name":"Hans Wurst","screen_name":"wurst_hw","location":null,"url":null,"description":null,"protected":false,"followers_count":1,"friends_count":2,"listed_count":0,"created_at":"Thu May 30 08:47:22 +0000 2013","favourites_count":0,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":false,"statuses_count":31,"lang":"en","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_tile":false,"profile_image_url":"http:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png","profile_image_url_https":"https:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png","profile_link_color":"1DA1F2","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"default_profile":true,"default_profile_image":false,"following":false,"follow_request_sent":false,"notifications":false,"translator_type":"none"},"recipient_id":11805587900,"recipient_id_str":"11805587900","recipient_screen_name":"baedert","created_at":"Thu Sep 07 13:42:36 +0000 2017","entities":{"hashtags":[],"symbols":[],"user_mentions":[],"urls":[]}} }"""; const string DM_DATA4 = """ { "direct_message":{ "id":905788395095457799, "text":"Foo2", "sender":{ "id":"11805587900", "name":"Schupp & Wupp", "screen_name":"baedert", "location":null, "url":"http:\/\/corebird.baedert.org", "description":"Corebird developer by night, silently judging people on the train by day.", "protected":false, "followers_count":221, "friends_count":70, "listed_count":4, "created_at":"Sat Feb 27 13:13:34 +0000 2010", "favourites_count":159, "utc_offset":7200, "time_zone":"Bern", "geo_enabled":false, "verified":false, "statuses_count":2142, "lang":"en", "contributors_enabled":false, "is_translator":false, "is_translation_enabled":false, "profile_background_color":"C6E2EE", "profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme2\/bg.gif", "profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme2\/bg.gif", "profile_background_tile":false, "profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/810507927941476352\/cSydClet_normal.jpg", "profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/810507927941476352\/cSydClet_normal.jpg", "profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/118055879\/1488791887", "profile_link_color":"1F98C7", "profile_sidebar_border_color":"C6E2EE", "profile_sidebar_fill_color":"DAECF4", "profile_text_color":"663B12", "profile_use_background_image":true, "default_profile":false, "default_profile_image":false, "following":false, "follow_request_sent":false, "notifications":false, "translator_type":"none" }, "sender_id":11805587900, "sender_id_str":"11805587900", "sender_screen_name":"baedert", "recipient":{ "id":"11805587900", "name":"Schupp & Wupp", "screen_name":"baedert", "location":null, "url":null, "description":null, "protected":false, "followers_count":1, "friends_count":2, "listed_count":0, "created_at":"Thu May 30 08:47:22 +0000 2013", "favourites_count":0, "utc_offset":null, "time_zone":null, "geo_enabled":false, "verified":false, "statuses_count":31, "lang":"en", "contributors_enabled":false, "is_translator":false, "is_translation_enabled":false, "profile_background_color":"C0DEED", "profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png", "profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png", "profile_background_tile":false, "profile_image_url":"http:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png", "profile_image_url_https":"https:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_normal.png", "profile_link_color":"1DA1F2", "profile_sidebar_border_color":"C0DEED", "profile_sidebar_fill_color":"DDEEF6", "profile_text_color":"333333", "profile_use_background_image":true, "default_profile":true, "default_profile_image":false, "following":false, "follow_request_sent":false, "notifications":false, "translator_type":"none" }, "recipient_id":11805587900, "recipient_id_str":"11805587900", "recipient_screen_name":"baedert", "created_at":"Thu Sep 07 13:42:36 +0000 2017", "entities":{ "hashtags":[ ], "symbols":[ ], "user_mentions":[ ], "urls":[ ] } } } """; // }}} corebird-1.7.4/tests/filters.vala000066400000000000000000000230571324604713000170020ustar00rootroot00000000000000 // {{{ const string TD1 = """ { "created_at" : "Mon May 05 06:48:32 +0000 2014", "id" : 463208606784311296, "id_str" : "463208606784311296", "text" : "RT @BlackForestTeam: DIESELSTÖRMERS Kickstarter is live! - Go and check it out right now!... http://t.co/ZVmefc0w5e", "source" : "web", "truncated" : false, "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 62574927, "id_str" : "62574927", "name" : "Frozenbyte", "screen_name" : "Frozenbyte", "location" : "Helsinki, Finland", "description" : "We're an independent game developer. Follow us on Twitter to get the latest news on our games! For support issues please get in touch via email.", "url" : "http://t.co/NlgW9k9ZXj", "entities" : { "url" : { "urls" : [ { "url" : "http://t.co/NlgW9k9ZXj", "expanded_url" : "http://www.frozenbyte.com", "display_url" : "frozenbyte.com", "indices" : [ 0, 22 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 5682, "friends_count" : 137, "listed_count" : 242, "created_at" : "Mon Aug 03 17:52:07 +0000 2009", "favourites_count" : 233, "utc_offset" : 10800, "time_zone" : "Helsinki", "geo_enabled" : false, "verified" : false, "statuses_count" : 1042, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "000000", "profile_background_image_url" : "http://pbs.twimg.com/profile_background_images/378800000117103508/abe5a14a1f3b0b78e9038e73bf6b812d.jpeg", "profile_background_image_url_https" : "https://pbs.twimg.com/profile_background_images/378800000117103508/abe5a14a1f3b0b78e9038e73bf6b812d.jpeg", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/1130292729/fb_newlogo_black480_normal.png", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/1130292729/fb_newlogo_black480_normal.png", "profile_link_color" : "CBA051", "profile_sidebar_border_color" : "000000", "profile_sidebar_fill_color" : "D4802D", "profile_text_color" : "4A2500", "profile_use_background_image" : true, "default_profile" : false, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "retweeted_status" : { "created_at" : "Tue Apr 29 11:00:25 +0000 2014", "id" : 461097667775725569, "id_str" : "461097667775725569", "text" : "DIESELSTÖRMERS Kickstarter is live! - Go and check it out right now!... http://t.co/ZVmefc0w5e #foobar", "source" : "Tumblr", "truncated" : false, "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 726763934, "id_str" : "726763934", "name" : "Black Forest Games", "screen_name" : "BlackForestTeam", "location" : "Offenburg", "description" : "South-german team that brought you Giana Sisters: Twisted Dreams", "url" : "http://t.co/BXuCnqlX50", "entities" : { "url" : { "urls" : [ { "url" : "http://t.co/BXuCnqlX50", "expanded_url" : "http://gianasisterstwisteddreams.com", "display_url" : "gianasisterstwisteddreams.com", "indices" : [ 0, 22 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 1145, "friends_count" : 308, "listed_count" : 35, "created_at" : "Mon Jul 30 20:11:50 +0000 2012", "favourites_count" : 116, "utc_offset" : 7200, "time_zone" : "Amsterdam", "geo_enabled" : false, "verified" : false, "statuses_count" : 1475, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/3694489354/ed399e59260bf71b10235dcd7eb56fe5_normal.png", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/3694489354/ed399e59260bf71b10235dcd7eb56fe5_normal.png", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/726763934/1369154494", "profile_link_color" : "0084B4", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : true, "default_profile" : true, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "retweet_count" : 6, "favorite_count" : 2, "entities" : { "hashtags" : [ ], "symbols" : [ ], "urls" : [ { "url" : "http://t.co/ZVmefc0w5e", "expanded_url" : "http://tmblr.co/ZTqD4s1ERcDZg", "display_url" : "tmblr.co/ZTqD4s1ERcDZg", "indices" : [ 72, 94 ] } ], "user_mentions" : [ ] }, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "lang" : "en" }, "retweet_count" : 6, "favorite_count" : 0, "entities" : { "hashtags" : [ ], "symbols" : [ ], "urls" : [ { "url" : "http://t.co/ZVmefc0w5e", "expanded_url" : "http://tmblr.co/ZTqD4s1ERcDZg", "display_url" : "tmblr.co/ZTqD4s1ERcDZg", "indices" : [ 93, 115 ] } ], "user_mentions" : [ { "screen_name" : "BlackForestTeam", "name" : "Black Forest Games", "id" : 726763934, "id_str" : "726763934", "indices" : [ 3, 19 ] } ] }, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "lang" : "en" } """; // """ // }}} void matches () { var f = new Cb.Filter ("a+"); assert (f.matches ("a")); } void matches_tweet () { var acc = new Account (12345, "foobar", "Foo Bar"); var filter = new Cb.Filter ("a+"); acc.add_filter (filter); var tweet = new Cb.Tweet (); var parser = new Json.Parser (); var now = new GLib.DateTime.now_local (); try { parser.load_from_data (TD1); } catch (GLib.Error e) { critical (e.message); return; } tweet.load_from_json (parser.get_root (), 0, now); } void same_user () { var acc = new Account (12345, "foobar", "Foo Bar"); var filter = new Cb.Filter ("a+"); acc.add_filter (filter); var tweet = new Cb.Tweet (); var parser = new Json.Parser (); var now = new GLib.DateTime.now_local (); try { parser.load_from_data (TD1); } catch (GLib.Error e) { critical (e.message); return; } tweet.load_from_json (parser.get_root (), 0, now); tweet.source_tweet.author.id = 12345; // Should always return false even if the filter(s) would match assert (!acc.filter_matches (tweet)); } void links () { var acc = new Account (12345, "foobar", "Foo Bar"); var filter = new Cb.Filter ("t\\.co"); acc.add_filter (filter); var tweet = new Cb.Tweet (); var parser = new Json.Parser (); var now = new GLib.DateTime.now_local (); try { parser.load_from_data (TD1); } catch (GLib.Error e) { critical (e.message); return; } tweet.load_from_json (parser.get_root (), 0, now); // This should never match since we should be using the // 'real' url instead of the t.co shortened one. assert (!acc.filter_matches (tweet)); // ... which is also why it should match now //message (tweet.get_real_text ()); acc.add_filter (new Cb.Filter ("tmblr")); assert (acc.filter_matches (tweet)); } void hashtags () { var acc = new Account (12345, "foobar", "Foo Bar"); var filter = new Cb.Filter ("#foobar"); acc.add_filter (filter); var tweet = new Cb.Tweet (); var parser = new Json.Parser (); var now = new GLib.DateTime.now_local (); try { parser.load_from_data (TD1); } catch (GLib.Error e) { critical (e.message); return; } tweet.load_from_json (parser.get_root (), 0, now); // This should never match since we should be using the // 'real' url instead of the t.co shortened one. assert (acc.filter_matches (tweet)); } int main (string[] args) { GLib.Test.init (ref args); Settings.init (); Gtk.init (ref args); Twitter.get ().init (); Dirs.create_dirs (); Utils.init_soup_session (); GLib.Test.add_func ("/filters/matches", matches); GLib.Test.add_func ("/filters/matches-tweet", matches_tweet); GLib.Test.add_func ("/filters/same-user", same_user); GLib.Test.add_func ("/filters/links", links); GLib.Test.add_func ("/filters/hashtags", hashtags); return GLib.Test.run (); } corebird-1.7.4/tests/friends.vala000066400000000000000000000030541324604713000167570ustar00rootroot00000000000000 void set () { Account account = new Account (1337, "Name", "Screen Name"); Json.Array friends = new Json.Array (); for (int i = 10; i <= 17; i ++) { friends.add_int_element (i); } account.set_friends (friends); for (int i = 10; i <= 17; i ++) { assert (account.follows_id (i)); } } void add () { Account account = new Account (1337, "Name", "Screen Name"); Json.Array friends = new Json.Array (); for (int i = 10; i <= 17; i ++) { friends.add_int_element (i); } account.set_friends (friends); account.follow_id (1337); assert (account.follows_id (1337)); for (int i = 10; i <= 17; i ++) { assert (account.follows_id (i)); } } void _remove () { Account account = new Account (1337, "Name", "Screen Name"); Json.Array friends = new Json.Array (); for (int i = 10; i <= 17; i ++) { friends.add_int_element (i); } account.set_friends (friends); account.unfollow_id (10); account.unfollow_id (11); account.unfollow_id (12); for (int i = 13; i <= 17; i ++) { assert (account.follows_id (i)); } assert (!account.follows_id (10)); assert (!account.follows_id (11)); assert (!account.follows_id (12)); account.unfollow_id (17); assert (!account.follows_id (17)); account.unfollow_id (17); assert (!account.follows_id (17)); } int main (string[] args) { GLib.Test.init (ref args); Dirs.create_dirs (); GLib.Test.add_func ("/friends/set", set); GLib.Test.add_func ("/friends/add", add); GLib.Test.add_func ("/friends/remove", _remove); return GLib.Test.run (); } corebird-1.7.4/tests/inlinemediadownloader.vala000066400000000000000000000064441324604713000216700ustar00rootroot00000000000000 void normal_download () { var url = "http://pbs.twimg.com/media/BiHRjmFCYAAEKFg.png"; var main_loop = new GLib.MainLoop (); var media = new Cb.Media (); media.url = url; Cb.MediaDownloader.get_default ().load_async.begin (media, () => { main_loop.quit (); }); main_loop.run (); } void animation_download () { var main_loop = new GLib.MainLoop (); var url = "http://i.imgur.com/rgF0Czu.gif"; var media = new Cb.Media (); media.url = url; Cb.MediaDownloader.get_default ().load_async.begin (media, () => { main_loop.quit (); }); main_loop.run (); } void download_twice () { var main_loop = new GLib.MainLoop (); var url = "http://pbs.twimg.com/media/BiHRjmFCYAAEKFg.png"; var media = new Cb.Media (); media.url = url; Cb.MediaDownloader.get_default ().load_async.begin (media, () => { var media2 = new Cb.Media (); media2.url = url; Cb.MediaDownloader.get_default ().load_async.begin (media2, () => { main_loop.quit (); }); }); main_loop.run (); } void double_download () { var main_loop = new GLib.MainLoop (); var url = "http://pbs.twimg.com/media/BiHRjmFCYAAEKFg.png"; var media = new Cb.Media (); media.url = url; var collect_obj = new Collect (5); Cb.MediaDownloader.get_default ().load_async.begin (media, () => { assert (!media.invalid); collect_obj.emit (); }); media = new Cb.Media (); media.url = url; Cb.MediaDownloader.get_default ().load_async.begin (media, () => { assert (!media.invalid); collect_obj.emit (); }); media = new Cb.Media (); media.url = url; Cb.MediaDownloader.get_default ().load_async.begin (media, () => { assert (!media.invalid); collect_obj.emit (); }); media = new Cb.Media (); media.url = url; Cb.MediaDownloader.get_default ().load_async.begin (media, () => { assert (!media.invalid); collect_obj.emit (); }); media = new Cb.Media (); media.url = url; Cb.MediaDownloader.get_default ().load_async.begin (media, () => { assert (!media.invalid); collect_obj.emit (); }); media = new Cb.Media (); media.url = url; collect_obj.finished.connect (() => { main_loop.quit (); }); main_loop.run (); assert (collect_obj.done); } void shutdown () { var main_loop = new GLib.MainLoop (); var url = "http://pbs.twimg.com/media/BiHRjmFCYAAEKFg.png"; var media = new Cb.Media (); media.url = url; Cb.MediaDownloader.get_default ().load_async.begin (media, () => { var media2 = new Cb.Media (); media2.url = url; Cb.MediaDownloader.get_default ().load_async.begin (media2); Cb.MediaDownloader.get_default ().shutdown (); main_loop.quit (); }); main_loop.run (); } int main (string[] args) { GLib.Test.init (ref args); GLib.Environment.set_variable ("GSETTINGS_BACKEND", "memory", true); Gtk.init (ref args); Settings.init (); Dirs.create_dirs (); Utils.init_soup_session (); GLib.Test.add_func ("/media/normal-download", normal_download); GLib.Test.add_func ("/media/animation-download", animation_download); GLib.Test.add_func ("/media/download-twice", download_twice); GLib.Test.add_func ("/media/double-download", double_download); GLib.Test.add_func ("/media/shutdown", shutdown); int retval = GLib.Test.run (); Cb.MediaDownloader.get_default ().shutdown (); return retval; } corebird-1.7.4/tests/meson.build000066400000000000000000000012031324604713000166140ustar00rootroot00000000000000 tests = [ 'avatarcache', 'avatardownload', 'bundlehistory', 'dmmanager', 'filters', 'friends', 'inlinemediadownloader', 'texttransform', 'tweetmodel', 'tweetparsing', 'twitteritem', 'usercompletionmodel', 'usercounter', 'utils', ] foreach test_name : tests testcase = executable( test_name, test_name + '.vala', meson.source_root() + '/vapi/corebird-internal.vapi', meson.source_root() + '/vapi/rest-0.7.vapi', cb_resources, dependencies: cb_dep, vala_args: [ '--gresources=' + meson.source_root() + '/corebird.gresource.xml', ], ) test(test_name, testcase) endforeach corebird-1.7.4/tests/texttransform.vala000066400000000000000000000735341324604713000202570ustar00rootroot00000000000000 void normal () { var entities = new Cb.TextEntity[0]; string source_text = "foo bar foo"; string result = Cb.TextTransform.text (source_text, entities, 0, 0, 0); assert (result == source_text); } void simple () { var entities = new Cb.TextEntity[1]; entities[0] = Cb.TextEntity () { from = 4, to = 6, display_text = "display_text", tooltip_text = "tooltip_text", target = "target_text" }; string source_text = "foo bar foo"; string result = Cb.TextTransform.text (source_text, entities, 0, 0, 0); // Not the best asserts, but oh well assert (result.contains ("display_text")); assert (result.contains ("tooltip_text")); assert (result.contains ("target_text")); } void url_at_end () { var entities = new Cb.TextEntity[1]; entities[0] = Cb.TextEntity () { from = 8, to = 9, display_text = "display_text", tooltip_text = "tooltip_text", target = "target_text" }; string source_text = "foo bar foo"; string result = Cb.TextTransform.text (source_text, entities, 0, 0, 0); // Not the best asserts, but oh well assert (result.contains ("display_text")); assert (result.contains ("tooltip_text")); assert (result.contains ("target_text")); } void utf8 () { var entities = new Cb.TextEntity[1]; entities[0] = Cb.TextEntity () { from = 2, to = 6, display_text = "#foo", tooltip_text = "#foo", target = null }; string source_text = "× #foo"; string result = Cb.TextTransform.text (source_text, entities, Cb.TransformFlags.REMOVE_MEDIA_LINKS, 0, 0); assert (result.has_prefix ("× ")); } void expand_links () { var entities = new Cb.TextEntity[1]; entities[0] = Cb.TextEntity () { from = 2, to = 6, display_text = "displayfoobar", tooltip_text = "#foo", target = "target_url" }; string source_text = "× #foo"; string result = Cb.TextTransform.text (source_text, entities, Cb.TransformFlags.EXPAND_LINKS, 0, 0); assert (result.has_prefix ("× ")); assert (!result.contains ("displayfoobar")); assert (result.contains ("target_url")); } void multiple_links () { var entities = new Cb.TextEntity[4]; entities[0] = Cb.TextEntity () { from = 0, to = 22, display_text = "mirgehendirurlsaus.com", target = "http://mirgehendirurlsaus.com", tooltip_text = "http://mirgehendirurlsaus.com" }; entities[1] = Cb.TextEntity () { from = 26, to = 48, display_text = "foobar.com", target = "http://foobar.com", tooltip_text = "http://foobar.com" }; entities[2] = Cb.TextEntity () { from = 52, to = 74, display_text = "hahaaha.com", target = "http://hahaaha.com", tooltip_text = "http://hahaaha.com" }; entities[3] = Cb.TextEntity () { from = 77, to = 99, display_text = "huehue.org", target = "http://huehue.org", tooltip_text = "http://huehue.org" }; string text = "http://t.co/O5uZwJg31k http://t.co/BsKkxv8UG4 http://t.co/W8qs846ude http://t.co/x4bKoCusvQ"; string result = Cb.TextTransform.text (text, entities, 0, 0, 0); string spec = """mirgehendirurlsaus.com foobar.com hahaaha.com huehue.org"""; assert (result == spec); } void remove_only_trailing_hashtags () { string text = "Hey, #totally inappropriate @baedert! #baedertworship öä #thefeels "; var entities = new Cb.TextEntity[4]; entities[0] = Cb.TextEntity () { from = 5, to = 13, display_text = "#totally", target = "foobar" }; entities[1] = Cb.TextEntity () { from = 28, to = 36, display_text = "@baedert", target = "blubb" }; entities[2] = Cb.TextEntity () { from = 38, to = 53, display_text = "#baedertwhorship", target = "bla" }; entities[3] = Cb.TextEntity () { from = 57, to = 66, display_text = "#thefeels", target = "foobar" }; string result = Cb.TextTransform.text (text, entities, Cb.TransformFlags.REMOVE_TRAILING_HASHTAGS, 0, 0); assert (result.contains (">@baedert<")); // Mention should still be a link assert (result.contains (">#totally<")); assert (!result.contains ("#baedertworship")); assert (!result.contains ("#thefeels")); } void remove_multiple_trailing_hashtags () { string text = "Hey, #totally inappropriate @baedert! #baedertworship #thefeels #foobar"; var entities = new Cb.TextEntity[5]; entities[0] = Cb.TextEntity () { from = 5, to = 13, display_text = "#totally", target = "foobar" }; entities[1] = Cb.TextEntity () { from = 28, to = 36, display_text = "@baedert", target = "blubb" }; entities[2] = Cb.TextEntity () { from = 38, to = 53, display_text = "#baedertwhorship", target = "bla" }; entities[3] = Cb.TextEntity () { from = 54, to = 63, display_text = "#thefeels", target = "foobar" }; entities[4] = Cb.TextEntity () { from = 64, to = 71, display_text = "#foobar", target = "bla" }; string result = Cb.TextTransform.text (text, entities, Cb.TransformFlags.REMOVE_TRAILING_HASHTAGS, 0, 0); assert (result.contains (">@baedert<")); // Mention should still be a link assert (result.contains (">#totally<")); assert (!result.contains ("#baedertworship")); assert (!result.contains ("#thefeels")); assert (!result.contains ("#foobar")); } void trailing_hashtags_mention_before () { string text = "Hey, #totally inappropriate! #baedertworship @baedert #foobar"; var entities = new Cb.TextEntity[4]; entities[0] = Cb.TextEntity () { from = 5, to = 13, display_text = "#totally", target = "foobar" }; entities[1] = Cb.TextEntity () { from = 29, to = 44, display_text = "#baedertworship", target = "bla" }; entities[2] = Cb.TextEntity () { from = 45, to = 53, display_text = "@baedert", target = "foobar" }; entities[3] = Cb.TextEntity () { from = 54, to = 61, display_text = "#foobar", target = "bla" }; string result = Cb.TextTransform.text (text, entities, Cb.TransformFlags.REMOVE_TRAILING_HASHTAGS, 0, 0); assert (result.contains (">@baedert<")); // Mention should still be a link assert (result.contains (">#totally<")); assert (result.contains (">#baedertworship<")); assert (!result.contains ("#foobar")); } void whitespace_hashtags () { string text = "Hey, #totally inappropriate @baedert! #baedertworship #thefeels #foobar"; var entities = new Cb.TextEntity[5]; entities[0] = Cb.TextEntity () { from = 5, to = 13, display_text = "#totally", target = "foobar" }; entities[1] = Cb.TextEntity () { from = 28, to = 36, display_text = "@baedert", target = "blubb" }; entities[2] = Cb.TextEntity () { from = 38, to = 53, display_text = "#baedertwhorship", target = "bla" }; entities[3] = Cb.TextEntity () { from = 54, to = 63, display_text = "#thefeels", target = "foobar" }; entities[4] = Cb.TextEntity () { from = 64, to = 71, display_text = "#foobar", target = "bla" }; string result = Cb.TextTransform.text (text, entities, Cb.TransformFlags.REMOVE_TRAILING_HASHTAGS, 0, 0); assert (result.contains (">@baedert<")); // Mention should still be a link assert (result.contains (">#totally<")); assert (!result.contains ("#baedertworship")); assert (!result.contains ("#thefeels")); assert (!result.contains ("#foobar")); assert (!result.contains (" ")); // 3 spaces between the 3 hashtags } void trailing_hashtags_link_after () { string text = "Hey, #totally inappropriate @baedert! #baedertworship https://foobar.com"; var entities = new Cb.TextEntity[4]; entities[0] = Cb.TextEntity () { from = 5, to = 13, display_text = "#totally", target = "foobar" }; entities[1] = Cb.TextEntity () { from = 28, to = 36, display_text = "@baedert", target = "blubb" }; entities[2] = Cb.TextEntity () { from = 38, to = 53, display_text = "#baedertwhorship", target = "bla" }; entities[3] = Cb.TextEntity () { from = 54, to = 72, display_text = "BLA BLA BLA", target = "https://foobar.com" }; string result = Cb.TextTransform.text (text, entities, Cb.TransformFlags.REMOVE_TRAILING_HASHTAGS, 0, 0); assert (result.contains (">@baedert<")); // Mention should still be a link assert (result.contains (">#totally<")); assert (!result.contains ("#baedertworship")); } void no_quoted_link () { var t = new Cb.Tweet (); t.quoted_tweet = Cb.MiniTweet (); t.quoted_tweet.id = 1337; t.source_tweet = Cb.MiniTweet (); t.source_tweet.text = "Foobar Some text after."; t.source_tweet.entities = new Cb.TextEntity[1]; t.source_tweet.entities[0] = Cb.TextEntity () { from = 0, to = 6, target = "https://twitter.com/bla/status/1337", display_text = "sometextwhocares" }; Settings.add_text_transform_flag (Cb.TransformFlags.REMOVE_MEDIA_LINKS); string result = t.get_trimmed_text (Settings.get_text_transform_flags ()); assert (!result.contains ("1337")); assert (result.length > 0); } void new_reply () { /* * This tests a the 'new reply' behavior, see * https://dev.twitter.com/overview/api/upcoming-changes-to-tweets */ var t = new Cb.Tweet (); var parser = new Json.Parser (); try { parser.load_from_data (REPLY_TWEET_DATA); t.load_from_json (parser.get_root (), 1337, new GLib.DateTime.now_local ()); } catch (GLib.Error e) { assert (false); } assert (t.source_tweet.display_range_start == 115); //message ("Entities:"); //foreach (var e in t.source_tweet.entities) { //message ("'%s': %u, %u", e.display_text, e.from, e.to); //} var text = t.get_trimmed_text (Cb.TransformFlags.EXPAND_LINKS); message (text); /* Should not contain any mention */ assert (!text.contains ("@")); /* One of the entities is a URL, the expanded link should point to * eventbrite.com, not t.co */ assert (!text.contains ("t.co")); } void bug1 () { var t = new Cb.Tweet (); var parser = new Json.Parser (); try { parser.load_from_data (BUG1_DATA); t.load_from_json (parser.get_root (), 1337, new GLib.DateTime.now_local ()); } catch (GLib.Error e) { assert (false); } string filter_text = t.get_filter_text (); assert (filter_text.length > 0); } int main (string[] args) { GLib.Environment.set_variable ("GSETTINGS_BACKEND", "memory", true); Intl.setlocale (LocaleCategory.ALL, ""); GLib.Test.init (ref args); Settings.init (); GLib.Test.add_func ("/tt/normal", normal); GLib.Test.add_func ("/tt/simple", simple); GLib.Test.add_func ("/tt/url-at-end", url_at_end); GLib.Test.add_func ("/tt/utf8", utf8); GLib.Test.add_func ("/tt/expand-links", expand_links); GLib.Test.add_func ("/tt/multiple-links", multiple_links); GLib.Test.add_func ("/tt/remove-only-trailing-hashtags", remove_only_trailing_hashtags); GLib.Test.add_func ("/tt/remove-multiple-trailing-hashtags", remove_multiple_trailing_hashtags); GLib.Test.add_func ("/tt/trailing-hashtags-mention-before", trailing_hashtags_mention_before); GLib.Test.add_func ("/tt/whitespace-between-trailing-hashtags", whitespace_hashtags); GLib.Test.add_func ("/tt/trailing-hashtags-media-link-after", trailing_hashtags_link_after); GLib.Test.add_func ("/tt/no-quoted-link", no_quoted_link); GLib.Test.add_func ("/tt/new-reply", new_reply); GLib.Test.add_func ("/tt/bug1", bug1); return GLib.Test.run (); } // {{{ const string REPLY_TWEET_DATA = """ { "created_at" : "Mon Apr 17 15:16:18 +0000 2017", "id" : 853990508326252550, "id_str" : "853990508326252550", "full_text" : "@jjdesmond @_UBRAS_ @franalsworth @4Apes @katy4apes @theAliceRoberts @JaneGoodallUK @Jane_Goodall @JaneGoodallInst And here's the link for tickets again ... https://t.co/a9lOVMouNK", "truncated" : false, "display_text_range" : [ 115, 180 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "jjdesmond", "name" : "Jimmy Jenny Desmond", "id" : 21278482, "id_str" : "21278482", "indices" : [ 0, 10 ] }, { "screen_name" : "_UBRAS_", "name" : "Roots and Shoots UOB", "id" : 803329927974096896, "id_str" : "803329927974096896", "indices" : [ 11, 19 ] }, { "screen_name" : "franalsworth", "name" : "Fran", "id" : 776983919287754752, "id_str" : "776983919287754752", "indices" : [ 20, 33 ] }, { "screen_name" : "4Apes", "name" : "Ian Redmond", "id" : 155889035, "id_str" : "155889035", "indices" : [ 34, 40 ] }, { "screen_name" : "katy4apes", "name" : "Katy Jedamzik", "id" : 159608654, "id_str" : "159608654", "indices" : [ 41, 51 ] }, { "screen_name" : "theAliceRoberts", "name" : "Prof Alice Roberts", "id" : 260211154, "id_str" : "260211154", "indices" : [ 52, 68 ] }, { "screen_name" : "JaneGoodallUK", "name" : "Roots & Shoots UK", "id" : 423423823, "id_str" : "423423823", "indices" : [ 69, 83 ] }, { "screen_name" : "Jane_Goodall", "name" : "Jane Goodall", "id" : 235157216, "id_str" : "235157216", "indices" : [ 84, 97 ] }, { "screen_name" : "JaneGoodallInst", "name" : "JaneGoodallInstitute", "id" : 39822897, "id_str" : "39822897", "indices" : [ 98, 114 ] } ], "urls" : [ { "url" : "https://t.co/a9lOVMouNK", "expanded_url" : "https://www.eventbrite.com/e/working-with-apes-tickets-33089771397", "display_url" : "eventbrite.com/e/working-with…", "indices" : [ 157, 180 ] } ] }, "source" : "Twitter for iPhone", "in_reply_to_status_id" : 853925036696141824, "in_reply_to_status_id_str" : "853925036696141824", "in_reply_to_user_id" : 21278482, "in_reply_to_user_id_str" : "21278482", "in_reply_to_screen_name" : "jjdesmond", "user" : { "id" : 415472140, "id_str" : "415472140", "name" : "Ben Garrod", "screen_name" : "Ben_garrod", "location" : "Bristol&Norfolk", "description" : "Monkey-chaser, TV-talker, bone geek and Teaching Fellow at @AngliaRuskin https://t.co/FXbftdxxTJ", "url" : "https://t.co/1B9SDHfWoF", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/1B9SDHfWoF", "expanded_url" : "http://www.josarsby.com/ben-garrod", "display_url" : "josarsby.com/ben-garrod", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ { "url" : "https://t.co/FXbftdxxTJ", "expanded_url" : "http://www.anglia.ac.uk/science-and-technology/about/life-sciences/our-staff/ben-garrod", "display_url" : "anglia.ac.uk/science-and-te…", "indices" : [ 73, 96 ] } ] } }, "protected" : false, "followers_count" : 6526, "friends_count" : 1016, "listed_count" : 128, "created_at" : "Fri Nov 18 11:30:48 +0000 2011", "favourites_count" : 25292, "utc_offset" : 3600, "time_zone" : "London", "geo_enabled" : true, "verified" : true, "statuses_count" : 17224, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://pbs.twimg.com/profile_background_images/590945579024257024/2F1itaGz.jpg", "profile_background_image_url_https" : "https://pbs.twimg.com/profile_background_images/590945579024257024/2F1itaGz.jpg", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/615498558385557505/cwSloac3_normal.jpg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/615498558385557505/cwSloac3_normal.jpg", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/415472140/1477223840", "profile_link_color" : "0084B4", "profile_sidebar_border_color" : "FFFFFF", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : false, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 6, "favorite_count" : 7, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "lang" : "en" } """; const string BUG1_DATA = """ { "created_at":"Wed Jul 05 19:38:02 +0000 2017", "id":882685018904068105, "id_str":"882685018904068105", "text":"@maljehani10 @sWs8ycsI3krjWrE @bnt_alhofuf @itiihade12 @A_algrni @berota_q8 @fayadhalshamari @OKadour82 @K_ibraheem\u2026 https:\/\/t.co\/9uPkhLBtv4", "display_text_range":[ 117, 140 ], "source":"\u003ca href=\"http:\/\/twitter.com\/download\/iphone\" rel=\"nofollow\"\u003eTwitter for iPhone\u003c\/a\u003e", "truncated":true, "in_reply_to_status_id":882681872479813633, "in_reply_to_status_id_str":"882681872479813633", "in_reply_to_user_id":784249163206815744, "in_reply_to_user_id_str":"784249163206815744", "in_reply_to_screen_name":"maljehani10", "user":{ "id":328900753, "id_str":"328900753", "name":"\u0627\u062a\u062d\u0627\u062f\u064a \u0644\u0644\u0646\u062e\u0627\u0639", "screen_name":"LudKadol", "location":"21.469328,39.268171", "url":null, "description":"\u0627\u0644\u0639\u064a\u0646 \u062a\u0631\u0649 \u0648 \u062a\u0645\u064a\u0644 \u0648 \u0627\u0644\u0642\u0644\u0628 \u064a\u0639\u0634\u0642 \u0643\u0644 \u062c\u0645\u064a\u0644 \u062d\u0628\u0643 \u064a\u0627 \u0627\u062a\u064a \u064a\u062f\u0627\u0648\u064a \u0643\u0644 \u0639\u0644\u064a\u0644 \u0627\u062a\u062d\u0627\u062f\u064a \u060c \u0627\u0631\u0633\u0646\u0627\u0644\u064a \u060c \u0645\u064a\u0644\u0627\u0646\u064a \u060c \u0645\u062f\u0631\u064a\u062f\u064a D2ABFF98", "protected":false, "verified":false, "followers_count":1771, "friends_count":2111, "listed_count":3, "favourites_count":609, "statuses_count":19199, "created_at":"Mon Jul 04 06:57:10 +0000 2011", "utc_offset":10800, "time_zone":"Riyadh", "geo_enabled":false, "lang":"ar", "contributors_enabled":false, "is_translator":false, "profile_background_color":"C0DEED", "profile_background_image_url":"http:\/\/pbs.twimg.com\/profile_background_images\/396412541\/Abstract_3d_8.jpg", "profile_background_image_url_https":"https:\/\/pbs.twimg.com\/profile_background_images\/396412541\/Abstract_3d_8.jpg", "profile_background_tile":false, "profile_link_color":"0084B4", "profile_sidebar_border_color":"C0DEED", "profile_sidebar_fill_color":"DDEEF6", "profile_text_color":"333333", "profile_use_background_image":true, "profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/823799368255938560\/HhgWWlCA_normal.jpg", "profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/823799368255938560\/HhgWWlCA_normal.jpg", "profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/328900753\/1436433030", "default_profile":false, "default_profile_image":false, "following":null, "follow_request_sent":null, "notifications":null }, "geo":null, "coordinates":null, "place":null, "contributors":null, "is_quote_status":false, "extended_tweet":{ "full_text":"@maljehani10 @sWs8ycsI3krjWrE @bnt_alhofuf @itiihade12 @A_algrni @berota_q8 @fayadhalshamari @OKadour82 @K_ibraheem @Adnan_Jas @othmanmali @ADEL_MARDI @battalalgoos \u0628\u0648\u0644\u0648\u0646\u064a \u0628\u064a\u062a\u0648\u0631\u0643\u0627 \u062a\u0631\u0627\u0648\u0633\u064a \u0645\u0627\u0631\u0643\u064a\u0646\u0647\u0648 \u062c\u064a\u0632\u0627\u0648\u064a \u0646\u0627\u062f\u064a \u0647\u062c\u0631 \u062f\u064a\u0627\u0643\u064a\u062a\u064a \u0645\u0648\u0646\u062a\u0627\u0631\u064a \u0627\u0644\u062e ...", "display_text_range":[ 165, 235 ], "entities":{ "hashtags":[ ], "urls":[ ], "user_mentions":[ { "screen_name":"maljehani10", "name":"\u0645\u062d\u0645\u062f \u0623\u0628\u0648 \u0633\u0627\u0631\u064a", "id":784249163206815744, "id_str":"784249163206815744", "indices":[ 0, 12 ] }, { "screen_name":"sWs8ycsI3krjWrE", "name":"mwni6xx6mwni", "id":859152891076018176, "id_str":"859152891076018176", "indices":[ 13, 29 ] }, { "screen_name":"bnt_alhofuf", "name":"\u0627\u0645 \u0631\u064a\u0646\u0627\u062f \u2665 \u0625\u062a\u062d\u0627\u062f\u064a\u0629 \u2665", "id":2214026312, "id_str":"2214026312", "indices":[ 30, 42 ] }, { "screen_name":"itiihade12", "name":"\u0628\u0637\u0644 \u0643\u0623\u0633 \u0648\u0644\u064a \u0627\u0644\u0639\u0647\u062f", "id":806883635634769921, "id_str":"806883635634769921", "indices":[ 43, 54 ] }, { "screen_name":"A_algrni", "name":"#\u0639\u0628\u062f\u0627\u0644\u0631\u062d\u0645\u0646_\u0627\u0644\u0642\u0631\u0646\u064a", "id":370497227, "id_str":"370497227", "indices":[ 55, 64 ] }, { "screen_name":"berota_q8", "name":"\u0639\u0628\u0640\u064a\u0640\u0631~\u0627\u0644\u0625\u062a\u062d\u0627\u062f", "id":396583124, "id_str":"396583124", "indices":[ 65, 75 ] }, { "screen_name":"fayadhalshamari", "name":"\u0641\u064a\u0627\u0636 \u0627\u0644\u0634\u0645\u0631\u064a", "id":377591886, "id_str":"377591886", "indices":[ 76, 92 ] }, { "screen_name":"OKadour82", "name":"\u0639\u0628\u064a\u062f \u0643\u0639\u062f\u0648\u0631", "id":1358991192, "id_str":"1358991192", "indices":[ 93, 103 ] }, { "screen_name":"K_ibraheem", "name":"\u062e\u0644\u064a\u0644 \u0625\u0628\u0631\u0627\u0647\u064a\u0645", "id":271130300, "id_str":"271130300", "indices":[ 104, 115 ] }, { "screen_name":"Adnan_Jas", "name":"\u0639\u062f\u0646\u0627\u0646 \u062c\u0633\u062a\u0646\u064a\u0647", "id":416120500, "id_str":"416120500", "indices":[ 116, 126 ] }, { "screen_name":"othmanmali", "name":"\u0639\u062b\u0645\u0627\u0646 \u0627\u0628\u0648\u0628\u0643\u0631 \u0645\u0627\u0644\u064a", "id":299213308, "id_str":"299213308", "indices":[ 127, 138 ] }, { "screen_name":"ADEL_MARDI", "name":"\u0639\u0627\u062f\u0644 \u0627\u0644\u0645\u0631\u0636\u064a", "id":508105416, "id_str":"508105416", "indices":[ 139, 150 ] }, { "screen_name":"battalalgoos", "name":"\u0628\u062a\u0627\u0644 \u0627\u0644\u0642\u0648\u0633", "id":251600033, "id_str":"251600033", "indices":[ 151, 164 ] } ], "symbols":[ ] } }, "retweet_count":0, "favorite_count":0, "entities":{ "hashtags":[ ], "urls":[ { "url":"https:\/\/t.co\/9uPkhLBtv4", "expanded_url":"https:\/\/twitter.com\/i\/web\/status\/882685018904068105", "display_url":"twitter.com\/i\/web\/status\/8\u2026", "indices":[ 117, 140 ] } ], "user_mentions":[ { "screen_name":"maljehani10", "name":"\u0645\u062d\u0645\u062f \u0623\u0628\u0648 \u0633\u0627\u0631\u064a", "id":784249163206815744, "id_str":"784249163206815744", "indices":[ 0, 12 ] }, { "screen_name":"sWs8ycsI3krjWrE", "name":"mwni6xx6mwni", "id":859152891076018176, "id_str":"859152891076018176", "indices":[ 13, 29 ] }, { "screen_name":"bnt_alhofuf", "name":"\u0627\u0645 \u0631\u064a\u0646\u0627\u062f \u2665 \u0625\u062a\u062d\u0627\u062f\u064a\u0629 \u2665", "id":2214026312, "id_str":"2214026312", "indices":[ 30, 42 ] }, { "screen_name":"itiihade12", "name":"\u0628\u0637\u0644 \u0643\u0623\u0633 \u0648\u0644\u064a \u0627\u0644\u0639\u0647\u062f", "id":806883635634769921, "id_str":"806883635634769921", "indices":[ 43, 54 ] }, { "screen_name":"A_algrni", "name":"#\u0639\u0628\u062f\u0627\u0644\u0631\u062d\u0645\u0646_\u0627\u0644\u0642\u0631\u0646\u064a", "id":370497227, "id_str":"370497227", "indices":[ 55, 64 ] }, { "screen_name":"berota_q8", "name":"\u0639\u0628\u0640\u064a\u0640\u0631~\u0627\u0644\u0625\u062a\u062d\u0627\u062f", "id":396583124, "id_str":"396583124", "indices":[ 65, 75 ] }, { "screen_name":"fayadhalshamari", "name":"\u0641\u064a\u0627\u0636 \u0627\u0644\u0634\u0645\u0631\u064a", "id":377591886, "id_str":"377591886", "indices":[ 76, 92 ] }, { "screen_name":"OKadour82", "name":"\u0639\u0628\u064a\u062f \u0643\u0639\u062f\u0648\u0631", "id":1358991192, "id_str":"1358991192", "indices":[ 93, 103 ] }, { "screen_name":"K_ibraheem", "name":"\u062e\u0644\u064a\u0644 \u0625\u0628\u0631\u0627\u0647\u064a\u0645", "id":271130300, "id_str":"271130300", "indices":[ 104, 115 ] } ], "symbols":[ ] }, "favorited":false, "retweeted":false, "filter_level":"low", "lang":"ar", "timestamp_ms":"1499283482658" } """; // }}} corebird-1.7.4/tests/tweetmodel.vala000066400000000000000000000237751324604713000175120ustar00rootroot00000000000000 bool is_sorted (Cb.TweetModel tm) { int64 last_id = ((Cb.Tweet)tm.get_item (0)).id; for (int i = 1; i < tm.get_n_items (); i ++) { Cb.Tweet t = (Cb.Tweet)tm.get_item (i); if (t.id > last_id) return false; last_id = t.id; } return true; } int64 get_max_id (Cb.TweetModel tm) { int64 max = -1; for (int i = 0; i < tm.get_n_items (); i ++) { var t = (Cb.Tweet)tm.get_item (i); if (t.id > max) max = t.id; } return max; } void basic_tweet_order () { Cb.TweetModel tm = new Cb.TweetModel (); Cb.Tweet t1 = new Cb.Tweet (); t1.id = 10; Cb.Tweet t2 = new Cb.Tweet (); t2.id = 100; Cb.Tweet t3 = new Cb.Tweet (); t3.id = 1000; tm.add (t3); // 1000 assert (tm.min_id == 1000); assert (tm.max_id == 1000); tm.add (t1); // 10 assert (tm.min_id == 10); assert (tm.max_id == 1000); assert (tm.min_id == 10); assert (tm.max_id == 1000); tm.add (t2); // 100 assert (tm.get_n_items () == 3); assert (((Cb.Tweet)tm.get_item (0)).id == 1000); assert (((Cb.Tweet)tm.get_item (1)).id == 100); assert (((Cb.Tweet)tm.get_item (2)).id == 10); } void tweet_removal () { var tm = new Cb.TweetModel (); //add 10 visible tweets for (int i = 0; i < 10; i ++) { var t = new Cb.Tweet (); t.id = 100 - i; tm.add (t); } // now add 2 invisible tweets { var t = new Cb.Tweet (); t.id = 2; t.set_flag (Cb.TweetState.HIDDEN_FORCE); tm.add (t); assert (tm.get_n_items () == 10); t = new Cb.Tweet (); t.id = 1; t.set_flag (Cb.TweetState.HIDDEN_UNFOLLOWED); tm.add (t); assert (tm.get_n_items () == 10); } // We should have 10 now assert (tm.get_n_items () == 10); // Now remove the last 5 visible ones. // This should remove 2 invisible tweets as well as 5 visible ones // Leaving the model with 5 remaining tweets tm.remove_last_n_visible (5); assert (tm.get_n_items () == 5); } void clear () { var tm = new Cb.TweetModel (); const int n = 10; for (int i = 0; i < n; i++) { var t = new Cb.Tweet (); t.id = 100 + i; tm.add (t); } assert (tm.get_n_items () == n); tm.clear (); assert (tm.get_n_items () == 0); } void clear2 () { var tm1 = new Cb.TweetModel (); var tm2 = new Cb.TweetModel (); var t = new Cb.Tweet (); t.id = 10000; tm1.add (t); var t2 = new Cb.Tweet (); t2.id = 3400; t2.set_flag (Cb.TweetState.HIDDEN_FORCE); tm1.add(t2); tm1.clear (); assert (tm1.get_n_items () == tm2.get_n_items ()); assert (tm1.max_id == tm2.max_id); assert (tm1.min_id == tm2.min_id); assert (tm1.hidden_tweets.length == 0); } void remove_tweet () { var tm = new Cb.TweetModel (); var t1 = new Cb.Tweet (); t1.id = 10; tm.add (t1); var t2 = new Cb.Tweet (); t2.id = 100; tm.add (t2); assert (tm.get_n_items () == 2); tm.remove_tweet (t2); assert (tm.get_n_items () == 1); tm.remove_tweet (t1); assert (tm.get_n_items () == 0); } void remove_hidden () { var tm = new Cb.TweetModel (); var t1 = new Cb.Tweet (); t1.id = 10; t1.set_flag (Cb.TweetState.HIDDEN_UNFOLLOWED); tm.add (t1); assert (tm.get_n_items () == 0); assert (tm.hidden_tweets.length == 1); tm.remove_tweet (t1); assert (tm.get_n_items () == 0); assert (tm.hidden_tweets.length == 0); } void remove_own_retweet () { var tm = new Cb.TweetModel (); var t1 = new Cb.Tweet (); t1.id = 1337; t1.my_retweet = 500; // <-- t1.set_flag (Cb.TweetState.RETWEETED); tm.add (t1); for (int i = 1; i < 51; i ++) { var t = new Cb.Tweet (); t.id = i; tm.add (t); } assert (tm.get_n_items () == 51); //tm.remove (5); //assert (tm.get_n_items () == 50); // should not actually remove any tweet tm.remove_tweet (t1); assert (tm.get_n_items () == 50); } void hide_rt () { var tm = new Cb.TweetModel (); var t1 = new Cb.Tweet (); t1.id = 100; t1.source_tweet = Cb.MiniTweet (); t1.source_tweet.author = Cb.UserIdentity (); t1.source_tweet.author.id = 10; t1.source_tweet.id = 1; t1.retweeted_tweet = Cb.MiniTweet (); t1.retweeted_tweet.id = 100; t1.retweeted_tweet.author = Cb.UserIdentity (); t1.retweeted_tweet.author.id = 100; tm.add (t1); //assert (!t1.is_hidden ()); //tm.toggle_flag_on_user_retweets (10, Cb.TweetState.HIDDEN_FILTERED, true); //assert (t1.is_hidden ()); //assert (tm.get_n_items () == 0); //assert (tm.hidden_tweets.length == 1); //tm.toggle_flag_on_user_retweets (10, Cb.TweetState.HIDDEN_FILTERED, false); //assert (!t1.is_hidden ()); //assert (tm.get_n_items () == 1); //assert (!((Cb.Tweet)tm.get_item (0)).is_hidden ()); } void get_for_id () { var tm = new Cb.TweetModel (); var t1 = new Cb.Tweet (); t1.id = 10; var t2 = new Cb.Tweet (); t2.id = 100; tm.add (t1); tm.add (t2); assert (tm.get_n_items () == 2); assert (((Cb.Tweet)tm.get_item (0)).id == 100); assert (((Cb.Tweet)tm.get_item (1)).id == 10); var result = tm.get_for_id (10); assert (result != null); assert (result.id == 100); } void min_max_id () { var tm = new Cb.TweetModel (); var t = new Cb.Tweet (); t.id = 1337; tm.add (t); var t2 = new Cb.Tweet (); t2.id = 30000; t2.set_flag (Cb.TweetState.HIDDEN_FORCE); tm.add (t2); // Hidden tweets shouldn't affect the min/max id values assert (tm.min_id == 1337); assert (tm.max_id == 1337); } void sorting () { var tm = new Cb.TweetModel (); for (int i = 0; i < 100; i ++) { var t = new Cb.Tweet (); t.id = GLib.Random.next_int (); tm.add (t); } assert (is_sorted (tm)); } void min_max_remove () { var tm = new Cb.TweetModel (); var t1 = new Cb.Tweet (); t1.id = 10; tm.add (t1); var t2 = new Cb.Tweet (); t2.id = 20; tm.add (t2); var t3 = new Cb.Tweet (); t3.id = 2; tm.add (t3); assert (tm.max_id == 20); assert (tm.min_id == 2); tm.remove_tweet (t1); // Should still be the same assert (tm.max_id == 20); assert (tm.min_id == 2); var t = new Cb.Tweet (); t.id = 10; tm.add (t); // And again... assert (tm.max_id == 20); assert (tm.min_id == 2); // Now it gets interesting tm.remove_tweet (t2); assert (tm.min_id == 2); assert (tm.max_id == 10); assert (tm.max_id == get_max_id (tm)); tm.remove_tweet (t3); assert (tm.min_id == 10); assert (tm.max_id == 10); tm.remove_tweet (t); assert (tm.get_n_items () == 0); assert (tm.min_id == int64.MAX); assert (tm.max_id == int64.MIN); } void tweet_count () { var tm = new Cb.TweetModel (); var t1 = new Cb.Tweet (); t1.id = 10; t1.source_tweet = Cb.MiniTweet (); t1.source_tweet.author = Cb.UserIdentity (); t1.source_tweet.author.id = 11; t1.retweeted_tweet = Cb.MiniTweet (); t1.retweeted_tweet.id = 100; t1.retweeted_tweet.author = Cb.UserIdentity (); t1.retweeted_tweet.author.id = 111; tm.add (t1); assert (tm.get_n_items () == 1); assert (tm.max_id == t1.id); assert (tm.min_id == t1.id); tm.toggle_flag_on_user_retweets (11, Cb.TweetState.HIDDEN_FILTERED, true); assert (tm.get_n_items () == 0); assert (tm.hidden_tweets.length == 0); t1.unset_flag (Cb.TweetState.HIDDEN_FILTERED); var t2 = new Cb.Tweet (); t2.id = 20; t2.source_tweet = Cb.MiniTweet (); t2.source_tweet.author = Cb.UserIdentity (); t2.source_tweet.author.id = 11; tm.add (t2); tm.add (t1); assert (tm.get_n_items () == 2); assert (tm.max_id == t2.id); assert (tm.min_id == t1.id); tm.toggle_flag_on_user_tweets (11, Cb.TweetState.HIDDEN_FILTERED, true); assert (tm.get_n_items () == 1); tm.toggle_flag_on_user_retweets (11, Cb.TweetState.HIDDEN_FILTERED, true); assert (tm.get_n_items () == 0); assert (tm.hidden_tweets.length == 0); } void hidden_remove_last_n_visible () { var tm = new Cb.TweetModel (); for (int i = 0; i < 20; i ++) { var t = new Cb.Tweet(); t.id = 10 + (i * 2); // Only even ids tm.add (t); } var t1 = new Cb.Tweet (); t1.id = 15; t1.set_flag (Cb.TweetState.HIDDEN_UNFOLLOWED); tm.add (t1); var t2 = new Cb.Tweet (); t2.id = 21; t2.set_flag (Cb.TweetState.HIDDEN_AUTHOR_MUTED); tm.add (t2); assert (tm.get_n_items () == 20); assert (tm.hidden_tweets.length == 2); tm.remove_last_n_visible (20); assert (tm.get_n_items () == 0); } void empty_hidden_tweets () { int n = 100; var tm = new Cb.TweetModel (); for (int i = 1; i <= n; i += 2) { var t1 = new Cb.Tweet (); t1.id = i + 1; tm.add (t1); var t2 = new Cb.Tweet (); t2.id = i; t2.set_flag (Cb.TweetState.HIDDEN_RT_BY_FOLLOWEE); tm.add (t2); tm.remove_tweet (t1); } message ("%u", tm.get_n_items ()); assert (tm.get_n_items () == 0); message ("%u", tm.hidden_tweets.length); assert (tm.hidden_tweets.length == 0); } void same_id () { var tm = new Cb.TweetModel (); var t = new Cb.Tweet (); t.id = 1337; tm.add (t); assert (tm.get_n_items () == 1); assert (tm.min_id == 1337); assert (tm.max_id == 1337); tm.add (t); /* No change */ assert (tm.get_n_items () == 1); assert (tm.min_id == 1337); assert (tm.max_id == 1337); } int main (string[] args) { GLib.Test.init (ref args); GLib.Test.add_func ("/tweetmodel/basic-tweet-order", basic_tweet_order); GLib.Test.add_func ("/tweetmodel/tweet-removal", tweet_removal); GLib.Test.add_func ("/tweetmodel/clear", clear); GLib.Test.add_func ("/tweetmodel/clear2", clear2); GLib.Test.add_func ("/tweetmodel/remove", remove_tweet); GLib.Test.add_func ("/tweetmodel/remove-hidden", remove_hidden); GLib.Test.add_func ("/tweetmodel/remove-own-retweet", remove_own_retweet); GLib.Test.add_func ("/tweetmodel/hide-rt", hide_rt); GLib.Test.add_func ("/tweetmodel/get-for-id", get_for_id); GLib.Test.add_func ("/tweetmodel/min-max-id", min_max_id); GLib.Test.add_func ("/tweetmodel/sorting", sorting); GLib.Test.add_func ("/tweetmodel/min-max-remove", min_max_remove); GLib.Test.add_func ("/tweetmodel/tweet-count", tweet_count); GLib.Test.add_func ("/tweetmodel/empty-hidden-tweets", empty_hidden_tweets); GLib.Test.add_func ("/tweetmodel/hidden-remove-last-n-visible", hidden_remove_last_n_visible); GLib.Test.add_func ("/tweetmodel/same-id", same_id); return GLib.Test.run (); } corebird-1.7.4/tests/tweetparsing.vala000066400000000000000000002314511324604713000200450ustar00rootroot00000000000000 // {{{ const string TD1 = """ { "created_at" : "Mon May 05 06:48:32 +0000 2014", "id" : 463208606784311296, "id_str" : "463208606784311296", "text" : "RT @BlackForestTeam: DIESELSTÖRMERS Kickstarter is live! - Go and check it out right now!... http://t.co/ZVmefc0w5e", "source" : "web", "truncated" : false, "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 62574927, "id_str" : "62574927", "name" : "Frozenbyte", "screen_name" : "Frozenbyte", "location" : "Helsinki, Finland", "description" : "We're an independent game developer. Follow us on Twitter to get the latest news on our games! For support issues please get in touch via email.", "url" : "http://t.co/NlgW9k9ZXj", "entities" : { "url" : { "urls" : [ { "url" : "http://t.co/NlgW9k9ZXj", "expanded_url" : "http://www.frozenbyte.com", "display_url" : "frozenbyte.com", "indices" : [ 0, 22 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 5682, "friends_count" : 137, "listed_count" : 242, "created_at" : "Mon Aug 03 17:52:07 +0000 2009", "favourites_count" : 233, "utc_offset" : 10800, "time_zone" : "Helsinki", "geo_enabled" : false, "verified" : false, "statuses_count" : 1042, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "000000", "profile_background_image_url" : "http://pbs.twimg.com/profile_background_images/378800000117103508/abe5a14a1f3b0b78e9038e73bf6b812d.jpeg", "profile_background_image_url_https" : "https://pbs.twimg.com/profile_background_images/378800000117103508/abe5a14a1f3b0b78e9038e73bf6b812d.jpeg", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/1130292729/fb_newlogo_black480_normal.png", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/1130292729/fb_newlogo_black480_normal.png", "profile_link_color" : "CBA051", "profile_sidebar_border_color" : "000000", "profile_sidebar_fill_color" : "D4802D", "profile_text_color" : "4A2500", "profile_use_background_image" : true, "default_profile" : false, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "retweeted_status" : { "created_at" : "Tue Apr 29 11:00:25 +0000 2014", "id" : 461097667775725569, "id_str" : "461097667775725569", "text" : "DIESELSTÖRMERS Kickstarter is live! - Go and check it out right now!... http://t.co/ZVmefc0w5e", "source" : "Tumblr", "truncated" : false, "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 726763934, "id_str" : "726763934", "name" : "Black Forest Games", "screen_name" : "BlackForestTeam", "location" : "Offenburg", "description" : "South-german team that brought you Giana Sisters: Twisted Dreams", "url" : "http://t.co/BXuCnqlX50", "entities" : { "url" : { "urls" : [ { "url" : "http://t.co/BXuCnqlX50", "expanded_url" : "http://gianasisterstwisteddreams.com", "display_url" : "gianasisterstwisteddreams.com", "indices" : [ 0, 22 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 1145, "friends_count" : 308, "listed_count" : 35, "created_at" : "Mon Jul 30 20:11:50 +0000 2012", "favourites_count" : 116, "utc_offset" : 7200, "time_zone" : "Amsterdam", "geo_enabled" : false, "verified" : false, "statuses_count" : 1475, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/3694489354/ed399e59260bf71b10235dcd7eb56fe5_normal.png", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/3694489354/ed399e59260bf71b10235dcd7eb56fe5_normal.png", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/726763934/1369154494", "profile_link_color" : "0084B4", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : true, "default_profile" : true, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "retweet_count" : 6, "favorite_count" : 2, "entities" : { "hashtags" : [ ], "symbols" : [ ], "urls" : [ { "url" : "http://t.co/ZVmefc0w5e", "expanded_url" : "http://tmblr.co/ZTqD4s1ERcDZg", "display_url" : "tmblr.co/ZTqD4s1ERcDZg", "indices" : [ 72, 94 ] } ], "user_mentions" : [ ] }, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "lang" : "en" }, "retweet_count" : 6, "favorite_count" : 0, "entities" : { "hashtags" : [ ], "symbols" : [ ], "urls" : [ { "url" : "http://t.co/ZVmefc0w5e", "expanded_url" : "http://tmblr.co/ZTqD4s1ERcDZg", "display_url" : "tmblr.co/ZTqD4s1ERcDZg", "indices" : [ 93, 115 ] } ], "user_mentions" : [ { "screen_name" : "BlackForestTeam", "name" : "Black Forest Games", "id" : 726763934, "id_str" : "726763934", "indices" : [ 3, 19 ] } ] }, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "lang" : "en" }"""; const string TD2 = """ { "created_at": "Tue Apr 29 00:50:10 +0000 2014", "id": 460944092554227713, "id_str": "460944092554227713", "text": "Combined. http:\/\/t.co\/fFJqqT1A4j", "source": "\u003ca href=\"http:\/\/twitter.com\/geekculturejam\" rel=\"nofollow\"\u003eCultureJam\u003c\/a\u003e", "truncated": false, "in_reply_to_status_id": null, "in_reply_to_status_id_str": null, "in_reply_to_user_id": null, "in_reply_to_user_id_str": null, "in_reply_to_screen_name": null, "user": { "id": 657693, "id_str": "657693", "screen_name": "FOOBAR", "name": "Foo Bar", "profile_image_url" : "http://pbs.twimg.com/profile_images/3694489354/ed399e59260bf71b10235dcd7eb56fe5_normal.png", "verified" : false }, "geo": null, "coordinates": null, "place": null, "contributors": null, "retweet_count": 0, "favorite_count": 0, "entities": { "hashtags": [], "symbols": [], "urls": [], "user_mentions": [], "media": [] }, "extended_entities": { "media": [ { "id": 460938773744717825, "id_str": "460938773744717825", "indices": [ 10, 32 ], "media_url": "http:\/\/pbs.twimg.com\/media\/BmWVX2BCEAEx4MK.jpg", "media_url_https": "https:\/\/pbs.twimg.com\/media\/BmWVX2BCEAEx4MK.jpg", "url": "http:\/\/t.co\/fFJqqT1A4j", "display_url": "pic.twitter.com\/fFJqqT1A4j", "expanded_url": "http:\/\/twitter.com\/froginthevalley\/status\/460944092554227713\/photo\/1", "type": "photo", "sizes": { "medium": { "w": 599, "h": 397, "resize": "fit" }, "thumb": { "w": 150, "h": 150, "resize": "crop" }, "small": { "w": 340, "h": 225, "resize": "fit" }, "large": { "w": 1023, "h": 678, "resize": "fit" } } }, { "id": 460938635315916, "indices": [ 10, 32 ], "media_url": "http:\/\/pbs.twimg.com\/media\/BmWVPyVCMAAeAwI.jpg", "media_url_https": "https:\/\/pbs.twimg.com\/media\/BmWVPyVCMAAeAwI.jpg", "url": "http:\/\/t.co\/fFJqqT1A4j", "display_url": "pic.twitter.com\/fFJqqT1A4j", "expanded_url": "http:\/\/twitter.com\/froginthevalley\/status\/460944092554227713\/photo\/1", "type": "photo", "sizes": { "medium": { "w": 600, "h": 600, "resize": "fit" }, "thumb": { "w": 150, "h": 150, "resize": "crop" }, "large": { "w": 1024, "h": 1024, "resize": "fit" }, "small": { "w": 340, "h": 340, "resize": "fit" } } } ] }, "favorited": false, "retweeted": false, "possibly_sensitive": false, "lang": "en" } """; const string TD3 = """ { "created_at" : "Thu Jun 12 19:34:16 +0000 2014", "id" : 477172048427765760, "id_str" : "477172048427765760", "text" : "http://t.co/ZGX7b9YGiU http://t.co/6hfxg0TPyt", "source" : "Twitter Web Client", "truncated" : false, "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 993713617, "id_str" : "993713617", "name" : "Core Bird", "screen_name" : "corebirdgtk", "location" : "", "description" : "", "url" : null, "entities" : { "description" : { "urls" : [ ] } }, "protected" : true, "followers_count" : 1, "friends_count" : 6, "listed_count" : 0, "created_at" : "Thu Dec 06 19:47:16 +0000 2012", "favourites_count" : 12, "utc_offset" : 7200, "time_zone" : "Amsterdam", "geo_enabled" : false, "verified" : false, "statuses_count" : 537, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://abs.twimg.com/sticky/default_profile_images/default_profile_1_normal.png", "profile_image_url_https" : "https://abs.twimg.com/sticky/default_profile_images/default_profile_1_normal.png", "profile_link_color" : "0084B4", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : true, "default_profile" : true, "default_profile_image" : true, "following" : true, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "retweet_count" : 0, "favorite_count" : 0, "entities" : { "hashtags" : [ ], "symbols" : [ ], "urls" : [ { "url" : "http://t.co/ZGX7b9YGiU", "expanded_url" : "http://i.imgur.com/kgrtCf0.png", "display_url" : "i.imgur.com/kgrtCf0.png", "indices" : [ 0, 22 ] }, { "url" : "http://t.co/6hfxg0TPyt", "expanded_url" : "http://i.imgur.com/xqmzPar.gif", "display_url" : "i.imgur.com/xqmzPar.gif", "indices" : [ 23, 45 ] } ], "user_mentions" : [ ] }, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "lang" : "und" } """; const string TD4 = """ { "created_at" : "Thu Jun 12 19:34:16 +0000 2014", "id" : 477172048465760, "id_str" : "477172048427765760", "text" : "http://t.co/ZGX7b9YGiU http://t.co/6hfxg0TPyt", "source" : "Twitter Web Client", "truncated" : false, "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 993713617, "id_str" : "993713617", "name" : "Core Bird", "screen_name" : "corebirdgtk", "location" : "", "description" : "", "url" : null, "entities" : { "description" : { "urls" : [ ] } }, "protected" : true, "followers_count" : 1, "friends_count" : 6, "listed_count" : 0, "created_at" : "Thu Dec 06 19:47:16 +0000 2012", "favourites_count" : 12, "utc_offset" : 7200, "time_zone" : "Amsterdam", "geo_enabled" : false, "verified" : false, "statuses_count" : 537, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://abs.twimg.com/sticky/default_profile_images/default_profile_1_normal.png", "profile_image_url_https" : "https://abs.twimg.com/sticky/default_profile_images/default_profile_1_normal.png", "profile_link_color" : "0084B4", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : true, "default_profile" : true, "default_profile_image" : true, "following" : true, "follow_request_sent" : false, "notifications" : false }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "retweet_count" : 0, "favorite_count" : 0, "entities" : { "hashtags" : [ ], "symbols" : [ ], "urls" : [ { "url" : "http://t.co/ZGX7b9YGiU", "expanded_url" : "http://i.imgur.com/kgrtCf0.png", "display_url" : "i.imgur.com/kgrtCf0.png", "indices" : [ 0, 22 ] }, { "url" : "http://t.co/6hfxg0TPyt", "expanded_url" : "http://i.imgur.com/xqmzPar.gif", "display_url" : "i.imgur.com/xqmzPar.gif", "indices" : [ 23, 45 ] } ], "user_mentions" : [ ] }, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "lang" : "und", "extended_entities": { "media": [ { "id": 460938773744717, "media_url": "http:\/\/pbs.twimg.com\/media\/BmWVX2BCEAEx4MK.jpg", "media_url_https": "https:\/\/pbs.twimg.com\/media\/BmWVX2BCEAEx4MK.jpg", "url": "http:\/\/t.co\/fFJqqT1A4j", "display_url": "pic.twitter.com\/fFJqqT1A4j", "expanded_url": "http:\/\/twitter.com\/froginthevalley\/status\/460944092554227713\/photo\/1", "type": "photo" }, { "id": 460938615916800, "media_url": "http:\/\/pbs.twimg.com\/media\/BmWVPyVCMAAeAwI.jpg", "media_url_https": "https:\/\/pbs.twimg.com\/media\/BmWVPyVCMAAeAwI.jpg", "url": "http:\/\/t.co\/fFJqqT1A4j", "display_url": "pic.twitter.com\/fFJqqT1A4j", "expanded_url": "http:\/\/twitter.com\/froginthevalley\/status\/460944092554227713\/photo\/1", "type": "photo" } ] } } """; // """ const string TD5 = """ { "created_at":"Fri Sep 30 16:45:16 +0000 2016", "id":781897715399532544, "id_str":"781897715399532544", "text":"RT @oscaron: Okay folks! I\|ve decided to donate all profits from the #400LBHACKER shirt to @Hak4Kidz and @EFF \n\nPlease RT!\u2026 ", "source":"Corebird\u003c\/a\u003e", "truncated":false, "in_reply_to_status_id":null, "in_reply_to_status_id_str":null, "in_reply_to_user_id":null, "in_reply_to_user_id_str":null, "in_reply_to_screen_name":null, "user":{ "id":993713617, "id_str":"993713617", "name":"Core & Bird", "screen_name":"corebirdgtk", "location":null, "url":null, "description":null, "protected":true, "verified":false, "followers_count":3, "friends_count":4, "listed_count":0, "favourites_count":9, "statuses_count":845, "created_at":"Thu Dec 06 19:47:16 +0000 2012", "utc_offset":7200, "time_zone":"Amsterdam", "geo_enabled":false, "lang":"en", "contributors_enabled":false, "is_translator":false, "profile_background_color":"C0DEED", "profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png", "profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png", "profile_background_tile":false, "profile_link_color":"0084B4", "profile_sidebar_border_color":"C0DEED", "profile_sidebar_fill_color":"DDEEF6", "profile_text_color":"333333", "profile_use_background_image":true, "profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/655270693341417472\/h6BbZKJy_normal.png", "profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/655270693341417472\/h6BbZKJy_normal.png", "profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/993713617\/1413106147", "default_profile":true, "default_profile_image":false, "following":null, "follow_request_sent":null, "notifications":null }, "geo":null, "coordinates":null, "place":null, "contributors":null, "retweeted_status":{ "created_at":"Wed Sep 28 14:23:49 +0000 2016", "id":781137340962004992, "id_str":"781137340962004992", "text":"Okay folks! I\|ve decided to donate all profits from the #400LBHACKER shirt to @Hak4Kidz and @EFF \n\nPlease RT!\u2026 https:\/\/t.co\/V2vE2q8ol0", "display_text_range":[ 0, 140 ], "source":"\u003ca href=\"https:\/\/about.twitter.com\/products\/tweetdeck\" rel=\"nofollow\"\u003eTweetDeck\u003c\/a\u003e", "truncated":true, "in_reply_to_status_id":null, "in_reply_to_status_id_str":null, "in_reply_to_user_id":null, "in_reply_to_user_id_str":null, "in_reply_to_screen_name":null, "user":{ "id":16156052, "id_str":"16156052", "name":"Oscaron \u2615", "screen_name":"oscaron", "location":"Galactic Sector ZZ9 Plural Z A", "url":"http:\/\/goo.gl\/skGJRQ", "description":"Tech Alchemist, Infosec Enthusiast. NOT a \|Cybersecurity Expert\|. My views = My views. Trigger warnings: ALL. All stories exaggerated.", "protected":false, "verified":false, "followers_count":1391, "friends_count":762, "listed_count":105, "favourites_count":18359, "statuses_count":49369, "created_at":"Sat Sep 06 12:36:08 +0000 2008", "utc_offset":-14400, "time_zone":"Eastern Time (US & Canada)", "geo_enabled":false, "lang":"en", "contributors_enabled":false, "is_translator":false, "profile_background_color":"131516", "profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme14\/bg.gif", "profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme14\/bg.gif", "profile_background_tile":true, "profile_link_color":"5E2F00", "profile_sidebar_border_color":"FFFFFF", "profile_sidebar_fill_color":"EFEFEF", "profile_text_color":"333333", "profile_use_background_image":true, "profile_image_url":"http:\/\/pbs.twimg.com\/profile_images\/773567191832821761\/2ndP1v95_normal.jpg", "profile_image_url_https":"https:\/\/pbs.twimg.com\/profile_images\/773567191832821761\/2ndP1v95_normal.jpg", "profile_banner_url":"https:\/\/pbs.twimg.com\/profile_banners\/16156052\/1404228988", "default_profile":false, "default_profile_image":false, "following":null, "follow_request_sent":null, "notifications":null }, "geo":null, "coordinates":null, "place":null, "contributors":null, "is_quote_status":false, "extended_tweet":{ "full_text":"Okay folks! I\|ve decided to donate all profits from the #400LBHACKER shirt to @Hak4Kidz and @EFF \n\nPlease RT!\n\nhttps:\/\/t.co\/AP7qFSdYp0 https:\/\/t.co\/WtprxUumEE", "display_text_range":[ 0, 134 ], "entities":{ "hashtags":[ { "text":"400LBHACKER", "indices":[ 56, 68 ] } ], "urls":[ { "url":"https:\/\/t.co\/AP7qFSdYp0", "expanded_url":"https:\/\/teespring.com\/400lbhacker#pid=2&cid=2397&sid=front", "display_url":"teespring.com\/400lbhacker#pi\u2026", "indices":[ 111, 134 ] } ], "user_mentions":[ { "screen_name":"Hak4Kidz", "name":"Hak4Kidz", "id":2262322964, "id_str":"2262322964", "indices":[ 78, 87 ] }, { "screen_name":"EFF", "name":"EFF", "id":4816, "id_str":"4816", "indices":[ 92, 96 ] } ], "symbols":[ ], "media":[ { "id":781137070484054016, "id_str":"781137070484054016", "indices":[ 135, 158 ], "media_url":"http:\/\/pbs.twimg.com\/media\/CtcoBucXYAAs4rs.jpg", "media_url_https":"https:\/\/pbs.twimg.com\/media\/CtcoBucXYAAs4rs.jpg", "url":"https:\/\/t.co\/WtprxUumEE", "display_url":"pic.twitter.com\/WtprxUumEE", "expanded_url":"https:\/\/twitter.com\/oscaron\/status\/781137340962004992\/photo\/1", "type":"photo", "sizes":{ "medium":{ "w":480, "h":571, "resize":"fit" }, "large":{ "w":480, "h":571, "resize":"fit" }, "thumb":{ "w":150, "h":150, "resize":"crop" }, "small":{ "w":480, "h":571, "resize":"fit" } } }, { "id":781137082697805824, "id_str":"781137082697805824", "indices":[ 135, 158 ], "media_url":"http:\/\/pbs.twimg.com\/media\/CtcoCb8WcAATK4y.jpg", "media_url_https":"https:\/\/pbs.twimg.com\/media\/CtcoCb8WcAATK4y.jpg", "url":"https:\/\/t.co\/WtprxUumEE", "display_url":"pic.twitter.com\/WtprxUumEE", "expanded_url":"https:\/\/twitter.com\/oscaron\/status\/781137340962004992\/photo\/1", "type":"photo", "sizes":{ "medium":{ "w":480, "h":571, "resize":"fit" }, "large":{ "w":480, "h":571, "resize":"fit" }, "thumb":{ "w":150, "h":150, "resize":"crop" }, "small":{ "w":480, "h":571, "resize":"fit" } } } ] }, "extended_entities":{ "media":[ { "id":781137070484054016, "id_str":"781137070484054016", "indices":[ 135, 158 ], "media_url":"http:\/\/pbs.twimg.com\/media\/CtcoBucXYAAs4rs.jpg", "media_url_https":"https:\/\/pbs.twimg.com\/media\/CtcoBucXYAAs4rs.jpg", "url":"https:\/\/t.co\/WtprxUumEE", "display_url":"pic.twitter.com\/WtprxUumEE", "expanded_url":"https:\/\/twitter.com\/oscaron\/status\/781137340962004992\/photo\/1", "type":"photo", "sizes":{ "medium":{ "w":480, "h":571, "resize":"fit" }, "large":{ "w":480, "h":571, "resize":"fit" }, "thumb":{ "w":150, "h":150, "resize":"crop" }, "small":{ "w":480, "h":571, "resize":"fit" } } }, { "id":781137082697805824, "id_str":"781137082697805824", "indices":[ 135, 158 ], "media_url":"http:\/\/pbs.twimg.com\/media\/CtcoCb8WcAATK4y.jpg", "media_url_https":"https:\/\/pbs.twimg.com\/media\/CtcoCb8WcAATK4y.jpg", "url":"https:\/\/t.co\/WtprxUumEE", "display_url":"pic.twitter.com\/WtprxUumEE", "expanded_url":"https:\/\/twitter.com\/oscaron\/status\/781137340962004992\/photo\/1", "type":"photo", "sizes":{ "medium":{ "w":480, "h":571, "resize":"fit" }, "large":{ "w":480, "h":571, "resize":"fit" }, "thumb":{ "w":150, "h":150, "resize":"crop" }, "small":{ "w":480, "h":571, "resize":"fit" } } } ] } }, "retweet_count":355, "favorite_count":308, "entities":{ "hashtags":[ { "text":"400LBHACKER", "indices":[ 56, 68 ] } ], "urls":[ { "url":"https:\/\/t.co\/V2vE2q8ol0", "expanded_url":"https:\/\/twitter.com\/i\/web\/status\/781137340962004992", "display_url":"twitter.com\/i\/web\/status\/7\u2026", "indices":[ 111, 134 ] } ], "user_mentions":[ { "screen_name":"Hak4Kidz", "name":"Hak4Kidz", "id":2262322964, "id_str":"2262322964", "indices":[ 78, 87 ] }, { "screen_name":"EFF", "name":"EFF", "id":4816, "id_str":"4816", "indices":[ 92, 96 ] } ], "symbols":[ ] }, "favorited":false, "retweeted":false, "possibly_sensitive":false, "filter_level":"low", "lang":"en" }, "is_quote_status":false, "retweet_count":0, "favorite_count":0, "entities":{ "hashtags":[ { "text":"400LBHACKER", "indices":[ 69, 81 ] } ], "urls":[ { "url":"", "expanded_url":null, "indices":[ 124, 124 ] } ], "user_mentions":[ { "screen_name":"oscaron", "name":"Oscaron \u2615", "id":16156052, "id_str":"16156052", "indices":[ 3, 11 ] }, { "screen_name":"Hak4Kidz", "name":"Hak4Kidz", "id":2262322964, "id_str":"2262322964", "indices":[ 91, 100 ] }, { "screen_name":"EFF", "name":"EFF", "id":4816, "id_str":"4816", "indices":[ 105, 109 ] } ], "symbols":[ ] }, "favorited":false, "retweeted":false, "filter_level":"low", "lang":"en", "timestamp_ms":"1475253916965" } """; const string TD6 = """ { "created_at" : "Sat Apr 01 16:28:13 +0000 2017", "id" : 848210402995556353, "id_str" : "848210402995556353", "full_text" : "RT @wurst_hw: SOME TWEET https://t.co/ySiXE2r1dV https://t.co/k8Wlmh67nf", "truncated" : false, "display_text_range" : [ 0, 72 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "wurst_hw", "name" : "Hans Wurst", "id" : 1469297089, "id_str" : "1469297089", "indices" : [ 3, 12 ] } ], "urls" : [ { "url" : "https://t.co/ySiXE2r1dV", "expanded_url" : "https://twitter.com/baedert/status/847671175081676801", "display_url" : "twitter.com/baedert/status…", "indices" : [ 25, 48 ] } ], "media" : [ { "id" : 848210277820747776, "id_str" : "848210277820747776", "indices" : [ 49, 72 ], "media_url" : "http://pbs.twimg.com/media/C8VywPBXUAAgOkn.jpg", "media_url_https" : "https://pbs.twimg.com/media/C8VywPBXUAAgOkn.jpg", "url" : "https://t.co/k8Wlmh67nf", "display_url" : "pic.twitter.com/k8Wlmh67nf", "expanded_url" : "https://twitter.com/wurst_hw/status/848210293465468928/photo/1", "type" : "photo", "sizes" : { "medium" : { "w" : 1200, "h" : 675, "resize" : "fit" }, "large" : { "w" : 1920, "h" : 1080, "resize" : "fit" }, "small" : { "w" : 680, "h" : 383, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" } }, "source_status_id" : 848210293465468928, "source_status_id_str" : "848210293465468928", "source_user_id" : 1469297089, "source_user_id_str" : "1469297089" } ] }, "extended_entities" : { "media" : [ { "id" : 848210277820747776, "id_str" : "848210277820747776", "indices" : [ 49, 72 ], "media_url" : "http://pbs.twimg.com/media/C8VywPBXUAAgOkn.jpg", "media_url_https" : "https://pbs.twimg.com/media/C8VywPBXUAAgOkn.jpg", "url" : "https://t.co/k8Wlmh67nf", "display_url" : "pic.twitter.com/k8Wlmh67nf", "expanded_url" : "https://twitter.com/wurst_hw/status/848210293465468928/photo/1", "type" : "photo", "sizes" : { "medium" : { "w" : 1200, "h" : 675, "resize" : "fit" }, "large" : { "w" : 1920, "h" : 1080, "resize" : "fit" }, "small" : { "w" : 680, "h" : 383, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" } }, "source_status_id" : 848210293465468928, "source_status_id_str" : "848210293465468928", "source_user_id" : 1469297089, "source_user_id_str" : "1469297089" } ] }, "source" : "Twitter Web Client", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 993713617, "id_str" : "993713617", "name" : "Z??!@*(&*²³¤²³¤", "screen_name" : "corebirdgtk", "location" : "", "description" : "<asdf", "url" : null, "entities" : { "description" : { "urls" : [ ] } }, "protected" : true, "followers_count" : 3, "friends_count" : 4, "listed_count" : 0, "created_at" : "Thu Dec 06 19:47:16 +0000 2012", "favourites_count" : 9, "utc_offset" : 7200, "time_zone" : "Amsterdam", "geo_enabled" : false, "verified" : false, "statuses_count" : 865, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : true, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/655270693341417472/h6BbZKJy_normal.png", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/655270693341417472/h6BbZKJy_normal.png", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/993713617/1413106147", "profile_link_color" : "1DA1F2", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : true, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "retweeted_status" : { "created_at" : "Sat Apr 01 16:27:47 +0000 2017", "id" : 848210293465468928, "id_str" : "848210293465468928", "full_text" : "SOME TWEET https://t.co/ySiXE2r1dV https://t.co/k8Wlmh67nf", "truncated" : false, "display_text_range" : [ 0, 34 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ ], "urls" : [ { "url" : "https://t.co/ySiXE2r1dV", "expanded_url" : "https://twitter.com/baedert/status/847671175081676801", "display_url" : "twitter.com/baedert/status…", "indices" : [ 11, 34 ] } ], "media" : [ { "id" : 848210277820747776, "id_str" : "848210277820747776", "indices" : [ 35, 58 ], "media_url" : "http://pbs.twimg.com/media/C8VywPBXUAAgOkn.jpg", "media_url_https" : "https://pbs.twimg.com/media/C8VywPBXUAAgOkn.jpg", "url" : "https://t.co/k8Wlmh67nf", "display_url" : "pic.twitter.com/k8Wlmh67nf", "expanded_url" : "https://twitter.com/wurst_hw/status/848210293465468928/photo/1", "type" : "photo", "sizes" : { "medium" : { "w" : 1200, "h" : 675, "resize" : "fit" }, "large" : { "w" : 1920, "h" : 1080, "resize" : "fit" }, "small" : { "w" : 680, "h" : 383, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" } } } ] }, "extended_entities" : { "media" : [ { "id" : 848210277820747776, "id_str" : "848210277820747776", "indices" : [ 35, 58 ], "media_url" : "http://pbs.twimg.com/media/C8VywPBXUAAgOkn.jpg", "media_url_https" : "https://pbs.twimg.com/media/C8VywPBXUAAgOkn.jpg", "url" : "https://t.co/k8Wlmh67nf", "display_url" : "pic.twitter.com/k8Wlmh67nf", "expanded_url" : "https://twitter.com/wurst_hw/status/848210293465468928/photo/1", "type" : "photo", "sizes" : { "medium" : { "w" : 1200, "h" : 675, "resize" : "fit" }, "large" : { "w" : 1920, "h" : 1080, "resize" : "fit" }, "small" : { "w" : 680, "h" : 383, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" } } } ] }, "source" : "Twitter Web Client", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 1469297089, "id_str" : "1469297089", "name" : "Hans Wurst", "screen_name" : "wurst_hw", "location" : "", "description" : "", "url" : null, "entities" : { "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 1, "friends_count" : 2, "listed_count" : 0, "created_at" : "Thu May 30 08:47:22 +0000 2013", "favourites_count" : 0, "utc_offset" : null, "time_zone" : null, "geo_enabled" : false, "verified" : false, "statuses_count" : 31, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png", "profile_image_url_https" : "https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png", "profile_link_color" : "1DA1F2", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : true, "default_profile_image" : true, "following" : false, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : true, "quoted_status_id" : 847671175081676801, "quoted_status_id_str" : "847671175081676801", "quoted_status" : { "created_at" : "Fri Mar 31 04:45:31 +0000 2017", "id" : 847671175081676801, "id_str" : "847671175081676801", "full_text" : "Haha yeah. The Reply Madness of 2017. Very funny. It'll ruin my weekend but let's laugh first.", "truncated" : false, "display_text_range" : [ 0, 94 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ ], "urls" : [ ] }, "source" : "Twidere for Android #5", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 118055879, "id_str" : "118055879", "name" : "Schupp & Wupp", "screen_name" : "baedert", "location" : "", "description" : "Corebird developer by night, silently judging people on the train by day.", "url" : "https://t.co/yGvX7Nf6i3", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/yGvX7Nf6i3", "expanded_url" : "http://corebird.baedert.org", "display_url" : "corebird.baedert.org", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 193, "friends_count" : 73, "listed_count" : 4, "created_at" : "Sat Feb 27 13:13:34 +0000 2010", "favourites_count" : 152, "utc_offset" : 7200, "time_zone" : "Bern", "geo_enabled" : false, "verified" : false, "statuses_count" : 2079, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "C6E2EE", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme2/bg.gif", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme2/bg.gif", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/810507927941476352/cSydClet_normal.jpg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/810507927941476352/cSydClet_normal.jpg", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/118055879/1488791887", "profile_link_color" : "1F98C7", "profile_sidebar_border_color" : "C6E2EE", "profile_sidebar_fill_color" : "DAECF4", "profile_text_color" : "663B12", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 1, "favorite_count" : 2, "favorited" : false, "retweeted" : false, "lang" : "en" }, "retweet_count" : 1, "favorite_count" : 0, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "lang" : "en" }, "is_quote_status" : true, "quoted_status_id" : 847671175081676801, "quoted_status_id_str" : "847671175081676801", "retweet_count" : 1, "favorite_count" : 0, "favorited" : false, "retweeted" : false, "possibly_sensitive" : true, "lang" : "en" } """; const string REPLY_TWEET_DATA = """ { "created_at" : "Mon Apr 17 15:16:18 +0000 2017", "id" : 853990508326252550, "id_str" : "853990508326252550", "full_text" : "@jjdesmond @_UBRAS_ @franalsworth @4Apes @katy4apes @theAliceRoberts @JaneGoodallUK @Jane_Goodall @JaneGoodallInst And here's the link for tickets again ... https://t.co/a9lOVMouNK", "truncated" : false, "display_text_range" : [ 115, 180 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "jjdesmond", "name" : "Jimmy Jenny Desmond", "id" : 21278482, "id_str" : "21278482", "indices" : [ 0, 10 ] }, { "screen_name" : "_UBRAS_", "name" : "Roots and Shoots UOB", "id" : 803329927974096896, "id_str" : "803329927974096896", "indices" : [ 11, 19 ] }, { "screen_name" : "franalsworth", "name" : "Fran", "id" : 776983919287754752, "id_str" : "776983919287754752", "indices" : [ 20, 33 ] }, { "screen_name" : "4Apes", "name" : "Ian Redmond", "id" : 155889035, "id_str" : "155889035", "indices" : [ 34, 40 ] }, { "screen_name" : "katy4apes", "name" : "Katy Jedamzik", "id" : 159608654, "id_str" : "159608654", "indices" : [ 41, 51 ] }, { "screen_name" : "theAliceRoberts", "name" : "Prof Alice Roberts", "id" : 260211154, "id_str" : "260211154", "indices" : [ 52, 68 ] }, { "screen_name" : "JaneGoodallUK", "name" : "Roots & Shoots UK", "id" : 423423823, "id_str" : "423423823", "indices" : [ 69, 83 ] }, { "screen_name" : "Jane_Goodall", "name" : "Jane Goodall", "id" : 235157216, "id_str" : "235157216", "indices" : [ 84, 97 ] }, { "screen_name" : "JaneGoodallInst", "name" : "JaneGoodallInstitute", "id" : 39822897, "id_str" : "39822897", "indices" : [ 98, 114 ] } ], "urls" : [ { "url" : "https://t.co/a9lOVMouNK", "expanded_url" : "https://www.eventbrite.com/e/working-with-apes-tickets-33089771397", "display_url" : "eventbrite.com/e/working-with…", "indices" : [ 157, 180 ] } ] }, "source" : "Twitter for iPhone", "in_reply_to_status_id" : 853925036696141824, "in_reply_to_status_id_str" : "853925036696141824", "in_reply_to_user_id" : 21278482, "in_reply_to_user_id_str" : "21278482", "in_reply_to_screen_name" : "jjdesmond", "user" : { "id" : 415472140, "id_str" : "415472140", "name" : "Ben Garrod", "screen_name" : "Ben_garrod", "location" : "Bristol&Norfolk", "description" : "Monkey-chaser, TV-talker, bone geek and Teaching Fellow at @AngliaRuskin https://t.co/FXbftdxxTJ", "url" : "https://t.co/1B9SDHfWoF", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/1B9SDHfWoF", "expanded_url" : "http://www.josarsby.com/ben-garrod", "display_url" : "josarsby.com/ben-garrod", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ { "url" : "https://t.co/FXbftdxxTJ", "expanded_url" : "http://www.anglia.ac.uk/science-and-technology/about/life-sciences/our-staff/ben-garrod", "display_url" : "anglia.ac.uk/science-and-te…", "indices" : [ 73, 96 ] } ] } }, "protected" : false, "followers_count" : 6526, "friends_count" : 1016, "listed_count" : 128, "created_at" : "Fri Nov 18 11:30:48 +0000 2011", "favourites_count" : 25292, "utc_offset" : 3600, "time_zone" : "London", "geo_enabled" : true, "verified" : true, "statuses_count" : 17224, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://pbs.twimg.com/profile_background_images/590945579024257024/2F1itaGz.jpg", "profile_background_image_url_https" : "https://pbs.twimg.com/profile_background_images/590945579024257024/2F1itaGz.jpg", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/615498558385557505/cwSloac3_normal.jpg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/615498558385557505/cwSloac3_normal.jpg", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/415472140/1477223840", "profile_link_color" : "0084B4", "profile_sidebar_border_color" : "FFFFFF", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : false, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 6, "favorite_count" : 7, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "lang" : "en" } """; const string TD7 = """ { "created_at" : "Mon Feb 13 07:59:35 +0000 2017", "id" : 831050171744149504, "id_str" : "831050171744149504", "full_text" : "@GOP https://t.co/e2fTVgNC3D", "truncated" : false, "display_text_range" : [ 4, 4 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "GOP", "name" : "GOP", "id" : 11134252, "id_str" : "11134252", "indices" : [ 0, 4 ] } ], "urls" : [ ], "media" : [ { "id" : 831050167918923776, "id_str" : "831050167918923776", "indices" : [ 5, 28 ], "media_url" : "http://pbs.twimg.com/media/C4h7uYFWEAA1Af6.jpg", "media_url_https" : "https://pbs.twimg.com/media/C4h7uYFWEAA1Af6.jpg", "url" : "https://t.co/e2fTVgNC3D", "display_url" : "pic.twitter.com/e2fTVgNC3D", "expanded_url" : "https://twitter.com/CBoyForeman/status/831050171744149504/photo/1", "type" : "photo", "sizes" : { "large" : { "w" : 576, "h" : 357, "resize" : "fit" }, "small" : { "w" : 576, "h" : 357, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" }, "medium" : { "w" : 576, "h" : 357, "resize" : "fit" } } } ] }, "extended_entities" : { "media" : [ { "id" : 831050167918923776, "id_str" : "831050167918923776", "indices" : [ 5, 28 ], "media_url" : "http://pbs.twimg.com/media/C4h7uYFWEAA1Af6.jpg", "media_url_https" : "https://pbs.twimg.com/media/C4h7uYFWEAA1Af6.jpg", "url" : "https://t.co/e2fTVgNC3D", "display_url" : "pic.twitter.com/e2fTVgNC3D", "expanded_url" : "https://twitter.com/CBoyForeman/status/831050171744149504/photo/1", "type" : "photo", "sizes" : { "large" : { "w" : 576, "h" : 357, "resize" : "fit" }, "small" : { "w" : 576, "h" : 357, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" }, "medium" : { "w" : 576, "h" : 357, "resize" : "fit" } } } ] }, "source" : "Twitter for Android", "in_reply_to_status_id" : 830800137291194368, "in_reply_to_status_id_str" : "830800137291194368", "in_reply_to_user_id" : 11134252, "in_reply_to_user_id_str" : "11134252", "in_reply_to_screen_name" : "GOP", "user" : { "id" : 77301204, "id_str" : "77301204", "name" : "Chrissy Boy", "screen_name" : "CBoyForeman", "location" : "Brighton", "description" : "Musician, allegedly.", "url" : "https://t.co/wkTvkNumcF", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/wkTvkNumcF", "expanded_url" : "http://www.madness.co.uk/chris-cupboard/", "display_url" : "madness.co.uk/chris-cupboard/", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 8605, "friends_count" : 42, "listed_count" : 54, "created_at" : "Fri Sep 25 20:19:11 +0000 2009", "favourites_count" : 71, "utc_offset" : 3600, "time_zone" : "London", "geo_enabled" : true, "verified" : false, "statuses_count" : 1079, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "95B6C7", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : true, "profile_image_url" : "http://pbs.twimg.com/profile_images/542792449907638272/AsxM39Oz_normal.jpeg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/542792449907638272/AsxM39Oz_normal.jpeg", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/77301204/1387963265", "profile_link_color" : "89C9FA", "profile_sidebar_border_color" : "EEEEEE", "profile_sidebar_fill_color" : "EFEFEF", "profile_text_color" : "333333", "profile_use_background_image" : true, "has_extended_profile" : true, "default_profile" : false, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 0, "favorite_count" : 2, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "lang" : "und" } """; const string TD8 = """ { "created_at" : "Mon Apr 24 11:40:53 +0000 2017", "id" : 856473014560591872, "id_str" : "856473014560591872", "full_text" : "RT @corebirdgtk: @baedert and @corebirdclient so?", "truncated" : false, "display_text_range" : [ 0, 49 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "corebirdgtk", "name" : "Z??!@*(&*²³¤²³¤", "id" : 993713617, "id_str" : "993713617", "indices" : [ 3, 15 ] }, { "screen_name" : "baedert", "name" : "Schupp & Wupp", "id" : 118055879, "id_str" : "118055879", "indices" : [ 17, 25 ] }, { "screen_name" : "corebirdclient", "name" : "Corebird", "id" : 2877682863, "id_str" : "2877682863", "indices" : [ 30, 45 ] } ], "urls" : [ ] }, "source" : "Twitter Web Client", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 993713617, "id_str" : "993713617", "name" : "Z??!@*(&*²³¤²³¤", "screen_name" : "corebirdgtk", "location" : "", "description" : "<asdf", "url" : null, "entities" : { "description" : { "urls" : [ ] } }, "protected" : true, "followers_count" : 3, "friends_count" : 4, "listed_count" : 0, "created_at" : "Thu Dec 06 19:47:16 +0000 2012", "favourites_count" : 9, "utc_offset" : 7200, "time_zone" : "Amsterdam", "geo_enabled" : false, "verified" : false, "statuses_count" : 909, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : true, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/655270693341417472/h6BbZKJy_normal.png", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/655270693341417472/h6BbZKJy_normal.png", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/993713617/1413106147", "profile_link_color" : "1DA1F2", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : true, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "retweeted_status" : { "created_at" : "Mon Apr 24 11:35:17 +0000 2017", "id" : 856471602883686400, "id_str" : "856471602883686400", "full_text" : "@baedert and @corebirdclient so?", "truncated" : false, "display_text_range" : [ 9, 32 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "baedert", "name" : "Schupp & Wupp", "id" : 118055879, "id_str" : "118055879", "indices" : [ 0, 8 ] }, { "screen_name" : "corebirdclient", "name" : "Corebird", "id" : 2877682863, "id_str" : "2877682863", "indices" : [ 13, 28 ] } ], "urls" : [ ] }, "source" : "Twitter Web Client", "in_reply_to_status_id" : 853198126106148864, "in_reply_to_status_id_str" : "853198126106148864", "in_reply_to_user_id" : 993713617, "in_reply_to_user_id_str" : "993713617", "in_reply_to_screen_name" : "corebirdgtk", "user" : { "id" : 993713617, "id_str" : "993713617", "name" : "Z??!@*(&*²³¤²³¤", "screen_name" : "corebirdgtk", "location" : "", "description" : "<asdf", "url" : null, "entities" : { "description" : { "urls" : [ ] } }, "protected" : true, "followers_count" : 3, "friends_count" : 4, "listed_count" : 0, "created_at" : "Thu Dec 06 19:47:16 +0000 2012", "favourites_count" : 9, "utc_offset" : 7200, "time_zone" : "Amsterdam", "geo_enabled" : false, "verified" : false, "statuses_count" : 909, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : true, "profile_background_color" : "C0DEED", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/655270693341417472/h6BbZKJy_normal.png", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/655270693341417472/h6BbZKJy_normal.png", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/993713617/1413106147", "profile_link_color" : "1DA1F2", "profile_sidebar_border_color" : "C0DEED", "profile_sidebar_fill_color" : "DDEEF6", "profile_text_color" : "333333", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : true, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 1, "favorite_count" : 0, "favorited" : false, "retweeted" : false, "lang" : "en" }, "is_quote_status" : false, "retweet_count" : 1, "favorite_count" : 0, "favorited" : false, "retweeted" : false, "lang" : "en" } """; const string TD9 = """ { "created_at" : "Thu Apr 27 17:00:15 +0000 2017", "id" : 857640546793177088, "id_str" : "857640546793177088", "full_text" : "Hey @zillow, I get why you make it so hard to close my profile, but it's so transparent and just seems scammy. Scammy, scammy, scammy.", "truncated" : false, "display_text_range" : [ 0, 134 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "zillow", "name" : "Zillow", "id" : 19262500, "id_str" : "19262500", "indices" : [ 4, 11 ] } ], "urls" : [ ] }, "source" : "Twitter Web Client", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 14498432, "id_str" : "14498432", "name" : "Kristian Høgsberg", "screen_name" : "hoegsberg", "location" : "Portland OR", "description" : "Casually Defiant, Inadvertently Pretentious", "url" : "https://t.co/z672MnpkDP", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/z672MnpkDP", "expanded_url" : "http://hoegsberg.blogspot.com", "display_url" : "hoegsberg.blogspot.com", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 689, "friends_count" : 210, "listed_count" : 25, "created_at" : "Wed Apr 23 18:29:05 +0000 2008", "favourites_count" : 717, "utc_offset" : -14400, "time_zone" : "Eastern Time (US & Canada)", "geo_enabled" : true, "verified" : false, "statuses_count" : 2685, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "352726", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme5/bg.gif", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme5/bg.gif", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/679383568016130049/DcwLkGhB_normal.jpg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/679383568016130049/DcwLkGhB_normal.jpg", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/14498432/1450812639", "profile_link_color" : "D02B54", "profile_sidebar_border_color" : "829D5E", "profile_sidebar_fill_color" : "99CC33", "profile_text_color" : "3E4415", "profile_use_background_image" : true, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : true, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 0, "favorite_count" : 0, "favorited" : false, "retweeted" : false, "lang" : "en" } """; const string TD10 = """ { "created_at" : "Fri Apr 28 19:40:04 +0000 2017", "id" : 858043155547140096, "id_str" : "858043155547140096", "full_text" : ".@archillect\nhttps://t.co/8sOpx6hIV8\nhttps://t.co/LazmRudPfL\nhttps://t.co/Q2C5FM0OJO\nhttps://t.co/Vq6aJdD766\nhttps://t.co/otr3LIJ15c", "truncated" : false, "display_text_range" : [ 0, 132 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ { "screen_name" : "archillect", "name" : "Archillect", "id" : 2907774137, "id_str" : "2907774137", "indices" : [ 1, 12 ] } ], "urls" : [ { "url" : "https://t.co/8sOpx6hIV8", "expanded_url" : "http://www.pbs.org/newshour/art/photos-dying-art-neon-hong-kong/", "display_url" : "pbs.org/newshour/art/p…", "indices" : [ 13, 36 ] }, { "url" : "https://t.co/LazmRudPfL", "expanded_url" : "http://www.neonsigns.hk/media/upload/2014/05/shya-5.jpg", "display_url" : "neonsigns.hk/media/upload/2…", "indices" : [ 37, 60 ] }, { "url" : "https://t.co/Q2C5FM0OJO", "expanded_url" : "http://t.umblr.com/redirect?z=http%3A%2F%2Fwww.neonsigns.hk%2Fmy-neon-city%2Fwing-shya%2F%3Flang%3Den&t=YmUzYTFmNTM1NmJjODlkYjlkNGJiMzYyNzRjZmU3OTllOGZhZmIwYyw5ODIzODIzNzg5NQ%3D%3D&b=t%3AYGx1bpY-ynhca0q3r41odw&p=http%3A%2F%2Fotakugangsta.com%2Fpost%2F98238237895&m=0", "display_url" : "t.umblr.com/redirect?z=htt…", "indices" : [ 61, 84 ] }, { "url" : "https://t.co/Vq6aJdD766", "expanded_url" : "https://www.pinterest.com/pin/47569339794350377/", "display_url" : "pinterest.com/pin/4756933979…", "indices" : [ 85, 108 ] }, { "url" : "https://t.co/otr3LIJ15c", "expanded_url" : "https://twitter.com/archillect/status/858043152820826113", "display_url" : "twitter.com/archillect/sta…", "indices" : [ 109, 132 ] } ] }, "source" : "archillinks", "in_reply_to_status_id" : 858043152820826113, "in_reply_to_status_id_str" : "858043152820826113", "in_reply_to_user_id" : 2907774137, "in_reply_to_user_id_str" : "2907774137", "in_reply_to_screen_name" : "archillect", "user" : { "id" : 808595043560857600, "id_str" : "808595043560857600", "name" : "Archillect Links", "screen_name" : "archillinks", "location" : "", "description" : "The Ocular Engine // Links", "url" : "https://t.co/U7Oh7xumQp", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/U7Oh7xumQp", "expanded_url" : "http://archillect.com", "display_url" : "archillect.com", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 9481, "friends_count" : 2, "listed_count" : 107, "created_at" : "Tue Dec 13 08:50:56 +0000 2016", "favourites_count" : 0, "utc_offset" : null, "time_zone" : null, "geo_enabled" : false, "verified" : false, "statuses_count" : 17980, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "000000", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme1/bg.png", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/808595805942714368/PuH1r_aQ_normal.jpg", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/808595805942714368/PuH1r_aQ_normal.jpg", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/808595043560857600/1481619199", "profile_link_color" : "111111", "profile_sidebar_border_color" : "000000", "profile_sidebar_fill_color" : "000000", "profile_text_color" : "000000", "profile_use_background_image" : false, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : true, "quoted_status_id" : 858043152820826113, "quoted_status_id_str" : "858043152820826113", "quoted_status" : { "created_at" : "Fri Apr 28 19:40:03 +0000 2017", "id" : 858043152820826113, "id_str" : "858043152820826113", "full_text" : "https://t.co/4Kk2ol4YDf", "truncated" : false, "display_text_range" : [ 0, 0 ], "entities" : { "hashtags" : [ ], "symbols" : [ ], "user_mentions" : [ ], "urls" : [ ], "media" : [ { "id" : 858043148114808833, "id_str" : "858043148114808833", "indices" : [ 0, 23 ], "media_url" : "http://pbs.twimg.com/media/C-hhsnVXUAESrBA.jpg", "media_url_https" : "https://pbs.twimg.com/media/C-hhsnVXUAESrBA.jpg", "url" : "https://t.co/4Kk2ol4YDf", "display_url" : "pic.twitter.com/4Kk2ol4YDf", "expanded_url" : "https://twitter.com/archillect/status/858043152820826113/photo/1", "type" : "photo", "sizes" : { "large" : { "w" : 1280, "h" : 960, "resize" : "fit" }, "small" : { "w" : 680, "h" : 510, "resize" : "fit" }, "medium" : { "w" : 1200, "h" : 900, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" } } } ] }, "extended_entities" : { "media" : [ { "id" : 858043148114808833, "id_str" : "858043148114808833", "indices" : [ 0, 23 ], "media_url" : "http://pbs.twimg.com/media/C-hhsnVXUAESrBA.jpg", "media_url_https" : "https://pbs.twimg.com/media/C-hhsnVXUAESrBA.jpg", "url" : "https://t.co/4Kk2ol4YDf", "display_url" : "pic.twitter.com/4Kk2ol4YDf", "expanded_url" : "https://twitter.com/archillect/status/858043152820826113/photo/1", "type" : "photo", "sizes" : { "large" : { "w" : 1280, "h" : 960, "resize" : "fit" }, "small" : { "w" : 680, "h" : 510, "resize" : "fit" }, "medium" : { "w" : 1200, "h" : 900, "resize" : "fit" }, "thumb" : { "w" : 150, "h" : 150, "resize" : "crop" } } } ] }, "source" : "Archillect", "in_reply_to_status_id" : null, "in_reply_to_status_id_str" : null, "in_reply_to_user_id" : null, "in_reply_to_user_id_str" : null, "in_reply_to_screen_name" : null, "user" : { "id" : 2907774137, "id_str" : "2907774137", "name" : "Archillect", "screen_name" : "archillect", "location" : "Meta", "description" : "The ocular engine. Sources: @archillinks", "url" : "https://t.co/U7Oh7xumQp", "entities" : { "url" : { "urls" : [ { "url" : "https://t.co/U7Oh7xumQp", "expanded_url" : "http://archillect.com", "display_url" : "archillect.com", "indices" : [ 0, 23 ] } ] }, "description" : { "urls" : [ ] } }, "protected" : false, "followers_count" : 332778, "friends_count" : 1, "listed_count" : 4168, "created_at" : "Sat Dec 06 14:07:45 +0000 2014", "favourites_count" : 10461, "utc_offset" : 10800, "time_zone" : "Istanbul", "geo_enabled" : false, "verified" : false, "statuses_count" : 119611, "lang" : "en", "contributors_enabled" : false, "is_translator" : false, "is_translation_enabled" : false, "profile_background_color" : "050505", "profile_background_image_url" : "http://abs.twimg.com/images/themes/theme19/bg.gif", "profile_background_image_url_https" : "https://abs.twimg.com/images/themes/theme19/bg.gif", "profile_background_tile" : false, "profile_image_url" : "http://pbs.twimg.com/profile_images/542430526057881602/mjWPm56H_normal.png", "profile_image_url_https" : "https://pbs.twimg.com/profile_images/542430526057881602/mjWPm56H_normal.png", "profile_banner_url" : "https://pbs.twimg.com/profile_banners/2907774137/1418102176", "profile_link_color" : "191919", "profile_sidebar_border_color" : "000000", "profile_sidebar_fill_color" : "000000", "profile_text_color" : "000000", "profile_use_background_image" : false, "has_extended_profile" : false, "default_profile" : false, "default_profile_image" : false, "following" : false, "follow_request_sent" : false, "notifications" : false, "translator_type" : "none" }, "geo" : null, "coordinates" : null, "place" : null, "contributors" : null, "is_quote_status" : false, "retweet_count" : 48, "favorite_count" : 68, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "lang" : "und" }, "retweet_count" : 0, "favorite_count" : 0, "favorited" : false, "retweeted" : false, "possibly_sensitive" : false, "lang" : "und" } """; // """ // }}} void retweet () { var now = new GLib.DateTime.now_local (); var t = new Cb.Tweet (); var parser = new Json.Parser (); try { parser.load_from_data (TD1); } catch (GLib.Error e) { critical (e.message); } var root = parser.get_root (); t.load_from_json (root, 0, now); assert (t.id == 463208606784311296); assert (t.retweeted_tweet != null); assert (t.retweeted_tweet.id == 461097667775725569); assert (t.source_tweet.author.user_name == "Frozenbyte"); assert (t.favorite_count == 0); assert (t.retweet_count == 6); assert (t.source_tweet.author.id == 62574927); assert (t.get_mentions ().length == 0); assert (t.retweeted_tweet.author.user_name == "Black Forest Games"); assert (!t.is_flag_set (Cb.TweetState.FAVORITED)); assert (!t.is_flag_set (Cb.TweetState.RETWEETED)); assert (!t.is_flag_set (Cb.TweetState.VERIFIED)); assert (t.source_tweet.reply_id == 0); assert (t.my_retweet == 0); //assert (!t.has_inline_media); } void media_count () { var now = new GLib.DateTime.now_local (); var t = new Cb.Tweet (); var parser = new Json.Parser (); try { parser.load_from_data (TD3); } catch (GLib.Error e) { critical (e.message); } var root = parser.get_root (); t.load_from_json (root, 0, now); assert (t.get_screen_name () == "corebirdgtk"); assert (t.get_medias ().length == 2); } void double_media () { var now = new GLib.DateTime.now_local (); var t = new Cb.Tweet (); var parser = new Json.Parser (); try { parser.load_from_data (TD5); } catch (GLib.Error e) { critical (e.message); } var root = parser.get_root (); t.load_from_json (root, 0, now); // See https://github.com/baedert/corebird/issues/627 // If this was still a problem, the following call would make this test crash t.get_filter_text (); } void special_quote () { /* This is retweet of a tweet that has media attached, but also contains a URL to another tweet. * It's *not* a quote tweet, but the retweeted_status field contains a tweet with a quoted_status * tweet object attached. * * The media is attached to both the outmost tweet object (the retweet) as well as the retweeted_tweet * object (the retweeted tweet). */ var now = new GLib.DateTime.now_local (); var t = new Cb.Tweet (); var parser = new Json.Parser (); try { parser.load_from_data (TD6); } catch (GLib.Error e) { critical (e.message); } var root = parser.get_root (); t.load_from_json (root, 0, now); /* The expected output here is that the tweet is still a retweet, of course... */ assert (t.retweeted_tweet != null); /* ... but the quoted_status field of the retweeted tweet should effectively be ignored * since it also has media attached. */ assert (t.quoted_tweet == null); /* ... I repeat: the retweeted tweet has media attached! */ assert (t.retweeted_tweet.medias.length > 0); } void reply_users () { var now = new GLib.DateTime.now_local (); var t = new Cb.Tweet (); var parser = new Json.Parser (); try { parser.load_from_data (REPLY_TWEET_DATA); } catch (GLib.Error e) { critical (e.message); } var root = parser.get_root (); t.load_from_json (root, 0, now); var reply_users = t.get_reply_users (); assert (reply_users.length == 9); } void empty_display_range () { var now = new GLib.DateTime.now_local (); var t = new Cb.Tweet (); var parser = new Json.Parser (); try { parser.load_from_data (TD7); } catch (GLib.Error e) { critical (e.message); } var root = parser.get_root (); t.load_from_json (root, 0, now); // This should NOT break. var s = t.get_filter_text (); assert (s.length > 0); } void rt_reply () { var now = new GLib.DateTime.now_local (); var t = new Cb.Tweet (); var parser = new Json.Parser (); try { parser.load_from_data (TD8); } catch (GLib.Error e) { critical (e.message); } var root = parser.get_root (); t.load_from_json (root, 0, now); /* * The loaded tweet is a retweet of a tweet that replies * to 2 users (namely @corebirdgtk and @baedert). * @baedert is mentioned directly in the tweet text, before the display_range_start * and @corebirdgtk as the in_reply_to_screen_name embedded in the retweeted tweet. * * The result we want is that the CbTweet is a reply to all the * users the retweeted tweet was a reply to, so both @corebirdgtk and @baedert. */ assert (t.retweeted_tweet.reply_users.length == 2); /* It's a direct reply to @corebirdgtk, so that should be first */ assert (t.retweeted_tweet.reply_users[0].screen_name == "corebirdgtk"); assert (t.retweeted_tweet.reply_users[1].screen_name == "baedert"); /* Consequently, the ones returned by get_reply_users should be the same. */ var reply_users = t.get_reply_users (); assert (reply_users.length == 2); assert (reply_users[0].screen_name == "corebirdgtk"); assert (reply_users[1].screen_name == "baedert"); } void entity_count () { var now = new GLib.DateTime.now_local (); var t = new Cb.Tweet (); var parser = new Json.Parser (); try { parser.load_from_data (TD9); } catch (GLib.Error e) { critical (e.message); } var root = parser.get_root (); t.load_from_json (root, 0, now); assert (t.source_tweet.entities.length == 1); } void reply_id () { var now = new GLib.DateTime.now_local (); var t = new Cb.Tweet (); var parser = new Json.Parser (); try { parser.load_from_data (TD10); } catch (GLib.Error e) { critical (e.message); } var root = parser.get_root (); t.load_from_json (root, 0, now); /* The source tweet is a reply, but the quoted one isn't */ assert (t.source_tweet.reply_id == 858043152820826113); assert (t.quoted_tweet != null); assert (t.quoted_tweet.reply_id == 0); /* source_tweet is a reply, so should definitely have >0 reply_users. * This is interesting for this tweet since the tweet text contains * a user mention, but it's part of the tweet text and not before * the display_range_start. */ assert (t.source_tweet.reply_users.length == 1); /* Sanity check: The source_tweet's user mention should still end up being * a text entity of course. */ assert (t.source_tweet.entities.length > 0); /* It's the first one. I'm sure. */ assert (t.source_tweet.entities[0].display_text == "@archillect"); } int main (string[] args) { GLib.Test.init (ref args); Settings.init (); Gtk.init (ref args); Twitter.get ().init (); Utils.init_soup_session (); // Parsing tweets will otherwise cause media to be downloaded which is an async operation // so it might spuriously make these tests fail. Also, we don't care about media here. Cb.MediaDownloader.get_default ().disable (); GLib.Test.add_func ("/tweet-parsing/retweet", retweet); GLib.Test.add_func ("/tweet-parsing/media-count", media_count); GLib.Test.add_func ("/tweet-parsing/double-media", double_media); GLib.Test.add_func ("/tweet-parsing/special-quote", special_quote); GLib.Test.add_func ("/tweet-parsing/reply-users", reply_users); GLib.Test.add_func ("/tweet-parsing/empty-display-range", empty_display_range); GLib.Test.add_func ("/tweet-parsing/rt-reply", rt_reply); GLib.Test.add_func ("/tweet-parsing/entity-count", entity_count); GLib.Test.add_func ("/tweet-parsing/reply-id", reply_id); return GLib.Test.run (); } corebird-1.7.4/tests/twitteritem.vala000066400000000000000000000023471324604713000177120ustar00rootroot00000000000000 class TestItem : GLib.Object, Cb.TwitterItem { int64 timestamp; int64 sort_factor; GLib.TimeSpan last_timediff; public TestItem (int64 a, int64 b) { timestamp = a; sort_factor = b; } public int64 get_sort_factor () { return sort_factor; } public int64 get_timestamp () { return timestamp; } public int update_time_delta (GLib.DateTime? now = null) { return 0; } public void set_last_set_timediff (GLib.TimeSpan span) { this.last_timediff = span; } public GLib.TimeSpan get_last_set_timediff () { return this.last_timediff; } } void simple () { var a = new TestItem (1, 2); var b = new TestItem (10, 20); assert (a.get_timestamp () == 1); assert (a.get_sort_factor () == 2); assert (b.get_timestamp () == 10); assert (b.get_sort_factor () == 20); a.set_last_set_timediff (100); b.set_last_set_timediff (1000); message ("a: %s", a.get_last_set_timediff ().to_string ()); message ("b: %s", b.get_last_set_timediff ().to_string ()); assert (a.get_last_set_timediff () == 100); assert (b.get_last_set_timediff () == 1000); } int main (string[] args) { GLib.Test.init (ref args); GLib.Test.add_func ("/tweetitem/simple", simple); return GLib.Test.run (); } corebird-1.7.4/tests/usercompletionmodel.vala000066400000000000000000000065071324604713000214240ustar00rootroot00000000000000void simple () { var model = new Cb.UserCompletionModel (); assert (model.get_n_items () == 0); model.clear (); assert (model.get_n_items () == 0); } void add_infos () { var model = new Cb.UserCompletionModel (); var infos = new Cb.UserInfo[3]; infos[0].user_id = 1; infos[0].screen_name = "a"; infos[0].user_name = "aa"; infos[1].user_id = 2; infos[1].screen_name = "b"; infos[1].user_name = "bb"; infos[2].user_id = 3; infos[2].screen_name = "c"; infos[2].user_name = "cc"; model.insert_infos (infos); assert (model.get_n_items () == 3); Cb.UserIdentity *id = (Cb.UserIdentity*)model.get_item (0); assert (id->id == 1); assert (id->screen_name == "a"); assert (id->user_name == "aa"); id = (Cb.UserIdentity*)model.get_item (1); assert (id->id == 2); assert (id->screen_name == "b"); assert (id->user_name == "bb"); id = (Cb.UserIdentity*)model.get_item (2); assert (id->id == 3); assert (id->screen_name == "c"); assert (id->user_name == "cc"); // test clear model.clear (); assert (model.get_n_items () == 0); } void add_items () { var model = new Cb.UserCompletionModel (); var ids = new Cb.UserIdentity[3]; ids[0].id = 1; ids[0].screen_name = "a"; ids[0].user_name = "aa"; ids[1].id = 2; ids[1].screen_name = "b"; ids[1].user_name = "bb"; ids[2].id = 3; ids[2].screen_name = "c"; ids[2].user_name = "cc"; model.insert_items (ids); assert (model.get_n_items () == 3); Cb.UserIdentity *id = (Cb.UserIdentity*)model.get_item (0); assert (id->id == 1); assert (id->screen_name == "a"); assert (id->user_name == "aa"); id = (Cb.UserIdentity*)model.get_item (1); assert (id->id == 2); assert (id->screen_name == "b"); assert (id->user_name == "bb"); id = (Cb.UserIdentity*)model.get_item (2); assert (id->id == 3); assert (id->screen_name == "c"); assert (id->user_name == "cc"); // test clear model.clear (); assert (model.get_n_items () == 0); } void id_duplicates () { var model = new Cb.UserCompletionModel (); var ids = new Cb.UserIdentity[3]; ids[0].id = 1; ids[0].screen_name = "a"; ids[0].user_name = "aa"; ids[1].id = 2; ids[1].screen_name = "b"; ids[1].user_name = "bb"; ids[2].id = 3; ids[2].screen_name = "c"; ids[2].user_name = "cc"; model.insert_items (ids); assert (model.get_n_items () == 3); model.insert_items (ids); // Still. assert (model.get_n_items () == 3); } void info_duplicates () { var model = new Cb.UserCompletionModel (); var infos = new Cb.UserInfo[3]; infos[0].user_id = 1; infos[0].screen_name = "a"; infos[0].user_name = "aa"; infos[1].user_id = 2; infos[1].screen_name = "b"; infos[1].user_name = "bb"; infos[2].user_id = 3; infos[2].screen_name = "c"; infos[2].user_name = "cc"; model.insert_infos (infos); assert (model.get_n_items () == 3); model.insert_infos (infos); assert (model.get_n_items () == 3); } int main (string[] args) { GLib.Test.init (ref args); GLib.Test.add_func ("/usercompletionmodel/simple", simple); GLib.Test.add_func ("/usercompletionmodel/add-infos", add_infos); GLib.Test.add_func ("/usercompletionmodel/add-items", add_items); GLib.Test.add_func ("/usercompletionmodel/id-duplicates", id_duplicates); GLib.Test.add_func ("/usercompletionmodel/info-duplicates", info_duplicates); return GLib.Test.run (); } corebird-1.7.4/tests/usercounter.vala000066400000000000000000000124741324604713000177110ustar00rootroot00000000000000void count () { FileUtils.remove (Dirs.config ("accounts/test-account.db")); var db = new Sql.Database (Dirs.config ("accounts/test-account.db"), Sql.ACCOUNTS_INIT_FILE, Sql.ACCOUNTS_SQL_VERSION); var counter = new Cb.UserCounter (); counter.user_seen (0, "baedert", "blabla"); int changed = counter.save (db.get_sqlite_db ()); message ("Single change: %d", changed); assert (changed == 1); assert (counter.save (db.get_sqlite_db ()) == 0); counter.user_seen (1, "baedert", ""); counter.user_seen (1, "baedert", ""); changed = counter.save (db.get_sqlite_db ()); message ("Double change: %d", changed); assert (changed == 1); assert (counter.save (db.get_sqlite_db ()) == 0); counter.user_seen (2, "baedert", ""); counter.user_seen (3, "baedert", ""); changed = counter.save (db.get_sqlite_db ()); message ("Two users changed: %d", changed); assert (changed == 2); assert (counter.save (db.get_sqlite_db ()) == 0); } void query_after_save () { FileUtils.remove (Dirs.config ("accounts/test-account.db")); var db = new Sql.Database (Dirs.config ("accounts/test-account.db"), Sql.ACCOUNTS_INIT_FILE, Sql.ACCOUNTS_SQL_VERSION); var counter = new Cb.UserCounter (); counter.user_seen (10, "baedert", "foobar"); counter.user_seen (20, "baedert2", "foobar2"); counter.save (db.get_sqlite_db ()); Cb.UserInfo[] infos; counter.query_by_prefix (db.get_sqlite_db (), "b", 10, out infos); assert (infos.length == 2); assert (infos[0].screen_name == "baedert"); assert (infos[1].screen_name == "baedert2"); } void query_no_save () { FileUtils.remove (Dirs.config ("accounts/test-account.db")); var db = new Sql.Database (Dirs.config ("accounts/test-account.db"), Sql.ACCOUNTS_INIT_FILE, Sql.ACCOUNTS_SQL_VERSION); var counter = new Cb.UserCounter (); counter.user_seen (10, "baedert", "foobar"); counter.user_seen (10, "baedert", "foobar"); // See this one twice so the order makes sense counter.user_seen (20, "baedert2", "foobar2"); Cb.UserInfo[] infos; counter.query_by_prefix (db.get_sqlite_db (), "b", 10, out infos); assert (infos.length == 2); assert (infos[0].screen_name == "baedert"); assert (infos[1].screen_name == "baedert2"); } void query_mixed () { FileUtils.remove (Dirs.config ("accounts/test-account.db")); var db = new Sql.Database (Dirs.config ("accounts/test-account.db"), Sql.ACCOUNTS_INIT_FILE, Sql.ACCOUNTS_SQL_VERSION); var counter = new Cb.UserCounter (); counter.user_seen (10, "baedert", "foobar"); counter.user_seen (10, "baedert", "foobar"); counter.user_seen (10, "baedert", "foobar"); counter.user_seen (10, "baedert", "foobar"); counter.user_seen (20, "baedert2", "foobar2"); counter.save (db.get_sqlite_db ()); /* Make sure nothing's in memory anymore */ db = new Sql.Database (Dirs.config ("accounts/test-account.db"), Sql.ACCOUNTS_INIT_FILE, Sql.ACCOUNTS_SQL_VERSION); counter = new Cb.UserCounter (); counter.user_seen (11, "b_", "__"); counter.user_seen (11, "b_", "__"); counter.user_seen (11, "b_", "__"); counter.user_seen (12, "ba", "bb"); counter.user_seen (12, "ba", "bb"); Cb.UserInfo[] infos; counter.query_by_prefix (db.get_sqlite_db (), "b", 10, out infos); assert (infos.length == 4); assert (infos[0].user_id == 10); assert (infos[1].user_id == 11); assert (infos[2].user_id == 12); assert (infos[3].user_id == 20); } void duplicates () { FileUtils.remove (Dirs.config ("accounts/test-account.db")); var db = new Sql.Database (Dirs.config ("accounts/test-account.db"), Sql.ACCOUNTS_INIT_FILE, Sql.ACCOUNTS_SQL_VERSION); var counter = new Cb.UserCounter (); counter.user_seen (10, "baedert", "foobar"); counter.save (db.get_sqlite_db ()); counter.user_seen (10, "baedert", "foobar"); // Now we have the same entry in memory and in the database. Cb.UserInfo[] infos; counter.query_by_prefix (db.get_sqlite_db (), "b", 2, out infos); assert (infos.length == 1); } void ids_64bit () { FileUtils.remove (Dirs.config ("accounts/test-account.db")); var db = new Sql.Database (Dirs.config ("accounts/test-account.db"), Sql.ACCOUNTS_INIT_FILE, Sql.ACCOUNTS_SQL_VERSION); var counter = new Cb.UserCounter (); counter.user_seen (741369463338115072, "baedert", "foobar"); counter.save (db.get_sqlite_db ()); // Now we have the same entry in memory and in the database. Cb.UserInfo[] infos; counter.query_by_prefix (db.get_sqlite_db (), "b", 1, out infos); assert (infos.length == 1); assert (infos[0].user_id == 741369463338115072); } int main (string[] args) { GLib.Test.init (ref args); Dirs.create_dirs (); GLib.Test.add_func ("/usercounter/count", count); GLib.Test.add_func ("/usercounter/query-after-save", query_after_save); GLib.Test.add_func ("/usercounter/query-no-save", query_no_save); GLib.Test.add_func ("/usercounter/query-mixed", query_mixed); GLib.Test.add_func ("/usercounter/duplicates", duplicates); GLib.Test.add_func ("/usercounter/ids-64bit", ids_64bit); return GLib.Test.run (); } corebird-1.7.4/tests/utils.vala000066400000000000000000000020541324604713000164640ustar00rootroot00000000000000 void file_type () { string p = "foobar.png"; assert (Cb.Utils.get_file_type (p) == "png"); p = ".hidden.bar"; assert (Cb.Utils.get_file_type (p) == "bar"); p = "foo"; assert (Cb.Utils.get_file_type (p) == ""); p = "some.pointy.name.txt"; assert (Cb.Utils.get_file_type (p) == "txt"); p = "/foo/bar/zomg.txt"; assert (Cb.Utils.get_file_type (p) == "txt"); } void time_delta () { var now = new GLib.DateTime.now_local (); var then = now.add (-GLib.TimeSpan.MINUTE * 3); string delta = Utils.get_time_delta (then, now); assert (delta == "3m"); then = now; delta = Utils.get_time_delta (then, now); assert (delta == "Now"); then = now.add (-GLib.TimeSpan.HOUR * 20); delta = Utils.get_time_delta (then, now); assert (delta == "20h"); then = now; delta = Utils.get_time_delta (then, now); assert (delta == "Now"); } int main (string[] args) { GLib.Test.init (ref args); GLib.Test.add_func ("/utils/file-type", file_type); GLib.Test.add_func ("/utils/time-delta", time_delta); return GLib.Test.run (); } corebird-1.7.4/ui/000077500000000000000000000000001324604713000137315ustar00rootroot00000000000000corebird-1.7.4/ui/about-dialog.ui000066400000000000000000000013371324604713000166430ustar00rootroot00000000000000 corebird-1.7.4/ui/account-create-widget.ui000066400000000000000000000143701324604713000204530ustar00rootroot00000000000000 corebird-1.7.4/ui/account-dialog.ui000066400000000000000000000350321324604713000171640ustar00rootroot00000000000000 corebird-1.7.4/ui/cb-emoji-chooser.ui000066400000000000000000000320171324604713000174200ustar00rootroot00000000000000 corebird-1.7.4/ui/compose-window.ui000066400000000000000000000247351324604713000172550ustar00rootroot00000000000000 corebird-1.7.4/ui/dm-page.ui000066400000000000000000000057271324604713000156150ustar00rootroot00000000000000 corebird-1.7.4/ui/dm-thread-entry.ui000066400000000000000000000075361324604713000173070ustar00rootroot00000000000000 corebird-1.7.4/ui/filter-list-entry.ui000066400000000000000000000114311324604713000176650ustar00rootroot00000000000000 corebird-1.7.4/ui/filter-page.ui000066400000000000000000000062021324604713000164670ustar00rootroot00000000000000 1 Users corebird-1.7.4/ui/list-list-entry.ui000066400000000000000000000143471324604713000173640ustar00rootroot00000000000000 corebird-1.7.4/ui/list-statuses-page.ui000066400000000000000000000412601324604713000200310ustar00rootroot00000000000000 1 1 Confirm 1 corebird-1.7.4/ui/media-dialog.ui000066400000000000000000000053671324604713000166170ustar00rootroot00000000000000 corebird-1.7.4/ui/menus.ui000066400000000000000000000014761324604713000154270ustar00rootroot00000000000000
Settings app.show-settings Shortcuts action-disabled app.show-shortcuts About app.show-about-dialog Quit app.quit
corebird-1.7.4/ui/modify-filter-dialog.ui000066400000000000000000000116031324604713000203000ustar00rootroot00000000000000 corebird-1.7.4/ui/modify-snippet-dialog.ui000066400000000000000000000117541324604713000205040ustar00rootroot00000000000000 corebird-1.7.4/ui/new-list-entry.ui000066400000000000000000000076051324604713000172010ustar00rootroot00000000000000 corebird-1.7.4/ui/profile-page.ui000066400000000000000000000546701324604713000166560ustar00rootroot00000000000000
Write Direct Message user.write-dm Tweet to @foobar user.tweet-to Add to/Remove from List user.add-remove-list
Blocked user.toggle-blocked Muted user.toggle-muted Retweets disabled user.toggle-retweets
corebird-1.7.4/ui/search-page.ui000066400000000000000000000070251324604713000164530ustar00rootroot00000000000000 1 6 6 1 0 Tweets 1 6 6 1 0 Users corebird-1.7.4/ui/settings-dialog.ui000066400000000000000000000531071324604713000173730ustar00rootroot00000000000000 20 2 0.1 1 1 main_stack corebird-1.7.4/ui/shortcuts-window.ui000066400000000000000000000153671324604713000176470ustar00rootroot00000000000000 1 1 shortcuts 1 General 1 <ctrl>t Compose Tweet 1 <Ctrl>p Show Account settings 1 <Ctrl>k Show Accounts Popover 1 <Ctrl><Shift>p Show Application Settings 1 <Ctrl><Shift>s Toggle Topbar 1 Back Go Back 1 Forward Go Forward 1 <Alt>1...7 Go to nth page 1 Tweets 1 t+t Retweet 1 f Favorite 1 r Reply true q Quote 1 Return Show Details 1 d+d Delete 1 Compose 1 <ctrl>Return Send 1 Escape Cancel 1 <ctrl>e Show Emoji Chooser corebird-1.7.4/ui/start-conversation-entry.ui000066400000000000000000000121341324604713000212750ustar00rootroot00000000000000 corebird-1.7.4/ui/style.css000066400000000000000000000117721324604713000156130ustar00rootroot00000000000000@define-color topbar_bg #333; * { /* The default for this style property for gtk3 is 150 * so we have to get rid of that. */ -GtkProgressBar-min-horizontal-bar-width: 0; } .avatar-round { border: 1px solid grey; border-radius: 48px; } .account-button { padding: 2px 4px; } .topbar button image, .topbar .button GtkImage { icon-shadow: none; color: #ddd; } .topbar button, .topbar .button { border: none; box-shadow: none; background-image: none; border-radius: 0px; background-color: @topbar_bg; outline-color: #999; margin: 0px; -GtkWidget-window-dragging: 1; } .topbar button:hover, .topbar .button:hover { background-color: shade(@topbar_bg, 1.3); } .topbar button:checked, .topbar .button:checked { background-color: shade(@topbar_bg, 1.5); } .topbar button .badge, .topbar .button .badge { background-color: shade(blue, 1.4); background-image: none; text-shadow: 0px 1px 1px #FFF; border-radius: 20px; border: 1px solid white; padding: 3px 2px; } .topbar button .badge:backdrop, .topbar .button .badge:backdrop { background-color: grey; } .inline-media { background: none; border-radius: 0px; padding: 0px; opacity: 0.9; box-shadow: none; border: none; border-bottom: 2px solid transparent; border-top: 2px solid transparent; } .inline-media:hover { opacity: 1.0; box-shadow: none; border-top: 2px solid grey; border-bottom: 2px solid grey; } .image-success { box-shadow: none; background-image: none; border-radius: 0px; border: none; background-color: transparent; color: white; background-color: alpha (green, 0.5); -gtk-icon-source: -gtk-icontheme("object-select-symbolic"); } .image-error { box-shadow: none; background-image: none; border-radius: 0px; border: none; background-color: transparent; color: white; background-color: alpha (red, 0.5); -gtk-icon-source: -gtk-icontheme("dialog-error-symbolic"); } .retweet-button:hover, .retweet-button:checked { color: shade(green, 1.1); } .favorite-button:hover, .favorite-button:checked { color: red; } /* Don't target :hover:active here */ .favorite-button:checked:not(:hover), .retweet-button:checked:not(:hover) { background-image: none; background-color: transparent; border-color: transparent; box-shadow: none; } .reply-button:hover { color: blue; } .favorite-button:backdrop, .retweet-button:backdrop { background-color: transparent; } .tweet { border-bottom: solid 1px alpha (grey, 0.3); } .text-only-button{ font-weight: bold; background-image: none; background-color: transparent; border:none; text-shadow: none; padding: 0px; box-shadow: none; border-radius: 0px; border-bottom: 1px solid transparent; min-height: 0px; margin-bottom: -1px; outline-offset: 0; } .text-only-button:hover { border-bottom: 1px solid currentColor; } .text-only-button:active { color: inherit; background-color: alpha(currentColor, 0.4); } .text-only-button:backdrop { background-color: transparent; } .pixbuf-button { padding: 0px; background-image: none; background-color: transparent; border: none; border-radius: 0px; box-shadow: none; transition: box-shadow 0.2s ease-in-out; } .pixbuf-button-round { border: 1px solid grey; border-radius: 48px; } .pixbuf-button:hover { border: 1px solid #4a90d9; } .pixbuf-button:active { box-shadow: inset 0px 0px 3px alpha(black, 0.95); } .pixbuf-button:backdrop { background-color: transparent; background-image: none; } replyindicator { background-color: #4a90d9; } .profile-button { border-radius: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; border-bottom: 2px solid transparent; box-shadow: none; } .profile-button:checked { border: none; box-shadow: none; background-image: none; background-color: transparent; color: inherit; border-bottom: 2px solid #4a90d9; } .profile-button:hover { background-image: none; background-color: alpha(white, 0.3); border-bottom: 2px solid #aaa; } .profile-button:active { color: inherit; border-bottom: 2px solid grey; } .quote { border-left: 3px solid alpha(currentColor, 0.4); } .close-button { padding: 3px; border-radius: 50%; outline-radius: 50%; min-height: 0px; min-width: 0px; box-shadow: none; } .avatar:insensitive, .inline-media:insensitive { opacity: 0.4; } /* Override the earlier rule to read-only * inline media are still full-opacity */ .read-only .inline-media:insensitive { opacity: 1.0; } progressbar.horizontal.embedded-progress, progressbar.horizontal.embedded-progress>trough, progressbar.horizontal.embedded-progress>trough>progress { margin: 0px; border-radius: 0px; border-left-width: 0px; border-right-width: 0px; } .fav-image-box { padding: 6px; } /* GtkFlowBox doesn't handle the hover state of its children so... */ .fav-image-item:hover { background-color: alpha(black, 0.1); } .fav-image-item:active { background-color: alpha(black, 0.3); } .invisible-links link:link { color:inherit; } .tweet-info-grid { padding: 12px; } corebird-1.7.4/ui/tweet-info-page.ui000066400000000000000000000432531324604713000172720ustar00rootroot00000000000000
Quote tweet.quote Delete tweet.delete
corebird-1.7.4/ui/tweet-list-entry.ui000066400000000000000000000331541324604713000175360ustar00rootroot00000000000000
Quote tweet.quote Delete tweet.delete
corebird-1.7.4/ui/user-filter-entry.ui000066400000000000000000000142401324604713000176710ustar00rootroot00000000000000 corebird-1.7.4/ui/user-list-entry.ui000066400000000000000000000127361324604713000173670ustar00rootroot00000000000000 corebird-1.7.4/ui/user-lists-widget.ui000066400000000000000000000115211324604713000176630ustar00rootroot00000000000000 corebird-1.7.4/vapi/000077500000000000000000000000001324604713000142535ustar00rootroot00000000000000corebird-1.7.4/vapi/config.vapi000066400000000000000000000004131324604713000163770ustar00rootroot00000000000000[CCode (cprefix = "", lower_case_cprefix = "", cheader_filename = "config.h")] namespace Config { public const string GETTEXT_PACKAGE; public const string PACKAGE; public const string PACKAGE_NAME; public const string DATADIR; public const string PKGDATADIR; } corebird-1.7.4/vapi/corebird-internal.vapi000066400000000000000000000355441324604713000205520ustar00rootroot00000000000000 [CCode (cprefix = "Cb", lower_case_cprefix = "cb_")] namespace Cb { [CCode (cprefix = "CB_MEDIA_TYPE_", cheader_filename = "CbMedia.h")] public enum MediaType { IMAGE, GIF, ANIMATED_GIF, TWITTER_VIDEO, INSTAGRAM_VIDEO, UNKNOWN } [CCode (cprefix = "CB_STREAM_MESSAGE_", cheader_filename = "CbUserStream.h")] public enum StreamMessageType { UNSUPPORTED, DELETE, DM_DELETE, SCRUB_GEO, LIMIT, DISCONNECT, FRIENDS, EVENT, WARNING, DIRECT_MESSAGE, TWEET, EVENT_LIST_CREATED, EVENT_LIST_DESTROYED, EVENT_LIST_UPDATED, EVENT_LIST_UNSUBSCRIBED, EVENT_LIST_SUBSCRIBED, EVENT_LIST_MEMBER_ADDED, EVENT_LIST_MEMBER_REMOVED, EVENT_FAVORITE, EVENT_UNFAVORITE, EVENT_FOLLOW, EVENT_UNFOLLOW, EVENT_BLOCK, EVENT_UNBLOCK, EVENT_MUTE, EVENT_UNMUTE, EVENT_USER_UPDATE, EVENT_QUOTED_TWEET } [CCode (cprefix = "CbMedia_", lower_case_cprefix = "cb_media_", cheader_filename = "CbMedia.h")] public class Media : GLib.Object { public int64 length; public bool loaded; public bool invalid; public string url; public string thumb_url; public string target_url; public MediaType type; public int width; public int height; public double percent_loaded; public Cairo.ImageSurface? surface; public Gdk.PixbufAnimation? animation; public signal void progress(); public Media(); public bool is_video (); } [CCode (cprefix = "CbUserIdentity_", lower_case_cprefix = "cb_user_identity_", cheader_filename = "CbTypes.h", destroy_function = "cb_user_identity_free")] public struct UserIdentity { public int64 id; public string screen_name; public string user_name; public bool verified; public void parse (Json.Object object); } /* Needed for unit tests */ [CCode (cprefix = "CbMediaDownloader", lower_case_cprefix = "cb_media_downloader_", cheader_filename = "CbMediaDownloader.h")] public class MediaDownloader : GLib.Object { public static unowned MediaDownloader get_default (); public async void load_async (Media media); public void disable (); public void shutdown (); } [CCode (cprefix = "CbTextEntity", lower_case_cprefix = "cb_text_entity_", cheader_filename = "CbTypes.h", destroy_function = "cb_text_entity_free")] public struct TextEntity { public uint from; public uint to; public string display_text; public string tooltip_text; public string? target; // If target is null, use display_text as target! public uint info; } [CCode (cprefix = "CbMiniTweet", lower_case_cprefix = "cb_mini_tweet_", cheader_filename = "CbTypes.h", destroy_function = "cb_mini_tweet_free")] public struct MiniTweet { public int64 id; public int64 created_at; public uint display_range_start; public int64 reply_id; public Cb.UserIdentity author; public string text; [CCode (array_length_cname = "n_entities", array_length_type = "size_t")] public Cb.TextEntity[] entities; [CCode (array_length_cname = "n_medias", array_length_type = "size_t")] public Cb.Media[] medias; [CCode (array_length_cname = "n_reply_users", array_length_type = "size_t")] public Cb.UserIdentity[] reply_users; [CCode (cname = "cb_mini_tweet_init")] public MiniTweet(); public void parse (Json.Object obj); public void parse_entities (Json.Object status); } [CCode (cprefix = "CbTweet", lower_case_cprefix = "cb_tweet_", cheader_filename = "CbTweet.h")] public class Tweet : GLib.Object { [CCode (cname = "CB_TWEET_MAX_LENGTH")] public static int MAX_LENGTH; public Cb.MiniTweet source_tweet; public Cb.MiniTweet? retweeted_tweet; public Cb.MiniTweet? quoted_tweet; public int64 id; public int64 my_retweet; public int favorite_count; public int retweet_count; public string avatar_url; public Tweet(); public int64 get_user_id (); public unowned string get_screen_name (); public unowned string get_user_name (); public bool has_inline_media (); public void load_from_json (Json.Node node, int64 account_id, GLib.DateTime now); public bool is_flag_set (uint flag); public void set_flag (uint flag); public void unset_flag (uint flag); public string get_formatted_text (); public string get_trimmed_text (uint transform_flags); public string get_real_text (); public string get_filter_text (); public unowned Cb.UserIdentity[] get_reply_users (); public unowned Cb.Media[] get_medias(); public string[] get_mentions(); public signal void state_changed(); public bool is_hidden (); public string? notification_id; public bool get_seen (); public void set_seen (bool val); public uint state; #if DEBUG public string json_data; #endif } [CCode (cprefix = "CB_TWEET_STATE_", lower_case_cprefix = "CbTweetState", cheader_filename = "CbTweet.h")] [Flags] public enum TweetState { HIDDEN_FORCE, HIDDEN_UNFOLLOWED, HIDDEN_FILTERED, HIDDEN_RTS_DISABLED, HIDDEN_RT_BY_USER, HIDDEN_RT_BY_FOLLOWEE, HIDDEN_AUTHOR_BLOCKED, HIDDEN_RETWEETER_BLOCKED, HIDDEN_AUTHOR_MUTED, HIDDEN_RETWEETER_MUTED, RETWEETED, FAVORITED, DELETED, VERIFIED, PROTECTED, NSFW } [CCode (cprefix = "CB_TEXT_TRANSFORM_", lower_case_cprefix = "CbTextTransformFlags", cheader_filename = "CbTextTransform.h")] public enum TransformFlags { REMOVE_TRAILING_HASHTAGS, EXPAND_LINKS, REMOVE_MEDIA_LINKS } [CCode (cprefix = "cb_text_transform_", cheader_filename = "CbTextTransform.h")] namespace TextTransform { string tweet (ref MiniTweet tweet, uint flags, int64 quote_id); string text (string text, TextEntity[] entities, uint flags, size_t n_medias, int64 quote_id, uint display_range_start = 0); } [CCode (cprefix = "cb_filter_", cheader_filename = "CbFilter.h")] public class Filter : GLib.Object { public Filter(string expr); public bool matches (string text); public void reset (string expr); public unowned string get_contents(); public int get_id (); public void set_id (int id); } [CCode (cprefix = "cb_avatar_cache_", cheader_filename = "CbAvatarCache.h")] class AvatarCache : GLib.Object { public AvatarCache (); public void add (int64 user_id, Cairo.Surface? surface, string? avatar_url); public void increase_refcount_for_surface (Cairo.Surface surface); public void decrease_refcount_for_surface (Cairo.Surface surface); public void set_url (int64 user_id, string url); public void set_avatar (int64 user_id, Cairo.Surface? surface, string url); public unowned Cairo.Surface? get_surface_for_id (int64 user_id, out bool found); public unowned string? get_url_for_id (int64 user_id); public uint get_n_entries (); } [CCode (cprefix = "cb_user_counter_", cheader_filename = "CbUserCounter.h")] public class UserCounter : GLib.Object { public UserCounter (); public void id_seen (ref Cb.UserIdentity id); public void user_seen (int64 id, string screen_name, string user_name); public int save (Sqlite.Database db); public void query_by_prefix (Sqlite.Database db, string prefix, int max_results, out Cb.UserInfo[] infos); } [CCode (cheader_filename = "CbUserCounter.h")] public struct UserInfo { int64 user_id; string screen_name; string user_name; int score; bool changed; } [CCode (cprefix = "CbMediaImageWidget_", lower_case_cprefix = "cb_media_image_widget_", cheader_filename = "CbMediaImageWidget.h")] public class MediaImageWidget : Gtk.ScrolledWindow { public MediaImageWidget (Media media); public void scroll_to (double x, double y); } [CCode (cprefix = "CbTweetModel_", lower_case_cprefix = "cb_tweet_model_", cheader_filename = "CbTweetModel.h")] public class TweetModel : GLib.Object, GLib.ListModel { public int64 min_id; public int64 max_id; public GLib.GenericArray hidden_tweets; public TweetModel (); public bool contains_id (int64 id); public void clear (); public unowned Tweet? get_for_id (int64 id, int diff = -1); public void add (Tweet t); public void remove_last_n_visible (uint amount); public bool delete_id (int64 id, out bool seen); public bool set_tweet_flag (Tweet t, TweetState flag); public bool unset_tweet_flag (Tweet t, TweetState flag); public void remove_tweet (Tweet t); public void remove_tweets_above (int64 id); public void toggle_flag_on_user_tweets (int64 user_id, TweetState flag, bool active); public void toggle_flag_on_user_retweets (int64 user_id, TweetState flag, bool active); } [CCode (cprefix = "CbTwitterItemInterface_", lower_case_cprefix = "cb_twitter_item_", cheader_filename = "CbTwitterItem.h", type_cname = "CbTwitterItemInterface")] public interface TwitterItem : GLib.Object { public abstract int64 get_sort_factor(); public abstract int64 get_timestamp(); public abstract int update_time_delta (GLib.DateTime? now = null); public abstract void set_last_set_timediff (GLib.TimeSpan span); public abstract GLib.TimeSpan get_last_set_timediff (); } [CCode (cprefix = "CbMessageReceiverInterface_", lower_case_cprefix = "cb_message_receiver_", cheader_filename = "CbMessageReceiver.h", type_cname = "CbMessageReceiverInterface")] public interface MessageReceiver : GLib.Object { public abstract void stream_message_received (Cb.StreamMessageType type, Json.Node node); } [CCode (cprefix = "CbDeltaUpdater_", lower_case_cprefix = "cb_delta_updater_", cheader_filename = "CbDeltaUpdater.h")] public class DeltaUpdater : GLib.Object { public DeltaUpdater (Gtk.Widget listbox); } [CCode (cprefix = "CbUtils_", lower_case_cprefix = "cb_utils_", cheader_filename = "CbUtils.h")] namespace Utils { public delegate Gtk.Widget CreateWidgetFunc (void *item); public void bind_model (Gtk.Widget listbox, GLib.ListModel model, Gtk.ListBoxCreateWidgetFunc func); public void bind_non_gobject_model (Gtk.Widget listbox, GLib.ListModel model, Cb.Utils.CreateWidgetFunc func); public void unbind_non_gobject_model (Gtk.Widget *listbox, GLib.ListModel model); public void linkify_user (ref Cb.UserIdentity id, GLib.StringBuilder str); public void write_reply_text (ref Cb.MiniTweet t, GLib.StringBuilder str); public GLib.DateTime parse_date (string _in); public string get_file_type (string url); public string rest_proxy_call_to_string (Rest.ProxyCall c); public async Json.Node? load_threaded_async (Rest.ProxyCall call, GLib.Cancellable? cancellable) throws GLib.Error; public async UserIdentity[] query_users_async (Rest.Proxy p, string q, GLib.Cancellable? cancellable) throws GLib.Error; } [CCode (cprefix = "CbBundle_", lower_case_cprefix = "cb_bundle_", cheader_filename = "CbBundle.h")] public class Bundle : GLib.Object { public Bundle (); public void put_string (int key, string val); public unowned string get_string (int key); public void put_int (int key, int val); public int get_int (int key); public void put_int64 (int key, int64 val); public int64 get_int64 (int key); public void put_bool (int key, bool val); public bool get_bool (int key); public void put_object (int key, GLib.Object val); public unowned GLib.Object get_object (int key); public bool equals (Bundle? other); } [CCode (cprefix = "CbBundleHistory_", lower_case_cprefix = "cb_bundle_history_", cheader_filename = "CbBundleHistory.h")] public class BundleHistory : GLib.Object { public BundleHistory (); public void push (int v, Cb.Bundle? b); public int forward (); public int back (); public bool at_start (); public bool at_end (); public void remove_current (); public int get_current (); public unowned Cb.Bundle? get_current_bundle (); } [CCode (cprefix = "CbSnippetManager_", lower_case_cprefix = "cb_snippet_manager_", cheader_filename = "CbSnippetManager.h")] public class SnippetManager : GLib.Object { public SnippetManager (Sqlite.Database db); public unowned string get_snippet (string key); public bool has_snippet_n (string key, size_t key_len); public uint n_snippets (); public void query_snippets (GLib.HFunc func); public void set_snippet (string old_key, string key, string value); public void remove_snippet (string key); public void insert_snippet (string key, string value); } [CCode (cprefix = "CbMediaVideoWidget_", lower_case_cprefix = "cb_media_video_widget_", cheader_filename = "CbMediaVideoWidget.h")] public class MediaVideoWidget : Gtk.Stack { public MediaVideoWidget (Media media); public void start (); } [CCode (cprefix = "CbUserStream_", lower_case_cprefix = "cb_user_stream_", cheader_filename = "CbUserStream.h")] public class UserStream : GLib.Object { public UserStream (string name, bool b); public void register (MessageReceiver r); public void unregister (MessageReceiver r); public void push_data (string data); public void start (); public void stop (); public void set_proxy_data (string a, string b); public signal void interrupted (); public signal void resumed (); } [CCode (cprefix = "CbComposeJob_", lower_case_cprefix = "cb_compose_job_", cheader_filename = "CbComposeJob.h")] public class ComposeJob : GLib.Object { public signal void image_upload_progress (string a, double d); public signal void image_upload_finished (string a, string? b); public ComposeJob (Rest.Proxy proxy, Rest.Proxy proxy2, GLib.Cancellable cancellable); public void set_reply_id (int64 id); public void set_quoted_tweet (Cb.Tweet t); public void set_text (string s); public void upload_image_async (string p); public void abort_image_upload (string s); public async bool send_async (GLib.Cancellable c) throws GLib.Error; } [CCode (cprefix = "CbUserCompletionModel_", lower_case_cprefix = "cb_user_completion_model_", cheader_filename = "CbUserCompletionModel.h")] public class UserCompletionModel : GLib.Object, GLib.ListModel { public UserCompletionModel (); public void insert_infos (Cb.UserInfo[] infos); public void insert_items (Cb.UserIdentity[] ids); public void clear (); } [CCode (cprefix = "CbEmojiChooser_", lower_case_cprefix = "cb_emoji_chooser_", cheader_filename = "CbEmojiChooser.h")] public class EmojiChooser : Gtk.Box { public EmojiChooser (); public void populate (); public bool try_init (); public signal void emoji_picked (string emoji); } } corebird-1.7.4/vapi/libtl.vapi000066400000000000000000000017671324604713000162550ustar00rootroot00000000000000namespace Tl { [CCode (cprefix = "TL_ENT_", cheader_filename = "libtl/libtweetlength.h")] enum EntityType { TEXT = 1, HASHTAG, LINK, MENTION, WHITESPACE } [CCode (cprefix = "TlEntity_", lower_case_cprefix = "tl_entity_", cheader_filename = "libtl/libtweetlength.h")] struct Entity { EntityType type; string *start; size_t start_character_index; size_t length_in_characters; size_t length_in_bytes; } [CCode (cprefix = "tl_", lower_case_cprefix = "tl_", cheader_filename = "libtl/libtweetlength.h")] size_t count_characters (string input); [CCode (cprefix = "tl_", lower_case_cprefix = "tl_", cheader_filename = "libtl/libtweetlength.h", array_length_pos = 1)] Entity[]? extract_entities (string input, out size_t text_length); [CCode (cprefix = "tl_", lower_case_cprefix = "tl_", cheader_filename = "libtl/libtweetlength.h", array_length_pos = 1)] Entity[]? extract_entities_and_text (string input, out size_t text_length); } corebird-1.7.4/vapi/rest-0.7.vapi000066400000000000000000000260621324604713000164210ustar00rootroot00000000000000/* rest-0.7.vapi generated by vapigen, do not modify. */ [CCode (cprefix = "Rest", gir_namespace = "Rest", gir_version = "0.7", lower_case_cprefix = "rest_")] namespace Rest { [CCode (cheader_filename = "rest/oauth2-proxy.h", cname = "OAuth2Proxy", lower_case_cprefix = "oauth2_proxy_", type_id = "oauth2_proxy_get_type ()")] public class OAuth2Proxy : Rest.Proxy { [CCode (has_construct_function = false, type = "RestProxy*")] public OAuth2Proxy (string client_id, string auth_endpoint, string url_format, bool binding_required); public string build_login_url (string redirect_uri); public string build_login_url_full (string redirect_uri, GLib.HashTable extra_params); public static string extract_access_token (string url); public unowned string get_access_token (); public void set_access_token (string access_token); [CCode (has_construct_function = false, type = "RestProxy*")] public OAuth2Proxy.with_token (string client_id, string access_token, string auth_endpoint, string url_format, bool binding_required); public string access_token { get; set; } [NoAccessorMethod] public string auth_endpoint { owned get; construct; } [NoAccessorMethod] public string client_id { owned get; construct; } } [CCode (cheader_filename = "rest/oauth2-proxy-call.h", cname = "OAuth2ProxyCall", lower_case_cprefix = "oauth2_proxy_call_", type_id = "oauth2_proxy_call_get_type ()")] public class OAuth2ProxyCall : Rest.ProxyCall { [CCode (has_construct_function = false)] protected OAuth2ProxyCall (); } [CCode (cheader_filename = "rest/oauth-proxy.h", cname = "OAuthProxy", lower_case_cprefix = "oauth_proxy_", type_id = "oauth_proxy_get_type ()")] public class OAuthProxy : Rest.Proxy { [CCode (has_construct_function = false, type = "RestProxy*")] public OAuthProxy (string consumer_key, string consumer_secret, string url_format, bool binding_required); public async bool access_token_async (string function, string verifier, GLib.Cancellable? cancellable) throws GLib.Error; public bool auth_step (string function) throws GLib.Error; public bool auth_step_async (string function, [CCode (delegate_target_pos = 3.1, scope = "async")] Rest.OAuthProxyAuthCallback callback, GLib.Object weak_object) throws GLib.Error; public unowned string get_signature_host (); public unowned string get_token (); public unowned string get_token_secret (); public bool is_oauth10a (); public Rest.Proxy new_echo_proxy (string service_url, string url_format, bool binding_required); public async void request_token_async (string function, string callback_uri, GLib.Cancellable? cancellable) throws GLib.Error; public void set_signature_host (string signature_host); public void set_token (string token); public void set_token_secret (string token_secret); [CCode (has_construct_function = false, type = "RestProxy*")] public OAuthProxy.with_token (string consumer_key, string consumer_secret, string token, string token_secret, string url_format, bool binding_required); [NoAccessorMethod] public string consumer_key { owned get; construct; } [NoAccessorMethod] public string consumer_secret { owned get; construct; } public string signature_host { get; set; } [NoAccessorMethod] public Rest.OAuthSignatureMethod signature_method { get; set; } public string token { get; set; } public string token_secret { get; set; } } [CCode (cheader_filename = "rest/oauth-proxy-call.h", cname = "OAuthProxyCall", lower_case_cprefix = "oauth_proxy_call_", type_id = "oauth_proxy_call_get_type ()")] public class OAuthProxyCall : Rest.ProxyCall { [CCode (has_construct_function = false)] protected OAuthProxyCall (); public void parse_token_reponse (); public void parse_token_response (); } [CCode (cheader_filename = "rest/rest-param.h", ref_function = "rest_param_ref", type_id = "rest_param_get_type ()", unref_function = "rest_param_unref")] [Compact] public class Param { [CCode (has_construct_function = false)] public Param.full (global::string name, Rest.MemoryUse use, [CCode (array_length_cname = "length", array_length_pos = 3.5, array_length_type = "gsize")] uint8[] data, global::string content_type, global::string filename); public void* get_content (); public size_t get_content_length (); public unowned global::string get_content_type (); public unowned global::string get_file_name (); public unowned global::string get_name (); public bool is_string (); public Rest.Param @ref (); [CCode (has_construct_function = false)] public Param.string (global::string name, Rest.MemoryUse use, global::string string); public void unref (); [CCode (has_construct_function = false)] public Param.with_owner (global::string name, [CCode (array_length_cname = "length", array_length_pos = 2.5, array_length_type = "gsize")] uint8[] data, global::string content_type, global::string? filename, owned void* owner, GLib.DestroyNotify? owner_dnotify); } [CCode (cheader_filename = "rest/rest-params.h", has_type_id = false)] [Compact] public class Params { public void add (owned Rest.Param param); public bool are_strings (); public GLib.HashTable as_string_hash_table (); public void free (); public unowned Rest.Param? @get (string name); public void remove (string name); } [CCode (cheader_filename = "rest/rest-params.h", has_type_id = false)] [Compact] public class ParamsIter { public void init (Rest.Params @params); public bool next (string name, Rest.Param param); } [CCode (cheader_filename = "rest/rest-proxy.h", type_id = "rest_proxy_get_type ()")] public class Proxy : GLib.Object { [CCode (has_construct_function = false)] public Proxy (string url_format, bool binding_required); [Version (since = "0.7.92")] public void add_soup_feature (Soup.SessionFeature feature); public bool bind (...); public unowned string get_user_agent (); public virtual Rest.ProxyCall new_call (); public void set_user_agent (string user_agent); [NoAccessorMethod] public bool binding_required { get; set; } [NoAccessorMethod] public bool disable_cookies { get; construct; } [NoAccessorMethod] public string ssl_ca_file { owned get; set; } [NoAccessorMethod] public bool ssl_strict { get; set; } [NoAccessorMethod] public string url_format { owned get; set; } public string user_agent { get; set; } public virtual signal bool authenticate (Rest.ProxyAuth auth, bool retrying); } [CCode (cheader_filename = "rest/oauth-proxy-call.h,rest/oauth-proxy.h,rest/oauth2-proxy-call.h,rest/oauth2-proxy.h,rest/rest-enum-types.h,rest/rest-param.h,rest/rest-params.h,rest/rest-proxy-auth.h,rest/rest-proxy-call.h,rest/rest-proxy.h", type_id = "rest_proxy_auth_get_type ()")] public class ProxyAuth : GLib.Object { [CCode (has_construct_function = false)] protected ProxyAuth (); public void cancel (); public void pause (); public void unpause (); } [CCode (cheader_filename = "rest/rest-proxy-call.h", type_id = "rest_proxy_call_get_type ()")] public class ProxyCall : GLib.Object { [CCode (has_construct_function = false)] protected ProxyCall (); public void add_header (string header, string value); public void add_headers (...); public void add_param (string name, string value); public void add_param_full (owned Rest.Param param); public void add_params (...); public bool cancel (); public bool continuous ([CCode (delegate_target_pos = 2.1)] Rest.ProxyCallContinuousCallback callback, GLib.Object weak_object) throws GLib.Error; [Version (since = "0.7.92")] public unowned string get_function (); public unowned string get_method (); public unowned Rest.Params get_params (); public unowned string get_payload (); public int64 get_payload_length (); public GLib.HashTable get_response_headers (); public async bool invoke_async (GLib.Cancellable? cancellable) throws GLib.Error; public unowned string lookup_header (string header); public Rest.Param lookup_param (string name); public unowned string lookup_response_header (string header); [NoWrapper] public virtual bool prepare () throws GLib.Error; public void remove_header (string header); public void remove_param (string name); public bool run (out unowned GLib.MainLoop loop = null) throws GLib.Error; [CCode (cname = "rest_proxy_call_async")] public bool run_async ([CCode (delegate_target_pos = 2.1)] Rest.ProxyCallAsyncCallback callback, GLib.Object? weak_object = null) throws GLib.Error; public virtual bool serialize_params (out string content_type, out string content, out size_t content_len) throws GLib.Error; public void set_function (string function); public void set_method (string method); public bool upload ([CCode (delegate_target_pos = 2.1)] Rest.ProxyCallUploadCallback callback, GLib.Object weak_object) throws GLib.Error; [NoAccessorMethod] public Rest.Proxy proxy { owned get; construct; } } [CCode (cheader_filename = "rest/rest-param.h", cprefix = "REST_MEMORY_", has_type_id = false)] public enum MemoryUse { STATIC, TAKE, COPY } [CCode (cheader_filename = "rest/oauth-proxy.h", cname = "OAuthSignatureMethod", cprefix = "", type_id = "oauth_signature_method_get_type ()")] public enum OAuthSignatureMethod { PLAINTEXT, HMAC_SHA1 } [CCode (cheader_filename = "rest/rest-proxy-call.h", cprefix = "REST_PROXY_CALL_")] public errordomain ProxyCallError { FAILED; public static GLib.Quark quark (); } [CCode (cheader_filename = "rest/rest-proxy.h", cprefix = "REST_PROXY_ERROR_")] public errordomain ProxyError { CANCELLED, RESOLUTION, CONNECTION, SSL, IO, FAILED, HTTP_MULTIPLE_CHOICES, HTTP_MOVED_PERMANENTLY, HTTP_FOUND, HTTP_SEE_OTHER, HTTP_NOT_MODIFIED, HTTP_USE_PROXY, HTTP_THREEOHSIX, HTTP_TEMPORARY_REDIRECT, HTTP_BAD_REQUEST, HTTP_UNAUTHORIZED, HTTP_FOUROHTWO, HTTP_FORBIDDEN, HTTP_NOT_FOUND, HTTP_METHOD_NOT_ALLOWED, HTTP_NOT_ACCEPTABLE, HTTP_PROXY_AUTHENTICATION_REQUIRED, HTTP_REQUEST_TIMEOUT, HTTP_CONFLICT, HTTP_GONE, HTTP_LENGTH_REQUIRED, HTTP_PRECONDITION_FAILED, HTTP_REQUEST_ENTITY_TOO_LARGE, HTTP_REQUEST_URI_TOO_LONG, HTTP_UNSUPPORTED_MEDIA_TYPE, HTTP_REQUESTED_RANGE_NOT_SATISFIABLE, HTTP_EXPECTATION_FAILED, HTTP_INTERNAL_SERVER_ERROR, HTTP_NOT_IMPLEMENTED, HTTP_BAD_GATEWAY, HTTP_SERVICE_UNAVAILABLE, HTTP_GATEWAY_TIMEOUT, HTTP_HTTP_VERSION_NOT_SUPPORTED; public static GLib.Quark quark (); } [CCode (cheader_filename = "rest/oauth-proxy.h", instance_pos = 3.9)] public delegate void OAuthProxyAuthCallback (Rest.OAuthProxy proxy, GLib.Error? error, GLib.Object? weak_object); [CCode (cheader_filename = "rest/rest-proxy-call.h", instance_pos = 3.9)] public delegate void ProxyCallAsyncCallback (Rest.ProxyCall call, GLib.Error? error, GLib.Object? weak_object); [CCode (cheader_filename = "rest/rest-proxy-call.h", instance_pos = 5.9)] public delegate void ProxyCallContinuousCallback (Rest.ProxyCall call, string buf, size_t len, GLib.Error? error, GLib.Object? weak_object); [CCode (cheader_filename = "rest/rest-proxy-call.h", instance_pos = 5.9)] public delegate void ProxyCallUploadCallback (Rest.ProxyCall call, size_t total, size_t uploaded, GLib.Error? error, GLib.Object? weak_object); }