pax_global_header00006660000000000000000000000064147610332010014507gustar00rootroot0000000000000052 comment=bcb55acd8d169f301a35e2c0a5ee5d5d3ece9dcd Portfolio-1.0.2/000077500000000000000000000000001476103320100134645ustar00rootroot00000000000000Portfolio-1.0.2/.github/000077500000000000000000000000001476103320100150245ustar00rootroot00000000000000Portfolio-1.0.2/.github/workflows/000077500000000000000000000000001476103320100170615ustar00rootroot00000000000000Portfolio-1.0.2/.github/workflows/CI.yml000066400000000000000000000006761476103320100201100ustar00rootroot00000000000000on: push: branches: [master] pull_request: name: CI jobs: testing: runs-on: ubuntu-latest container: image: ghcr.io/andyholmes/flatter/gnome:47 options: --privileged steps: - name: Checkout uses: actions/checkout@v4 with: submodules: true - name: Build uses: andyholmes/flatter@main with: files: dev.tchx84.Portfolio.json run-tests: true Portfolio-1.0.2/CHANGELOG.md000066400000000000000000000136431476103320100153040ustar00rootroot00000000000000# Changelog ## [1.0.2] 2025-03-02 - Fixed missing metainfo entries by @AsciiWolf. - Added Chinese translation by @lujianhua. - Added Hindi translation by @Scrambled777. - Added Arabib translation by @x9a. - Added Hebrew translation by @yoseforb. - Added Turkish translation by @yakushabb. - Changed CI runner to flatter by @AsciiWolf. - Changed to GNOME 47 runtime for stability by @AsciiWolf. ## [1.0.1] 2024-02-12 - Fixed missing screenshots captions. - Fixed deprecated regex warnings. - Fixed showing XDG directories when did not exist. - Changed to GNOME 45 runtime for stability. ## [1.0.0] 2023-08-29 - Fixed icons not showing on GNOME Software. - Fixed trashing and restoring files with special characters. - Fixed progress report when moving files between same device. - Fixed swapping back while browsing files. - Changed to GTK 4 and Libadwaita. - Changed to GNOME 44 runtime for stability. ## [0.9.15] 2023-03-14 - Fixed detecting mount points from fstab. - Fixed opening files with special characters in name by @jwaataja. - Fixed Spanish translation by @carlosgonz0. - Added French translation by @rene-coty. - Added support for system-wide style preferences by @pabloyoyoista. - Added file permissions to the properties view. - Changed to regular icons by @AngelTomkins. - Changed to GNOME 43 runtime for stability. ## [0.9.14] 2022-03-19 - Fixed places visibility when adding or removing devices. - Fixed ghost files showing up after pasting failures. - Changed devices unlocking sequence for a much improved UX. - Changed places view to ignore system devices. - Removed unnecessary sandbox permissions. ## [0.9.13] 2022-02-17 - Fixed creating trash directories before it's needed. - Fixed crashing on Sway tiling Wayland compositor. - Fixed showing trash button for volumes with no trash folder. - Fixed handling of broken symlinks. - Fixed responsiveness when copying big files to slow devices. - Fixed inconsistent use of size unit, in favor of the decimal unit. - Added more progress details on paste operations dialogs. - Added support for mounting external devices. - Added support for unlocking encrypted devices. - Changed to GNOME 41 runtime for stability. ## [0.9.12] 2021-08-07 - Fixed trash permissions issues. - Fixed trash restore and empty buttons on sub-directories. ## [0.9.11] 2021-07-30 - Fixed issues in systems without glibc by @craftyguy. - Added German translation by @lqs01. - Added improved icon by @jimmac. - Added shortcut to Flatpak host filesystem in places. - Added notifications when removing devices. - Added support for freedesktop Trash. - Added cache to speed up directories loading times. - Added scrolling to the just-left directory when navigating back. - Added persistence to filter and sort options. ## [0.9.10] 2021-03-01 - Fixed all blockers to enable portrait mode. - Fixed a few cases where the selection mode would incorrectly activate. - Fixed stopping operations to immediately cancel the current operation. - Fixed categories and keywords to make Portfolio easier to find, by @philipzae. - Added Brazilian Portuguese translation by @rffontenelle. - Added home page with places and devices, designed by @nahuelwexd. - Added file properties viewer. - Added eject button for external storage devices. - Changed folder loading behavior to only display the progress bar if loading too slow. ## [0.9.9] 2021-02-10 - Fixed setting the proper selection mode while switching Places. - Added support for org.freedesktop.FileManager1 interface. - Added Czech translation by @AsciiWolf. - Added more tests, tests, tests. ## [0.9.8] 2021-02-04 - Fixed missing Exec field code in desktop file by @henry-nicolas. ## [0.9.7] 2021-02-03 - Fixed gradients in the application icon by @bertob. ## [0.9.6] 2021-01-25 - Added support for stopping paste and delete operations. - Added support for opening folders as CLI arguments. - Added more tests, tests, tests. ## [0.9.5] 2021-01-18 - Fixed repeated places, e.g. "System" and "root". - Fixed catching an edge-case permission error, while deleting. - Fixed delete worker for smoother feedback, while deleting. - Added visual feedback when opening files. - Added more tests, tests, tests. - Updated all translations by @vistaus, @cho2, @eson57 and @GNUuser. ## [0.9.4] 2021-01-11 - Fixed catching permissions errors while loading by @Avolpe. - Added more detailed feedback to pasting and deleting screens. - Added credits section to about "dialog". - Added more tests, tests, tests. ## [0.9.3] 2021-01-04 - Fixed regression where the last unselected row would unexpectedly activate. - Fixed to kick-back to HOME when the external drive is unmounted. - Fixed page will no longer do full reload after deleting only a few files. - Added sorting options to the menu. - Added floating button to go back to the top and to save multiple swipes. - Added enhanced icon by @bertob. - Added i18n support. - Added Spanish translation. - Added Swedish translation by @eson57. - Added Indonesian translation by @cho2. - Added Dutch translation by @vistaus. - Changed paste to behave like other commonly used file managers. ## [0.9.2] 2020-12-28 - Fixed performance issues with big folders. - Fixed styling issues with menu items. - Fixed about "dialog" headerbar "weirdness". - Fixed issues with OSK ocluding action bar. - Fixed icon not showing on Phosh. - Fixed notifications on missing permissions. - Added auto-scroll to newly created folder or renamed file. - Added no-results page for search. - Added show hidden files menu filter. - Added show OS root directory in places. - Added tests, tests, tests. ## [0.9.1] 2020-12-21 - Fixed assigning old paths to copied rows. ## [0.9.0] 2020-12-20 - Added support for browsing HOME and external volumes directories. - Added support for opening files. - Added support for multi-selection of files and directories. - Added support for moving, copying and pasting files and directories. - Added support for creating of new directories. - Added support for renaming files and directories. Portfolio-1.0.2/COPYING000066400000000000000000001045141476103320100145240ustar00rootroot00000000000000 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 . Portfolio-1.0.2/README.md000066400000000000000000000030651476103320100147470ustar00rootroot00000000000000# Portfolio ![CI](https://github.com/tchx84/Portfolio/workflows/CI/badge.svg) A minimalist file manager for those who want to use Linux mobile devices. ## Usage Tap to activate and long press to select, to browse, open, copy, move, delete, or edit your files. ## Get it [](https://flathub.org/apps/details/dev.tchx84.Portfolio) ## Build it yourself ``` git clone https://github.com/tchx84/Portfolio.git cd Portfolio flatpak install --user org.gnome.{Platform,Sdk}//47 flatpak-builder --user --force-clean --install build dev.tchx84.Portfolio.json flatpak run --branch=master dev.tchx84.Portfolio ``` This app is powered by [Builder](https://flathub.org/apps/details/org.gnome.Builder) and [Glade](https://flathub.org/apps/details/org.gnome.Glade). ## Contribute If you are interested in contributing to this project just send a pull request to [this](https://github.com/tchx84/Portfolio) repo. ## Disclaimer 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](COPYING) for more details. Portfolio-1.0.2/build-aux/000077500000000000000000000000001476103320100153565ustar00rootroot00000000000000Portfolio-1.0.2/build-aux/meson/000077500000000000000000000000001476103320100164775ustar00rootroot00000000000000Portfolio-1.0.2/build-aux/meson/postinstall.py000077500000000000000000000012141476103320100214260ustar00rootroot00000000000000#!/usr/bin/env python3 from os import environ, path from subprocess import call prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local') datadir = path.join(prefix, 'share') destdir = environ.get('DESTDIR', '') # Package managers set this so we don't need to run if not destdir: print('Updating icon cache...') call(['gtk-update-icon-cache', '-qtf', path.join(datadir, 'icons', 'hicolor')]) print('Updating desktop database...') call(['update-desktop-database', '-q', path.join(datadir, 'applications')]) print('Compiling GSettings schemas...') call(['glib-compile-schemas', path.join(datadir, 'glib-2.0', 'schemas')]) Portfolio-1.0.2/data/000077500000000000000000000000001476103320100143755ustar00rootroot00000000000000Portfolio-1.0.2/data/dev.tchx84.Portfolio-symbolic.svg000066400000000000000000000026301476103320100226110ustar00rootroot00000000000000 Portfolio-1.0.2/data/dev.tchx84.Portfolio.desktop.in000066400000000000000000000006361476103320100222550ustar00rootroot00000000000000[Desktop Entry] # TRANSLATORS: Don't translate this text Name=Portfolio Exec=dev.tchx84.Portfolio %f Terminal=false Type=Application GenericName=File Manager Comment=Manage files on the go Categories=GTK;Utility;FileManager; # TRANSLATORS: Don't translate this text Icon=dev.tchx84.Portfolio StartupNotify=true X-Purism-FormFactor=Mobile; Keywords=folder;manager;explore;disk;filesystem; MimeType=inode/directory; Portfolio-1.0.2/data/dev.tchx84.Portfolio.gschema.xml000066400000000000000000000007751476103320100224110ustar00rootroot00000000000000 false 'alphabetical' Portfolio-1.0.2/data/dev.tchx84.Portfolio.metainfo.xml.in000066400000000000000000000044171476103320100232060ustar00rootroot00000000000000 dev.tchx84.Portfolio CC0-1.0 GPL-3.0-or-later Portfolio Manage files on the go

A minimalist file manager for those who want to use Linux mobile devices.

Portfolio showing its home view https://raw.githubusercontent.com/tchx84/Portfolio/master/screenshots/en/1.png Portfolio showing directories and files https://raw.githubusercontent.com/tchx84/Portfolio/master/screenshots/en/2.png Portfolio showing file operations https://raw.githubusercontent.com/tchx84/Portfolio/master/screenshots/en/3.png Portfolio showing its file properties view https://raw.githubusercontent.com/tchx84/Portfolio/master/screenshots/en/4.png Portfolio showing its trash management view https://raw.githubusercontent.com/tchx84/Portfolio/master/screenshots/en/5.png Martin Abente Lahaye Martin Abente Lahaye martin.abente.lahaye@gmail.com dev.tchx84.Portfolio.desktop https://github.com/tchx84/Portfolio https://github.com/tchx84/Portfolio/issues https://github.com/tchx84/Portfolio https://github.com/tchx84/Portfolio/tree/master/po mobile
Portfolio-1.0.2/data/dev.tchx84.Portfolio.rules000066400000000000000000000007661476103320100213350ustar00rootroot00000000000000polkit.addRule(function(action, subject) { if ((action.id == "org.freedesktop.udisks2.filesystem-mount" || action.id == "org.freedesktop.udisks2.eject-media" || action.id == "org.freedesktop.udisks2.encrypted-unlock" || action.id == "org.freedesktop.udisks2.power-off-drive") && subject.active == true && subject.local == true && subject.isInGroup("wheel")) { return polkit.Result.YES; } return polkit.Result.NOT_HANDLED; }); Portfolio-1.0.2/data/dev.tchx84.Portfolio.service.in000066400000000000000000000001651476103320100222410ustar00rootroot00000000000000[D-BUS Service] Name=org.freedesktop.FileManager1 Exec=@prefix@/@bindir@/dev.tchx84.Portfolio --gapplication-service Portfolio-1.0.2/data/dev.tchx84.Portfolio.svg000066400000000000000000000135541476103320100210010ustar00rootroot00000000000000 Portfolio-1.0.2/data/icons/000077500000000000000000000000001476103320100155105ustar00rootroot00000000000000Portfolio-1.0.2/data/icons/dev.tchx84.Portfolio.Source.svg000066400000000000000000002322041476103320100233460ustar00rootroot00000000000000 Adwaita Icon Template image/svg+xml GNOME Design Team Adwaita Icon Template Hicolor Symbolic Portfolio-1.0.2/data/meson.build000066400000000000000000000035671476103320100165520ustar00rootroot00000000000000desktop_file = i18n.merge_file( input: 'dev.tchx84.Portfolio.desktop.in', output: 'dev.tchx84.Portfolio.desktop', type: 'desktop', po_dir: '../po', install: true, install_dir: join_paths(get_option('datadir'), 'applications') ) desktop_utils = find_program('desktop-file-validate', required: false) if desktop_utils.found() test('Validate desktop file', desktop_utils, args: [desktop_file] ) endif appstream_file = i18n.merge_file( input: 'dev.tchx84.Portfolio.metainfo.xml.in', output: 'dev.tchx84.Portfolio.metainfo.xml', po_dir: '../po', install: true, install_dir: join_paths(get_option('datadir'), 'metainfo') ) install_data('dev.tchx84.Portfolio.svg', install_dir: join_paths(get_option('datadir'), 'icons/hicolor/scalable/apps') ) install_data('dev.tchx84.Portfolio-symbolic.svg', install_dir: join_paths(get_option('datadir'), 'icons/hicolor/symbolic/apps') ) appstreamcli = find_program('appstreamcli', required: false) if appstreamcli.found() test('Validate appstream file', appstreamcli, args: ['validate', '--no-net', '--explain', appstream_file] ) endif install_data('dev.tchx84.Portfolio.gschema.xml', install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas') ) install_data('dev.tchx84.Portfolio.rules', install_dir: join_paths(get_option('datadir'), 'polkit-1/rules.d'), install_mode: 'rw-r--r--' ) conf = configuration_data() conf.set('prefix', get_option('prefix')) conf.set('bindir', get_option('bindir')) configure_file( input: 'dev.tchx84.Portfolio.service.in', output: get_option('service_filename'), configuration: conf, install_dir: join_paths(get_option('datadir'), 'dbus-1', 'services') ) compile_schemas = find_program('glib-compile-schemas', required: false) if compile_schemas.found() test('Validate schema file', compile_schemas, args: ['--strict', '--dry-run', meson.current_source_dir()] ) endif Portfolio-1.0.2/dev.tchx84.Portfolio.json000066400000000000000000000023351476103320100202350ustar00rootroot00000000000000{ "app-id" : "dev.tchx84.Portfolio", "runtime" : "org.gnome.Platform", "runtime-version" : "47", "sdk" : "org.gnome.Sdk", "separate-locales": false, "command" : "dev.tchx84.Portfolio", "finish-args" : [ "--share=ipc", "--socket=fallback-x11", "--socket=wayland", "--device=dri", "--filesystem=home", "--filesystem=/media", "--filesystem=/run/media", "--system-talk-name=org.freedesktop.UDisks2", "--own-name=org.freedesktop.FileManager1" ], "cleanup" : [ "/include", "/lib/pkgconfig", "/man", "/share/doc", "/share/gtk-doc", "/share/man", "/share/pkgconfig", "*.la", "*.a" ], "modules" : [ "tests/requirements.json", { "name" : "portfolio", "builddir" : true, "buildsystem" : "meson", "config-opts": [ "-Dservice_filename=org.freedesktop.FileManager1.service" ], "sources" : [ { "type" : "git", "path" : ".", "branch": "HEAD" } ] } ] } Portfolio-1.0.2/meson.build000066400000000000000000000004421476103320100156260ustar00rootroot00000000000000project('portfolio', version: '1.0.2', meson_version: '>= 0.50.0', default_options: [ 'warning_level=2', ], ) i18n = import('i18n') subdir('data') subdir('src') subdir('po') subdir('tests') meson.add_install_script('build-aux/meson/postinstall.py') Portfolio-1.0.2/meson_options.txt000066400000000000000000000002201476103320100171130ustar00rootroot00000000000000option('service_filename', type : 'string', value : 'dev.tchx84.Portfolio.service') option('run_service_tests', type : 'boolean', value : true) Portfolio-1.0.2/po/000077500000000000000000000000001476103320100141025ustar00rootroot00000000000000Portfolio-1.0.2/po/LINGUAS000066400000000000000000000000551476103320100151270ustar00rootroot00000000000000ar cs de es fr he hi id nl pt_BR sv tr zh_CN Portfolio-1.0.2/po/POTFILES000066400000000000000000000004121476103320100152470ustar00rootroot00000000000000data/dev.tchx84.Portfolio.desktop.in data/dev.tchx84.Portfolio.metainfo.xml.in data/dev.tchx84.Portfolio.gschema.xml src/about.ui src/menu.ui src/placeholder.ui src/properties.ui src/window.ui src/files.py src/passphrase.py src/places.py src/window.py src/worker.py Portfolio-1.0.2/po/ar.po000066400000000000000000000213501476103320100150450ustar00rootroot00000000000000# Arabic translations for portfolio package. # Copyright (C) 2024 THE portfolio'S COPYRIGHT HOLDER # This file is distributed under the same license as the portfolio package. # Ahmed Mohammed , 2024. # msgid "" msgstr "" "Project-Id-Version: portfolio\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-09 16:34-0300\n" "PO-Revision-Date: 2024-08-09 03:03+0300\n" "Last-Translator: Ahmed Mohammed \n" "Language-Team: none\n" "Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 2.3.1\n" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.desktop.in:4 #: data/dev.tchx84.Portfolio.metainfo.xml.in:11 src/about.ui:30 msgid "Portfolio" msgstr "بورتÙوليو" #: data/dev.tchx84.Portfolio.desktop.in:8 msgid "File Manager" msgstr "مدير Ù…Ù„ÙØ§Øª" #: data/dev.tchx84.Portfolio.desktop.in:9 #: data/dev.tchx84.Portfolio.metainfo.xml.in:12 msgid "Manage files on the go" msgstr "أدر Ø§Ù„Ù…Ù„ÙØ§Øª أثناء تنقلك" #: data/dev.tchx84.Portfolio.desktop.in:15 msgid "folder;manager;explore;disk;filesystem;" msgstr "مجلد;مدير;استكشÙ;مستكشÙ;قرص;" #: data/dev.tchx84.Portfolio.metainfo.xml.in:14 src/about.ui:58 msgid "" "A minimalist file manager for those who want to use Linux mobile devices." msgstr "مدير Ù…Ù„ÙØ§Øª بسيط لمن يرغب باستخدام جوال لينكس محمول." #: data/dev.tchx84.Portfolio.metainfo.xml.in:20 msgid "Portfolio showing its home view" msgstr "بورتÙوليو ÙŠÙØ¸Ù‡Ø± عرضه الرئيسي" #: data/dev.tchx84.Portfolio.metainfo.xml.in:24 msgid "Portfolio showing directories and files" msgstr "بورتÙوليو ÙŠÙØ¸Ù‡Ø± الدلائل ÙˆØ§Ù„Ù…Ù„ÙØ§Øª" #: data/dev.tchx84.Portfolio.metainfo.xml.in:28 msgid "Portfolio showing file operations" msgstr "بورتÙوليو ÙŠÙØ¸Ù‡Ø± عمليات Ø§Ù„Ù…Ù„ÙØ§Øª" #: data/dev.tchx84.Portfolio.metainfo.xml.in:32 msgid "Portfolio showing its file properties view" msgstr "بورتÙوليو ÙŠÙØ¸Ù‡Ø± عرض خصائص الملÙ" #: data/dev.tchx84.Portfolio.metainfo.xml.in:36 msgid "Portfolio showing its trash management view" msgstr "بورتÙوليو ÙŠÙØ¸Ù‡Ø± عرض إدارة المهملات" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.metainfo.xml.in:41 msgid "Martin Abente Lahaye" msgstr "" #: src/about.ui:73 msgid "Website" msgstr "موقع Ø§Ù„ÙˆÙØ¨" #: src/about.ui:102 msgid "" "This program comes with absolutely no warranty. See the GNU General Public License, version 3 or " "later for details." msgstr "" "هذا البرنامج يأتي دون ضمان. طالع رخصة جنو العمومية، الإصدار الثالث أو ما يليها لمزيد من " "المعلومات." #: src/about.ui:118 msgid "" "Portfolio logo is a modifed version of Jorgen Carlberg's work. The original " "version can be found here , licensed under CC BY 3.0 ." msgstr "" "شعار بورتÙوليو هو نسخة معدلة لعمل جورغن ÙƒØ§Ø±Ù„Ø¨ÙØ±Øº. النسخة الأصلية Ù…ØªÙˆÙØ±Ø© هنا، وهو " "مرخص تحت رخصة المشاع الإبداعي نَسب Ø§Ù„Ù…ÙØµÙ†Ù‘ÙŽÙ 3.0 غير موطَّنة." #: src/about.ui:139 msgid "Created by" msgstr "أنشئ بواسطة" #: src/about.ui:166 msgid "Artwork by" msgstr "العمل الÙني بواسطة" #: src/about.ui:194 msgid "Translated by" msgstr "ترجمت بواسطة" #: src/about.ui:227 msgid "Contributions by" msgstr "المساهمات بواسطة" #: src/menu.ui:21 msgid "Filter" msgstr "مرشح" #: src/menu.ui:34 msgid "Show Hidden Files" msgstr "اعرض Ø§Ù„Ù…Ù„ÙØ§Øª المخÙية" #: src/menu.ui:57 msgid "Sort" msgstr "ÙØ±Ø²" #: src/menu.ui:69 msgid "A-Z" msgstr "Ø£-ÙŠ" #: src/menu.ui:84 msgid "Last Modified" msgstr "آخر تعديل" #: src/menu.ui:109 msgid "Help" msgstr "مساعدة" #: src/menu.ui:118 msgid "About Portfolio" msgstr "عَنْ بورتÙوليو" #: src/placeholder.ui:16 msgid "No files found" msgstr "لم يتم العثور على Ù…Ù„ÙØ§Øª" #: src/properties.ui:18 msgid "Name" msgstr "الاسم" #: src/properties.ui:49 msgid "Location" msgstr "المكان" #: src/properties.ui:80 msgid "Type" msgstr "النوع" #: src/properties.ui:111 msgid "Size" msgstr "الحجم" #: src/properties.ui:150 msgid "Created" msgstr "Ø£ÙÙ†Ø´ÙØ¦" #: src/properties.ui:181 msgid "Modified" msgstr "Ø¹ÙØ¯Ù‘ÙÙ„" #: src/properties.ui:212 msgid "Accessed" msgstr "وًصÙÙ„ إليه" #: src/properties.ui:251 msgid "Owner" msgstr "المالك" #: src/properties.ui:282 msgid "Group" msgstr "المجموعة" #: src/properties.ui:313 msgid "Owner can" msgstr "يستطيع المالك" #: src/properties.ui:344 msgid "Group can" msgstr "تستطيع المجموعة" #: src/properties.ui:375 msgid "Others can" msgstr "يستطيع الآخرون" #: src/window.ui:648 msgid "Properties" msgstr "الخصائي" #: src/window.ui:699 msgid "About" msgstr "عَنْ" #: src/files.py:292 src/window.py:491 #, python-format msgid "%s already exists" msgstr "%s موجود Ø¨Ø§Ù„ÙØ¹Ù„" #: src/files.py:410 msgid "New Folder" msgstr "مجلد جديد" #: src/passphrase.py:68 msgid "Sorry, that didn't work" msgstr "خطأ! لم يعمل هذا" #: src/places.py:52 msgid "Trash" msgstr "المهملات" #: src/places.py:89 msgid "Places" msgstr "الأماكن" #: src/places.py:93 msgid "Devices" msgstr "الأجهزة" #: src/places.py:103 msgid "Home" msgstr "المنزل" #: src/places.py:155 msgid "System" msgstr "النظام" #: src/places.py:163 msgid "Host" msgstr "المٌضÙÙŠÙ" #: src/places.py:177 msgid "No places found" msgstr "لا أماكن موجودة" #: src/window.py:501 msgid "No permissions on this directory" msgstr "لا صلاحيات على هذا الدليل" #: src/window.py:514 msgid "Opening" msgstr "ÙŠÙŽÙØªØ­" #: src/window.py:532 src/window.py:1075 src/window.py:1085 #, python-format msgid "Could not open %s" msgstr "تعذر ÙØªØ­ %s" #: src/window.py:551 src/window.py:580 msgid "Loading" msgstr "يٌحمل" #: src/window.py:580 #, python-format msgid "Could not load %s" msgstr "تعذر تحميل %s" #: src/window.py:630 src/window.py:941 #, python-format msgid "these %d files" msgstr "هؤلاء Ù…Ù„ÙØ§Øª %d" #: src/window.py:632 src/window.py:943 #, python-format msgid "Delete %s permanently?" msgstr "حذ٠%s نهائيًا?" #: src/window.py:652 #, python-format msgid "%s will be moved" msgstr "%s سينقل" #: src/window.py:654 #, python-format msgid "%d files will be moved" msgstr "Ù…Ù„ÙØ§Øª %d ستنقل" #: src/window.py:669 #, python-format msgid "%s will be copied" msgstr "%s سينسخ" #: src/window.py:671 #, python-format msgid "%d files will be copied" msgstr "Ù…Ù„ÙØ§Øª %d ستنسخ" #: src/window.py:695 src/window.py:874 msgid "Files will be overwritten, proceed?" msgstr "سيتم طمس Ø§Ù„Ù…Ù„ÙØ§ØªØŒ هل تود المتابعة؟" #: src/window.py:710 msgid "Pasting" msgstr "يلصق" #: src/window.py:734 #, python-format msgid "%s of %s" msgstr "%s لـ %s" #: src/window.py:749 #, python-format msgid "Could not paste %s" msgstr "تعذر لصق %s" #: src/window.py:801 src/window.py:969 msgid "Deleting" msgstr "يحذÙ" #: src/window.py:826 src/window.py:993 #, python-format msgid "Could not delete %s" msgstr "تعذر حذ٠%s" #: src/window.py:849 msgid "Stopping" msgstr "يتوقÙ" #: src/window.py:897 msgid "Restoring" msgstr "يسترجع" #: src/window.py:926 #, python-format msgid "Could not restore %s" msgstr "تعذر استرجاع %s" #: src/window.py:1006 msgid "Removing device, please wait" msgstr "جار٠إزالة الجهاز, انتظر من ÙØ¶Ù„Ùƒ" #: src/window.py:1010 msgid "Device can be removed" msgstr "يمكن ÙØµÙ„ الجهاز" #: src/window.py:1022 msgid "Device is busy, can't be removed" msgstr "الجهاز مشغول, لا يمكن إزالته حاليًا" #: src/worker.py:512 msgid "read" msgstr "قراءة" #: src/worker.py:514 msgid "write" msgstr "كتابة" #: src/worker.py:516 msgid "execute" msgstr "تنÙيذ" #: src/worker.py:521 msgid "do nothing" msgstr "لا ÙŠÙØ¹Ù„ شيئًا" #: src/worker.py:601 msgid "Calculating..." msgstr "يحسب..." Portfolio-1.0.2/po/cs.po000066400000000000000000000203001476103320100150420ustar00rootroot00000000000000# Czech translation for portfolio package. # Copyright (C) 2021 THE portfolio'S COPYRIGHT HOLDER # This file is distributed under the same license as the portfolio package. # # Daniel Rusek , 2021. # msgid "" msgstr "" "Project-Id-Version: portfolio\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-09 16:34-0300\n" "PO-Revision-Date: 2024-02-10 02:32+0100\n" "Last-Translator: Daniel Rusek \n" "Language-Team: none\n" "Language: cs\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" "X-Generator: Poedit 3.4.2\n" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.desktop.in:4 #: data/dev.tchx84.Portfolio.metainfo.xml.in:11 src/about.ui:30 msgid "Portfolio" msgstr "" #: data/dev.tchx84.Portfolio.desktop.in:8 msgid "File Manager" msgstr "Správce souborů" #: data/dev.tchx84.Portfolio.desktop.in:9 #: data/dev.tchx84.Portfolio.metainfo.xml.in:12 msgid "Manage files on the go" msgstr "Spravujte soubory na cestách" #: data/dev.tchx84.Portfolio.desktop.in:15 msgid "folder;manager;explore;disk;filesystem;" msgstr "složka;správce;prohlížet;disk;souborový systém;" #: data/dev.tchx84.Portfolio.metainfo.xml.in:14 src/about.ui:58 msgid "" "A minimalist file manager for those who want to use Linux mobile devices." msgstr "" "Minimalistický správce souborů pro ty, kteří chtÄ›jí používat linuxová " "mobilní zařízení." #: data/dev.tchx84.Portfolio.metainfo.xml.in:20 msgid "Portfolio showing its home view" msgstr "Portfolio zobrazující domovský pohled" #: data/dev.tchx84.Portfolio.metainfo.xml.in:24 msgid "Portfolio showing directories and files" msgstr "Portfolio zobrazující adresáře a soubory" #: data/dev.tchx84.Portfolio.metainfo.xml.in:28 msgid "Portfolio showing file operations" msgstr "Portfolio zobrazující operace se soubory" #: data/dev.tchx84.Portfolio.metainfo.xml.in:32 msgid "Portfolio showing its file properties view" msgstr "Portfolio zobrazující pohled na vlastnosti souboru" #: data/dev.tchx84.Portfolio.metainfo.xml.in:36 msgid "Portfolio showing its trash management view" msgstr "Portfolio zobrazující pohled na správu koÅ¡e" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.metainfo.xml.in:41 msgid "Martin Abente Lahaye" msgstr "" #: src/about.ui:73 msgid "Website" msgstr "Webová stránka" #: src/about.ui:102 msgid "" "This program comes with absolutely no warranty. See the GNU General Public License, version 3 or " "later for details." msgstr "" "Tento program je šířen bez jakékoliv záruky. Pro bližší informace viz GNU General Public " "License, verze 3 nebo pozdÄ›jší." #: src/about.ui:118 msgid "" "Portfolio logo is a modifed version of Jorgen Carlberg's work. The original " "version can be found here , licensed under CC BY 3.0 ." msgstr "" "Portfolio logo je upravená verze práce Jorgena Carlberga. Původní verze je k " "nalezení " "zde , licencována pod CC BY 3.0 ." #: src/about.ui:139 msgid "Created by" msgstr "VytvoÅ™ili" #: src/about.ui:166 msgid "Artwork by" msgstr "Grafika od" #: src/about.ui:194 msgid "Translated by" msgstr "PÅ™eložili" #: src/about.ui:227 msgid "Contributions by" msgstr "PříspÄ›vky od" #: src/menu.ui:21 msgid "Filter" msgstr "Filtr" #: src/menu.ui:34 msgid "Show Hidden Files" msgstr "Zobrazit skryté soubory" #: src/menu.ui:57 msgid "Sort" msgstr "Řadit podle" #: src/menu.ui:69 msgid "A-Z" msgstr "A-Ž" #: src/menu.ui:84 msgid "Last Modified" msgstr "Naposledy zmÄ›nÄ›no" #: src/menu.ui:109 msgid "Help" msgstr "NápovÄ›da" #: src/menu.ui:118 msgid "About Portfolio" msgstr "O aplikaci Portfolio" #: src/placeholder.ui:16 msgid "No files found" msgstr "Nenalezeny žádné soubory" #: src/properties.ui:18 msgid "Name" msgstr "Název" #: src/properties.ui:49 msgid "Location" msgstr "UmístÄ›ní" #: src/properties.ui:80 msgid "Type" msgstr "Typ" #: src/properties.ui:111 msgid "Size" msgstr "Velikost" #: src/properties.ui:150 msgid "Created" msgstr "VytvoÅ™eno" #: src/properties.ui:181 msgid "Modified" msgstr "ZmÄ›nÄ›no" #: src/properties.ui:212 msgid "Accessed" msgstr "Přístup" #: src/properties.ui:251 msgid "Owner" msgstr "Vlastník" #: src/properties.ui:282 msgid "Group" msgstr "Skupina" #: src/properties.ui:313 msgid "Owner can" msgstr "Vlastník může" #: src/properties.ui:344 msgid "Group can" msgstr "Skupina může" #: src/properties.ui:375 msgid "Others can" msgstr "Ostatní mohou" #: src/window.ui:648 msgid "Properties" msgstr "Vlastnosti" #: src/window.ui:699 msgid "About" msgstr "O aplikaci" #: src/files.py:292 src/window.py:491 #, python-format msgid "%s already exists" msgstr "%s již existuje" #: src/files.py:410 msgid "New Folder" msgstr "Nová složka" #: src/passphrase.py:68 msgid "Sorry, that didn't work" msgstr "Bohužel, ale nefunguje to" #: src/places.py:52 msgid "Trash" msgstr "KoÅ¡" #: src/places.py:89 msgid "Places" msgstr "Místa" #: src/places.py:93 msgid "Devices" msgstr "Zařízení" #: src/places.py:103 msgid "Home" msgstr "Domov" #: src/places.py:155 msgid "System" msgstr "Systém" #: src/places.py:163 msgid "Host" msgstr "Hostitel" #: src/places.py:177 msgid "No places found" msgstr "Nenalezena žádná umístÄ›ní" #: src/window.py:501 msgid "No permissions on this directory" msgstr "Žádná oprávnÄ›ní na tomto adresáři" #: src/window.py:514 msgid "Opening" msgstr "Otevírá se" #: src/window.py:532 src/window.py:1075 src/window.py:1085 #, python-format msgid "Could not open %s" msgstr "Nelze otevřít %s" #: src/window.py:551 src/window.py:580 msgid "Loading" msgstr "NaÄítá se" #: src/window.py:580 #, python-format msgid "Could not load %s" msgstr "Nelze naÄíst %s" #: src/window.py:630 src/window.py:941 #, python-format msgid "these %d files" msgstr "tÄ›chto %d souborů" #: src/window.py:632 src/window.py:943 #, python-format msgid "Delete %s permanently?" msgstr "Trvale smazat %s?" #: src/window.py:652 #, python-format msgid "%s will be moved" msgstr "%s bude pÅ™esunuto" #: src/window.py:654 #, python-format msgid "%d files will be moved" msgstr "%d souborů bude pÅ™esunuto" #: src/window.py:669 #, python-format msgid "%s will be copied" msgstr "%s bude zkopírováno" #: src/window.py:671 #, python-format msgid "%d files will be copied" msgstr "%d souborů bude zkopírováno" #: src/window.py:695 src/window.py:874 msgid "Files will be overwritten, proceed?" msgstr "Soubory budou pÅ™epsány, pokraÄovat?" #: src/window.py:710 msgid "Pasting" msgstr "Vkládá se" #: src/window.py:734 #, python-format msgid "%s of %s" msgstr "%s z %s" #: src/window.py:749 #, python-format msgid "Could not paste %s" msgstr "Nelze vložit %s" #: src/window.py:801 src/window.py:969 msgid "Deleting" msgstr "Mazání" #: src/window.py:826 src/window.py:993 #, python-format msgid "Could not delete %s" msgstr "Nelze smazat %s" #: src/window.py:849 msgid "Stopping" msgstr "Zastavování" #: src/window.py:897 msgid "Restoring" msgstr "Obnovuje se" #: src/window.py:926 #, python-format msgid "Could not restore %s" msgstr "Nelze obnovit %s" #: src/window.py:1006 msgid "Removing device, please wait" msgstr "Odebírá se zařízení, prosím Äekejte" #: src/window.py:1010 msgid "Device can be removed" msgstr "Zařízení může být odebráno" #: src/window.py:1022 msgid "Device is busy, can't be removed" msgstr "Zařízení je zaneprázdnÄ›no, nelze jej odebrat" #: src/worker.py:512 msgid "read" msgstr "Äíst" #: src/worker.py:514 msgid "write" msgstr "zapisovat" #: src/worker.py:516 msgid "execute" msgstr "spouÅ¡tÄ›t" #: src/worker.py:521 msgid "do nothing" msgstr "nedÄ›lat nic" #: src/worker.py:601 msgid "Calculating..." msgstr "PoÄítá se..." Portfolio-1.0.2/po/de.po000066400000000000000000000200261476103320100150320ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the portfolio package. # FIRST AUTHOR , YEAR. # liquidsky42 , 2021. # Jan , 2021-2022. # msgid "" msgstr "" "Project-Id-Version: portfolio\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-09 16:34-0300\n" "PO-Revision-Date: 2022-02-05 08:35+0100\n" "Last-Translator: Jan \n" "Language-Team: German <->\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" "X-Generator: Gtranslator 40.0\n" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.desktop.in:4 #: data/dev.tchx84.Portfolio.metainfo.xml.in:11 src/about.ui:30 msgid "Portfolio" msgstr "Portfolio" #: data/dev.tchx84.Portfolio.desktop.in:8 msgid "File Manager" msgstr "Dateiverwaltung" #: data/dev.tchx84.Portfolio.desktop.in:9 #: data/dev.tchx84.Portfolio.metainfo.xml.in:12 msgid "Manage files on the go" msgstr "Verwalte Dateien von unterwegs" #: data/dev.tchx84.Portfolio.desktop.in:15 msgid "folder;manager;explore;disk;filesystem;" msgstr "Ordner,Verwaltung,Erkunden,Speicher,Dateisystem" #: data/dev.tchx84.Portfolio.metainfo.xml.in:14 src/about.ui:58 msgid "" "A minimalist file manager for those who want to use Linux mobile devices." msgstr "Ein minimale Dateiverwaltung für die Nutzung auf mobilen Geräten." #: data/dev.tchx84.Portfolio.metainfo.xml.in:20 msgid "Portfolio showing its home view" msgstr "" #: data/dev.tchx84.Portfolio.metainfo.xml.in:24 msgid "Portfolio showing directories and files" msgstr "" #: data/dev.tchx84.Portfolio.metainfo.xml.in:28 msgid "Portfolio showing file operations" msgstr "" #: data/dev.tchx84.Portfolio.metainfo.xml.in:32 msgid "Portfolio showing its file properties view" msgstr "" #: data/dev.tchx84.Portfolio.metainfo.xml.in:36 msgid "Portfolio showing its trash management view" msgstr "" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.metainfo.xml.in:41 msgid "Martin Abente Lahaye" msgstr "Martin Abente Lahaye" #: src/about.ui:73 msgid "Website" msgstr "Webseite" #: src/about.ui:102 msgid "" "This program comes with absolutely no warranty. See the GNU General Public License, version 3 or " "later for details." msgstr "" "Dieses Programm kommt OHNE JEDWEDE GARANTIE.\n" "Besuchen Sie GNU " "General Public License, version 3 or later für weitere Informationen." #: src/about.ui:118 msgid "" "Portfolio logo is a modifed version of Jorgen Carlberg's work. The original " "version can be found here , licensed under CC BY 3.0 ." msgstr "" "Das Portfoliologo ist eine modifizierte Variante von Jorgen Carlbergs " "Arbeit. Die Orginalversion kann hier gefunden werden. Lizenziert unter CC BY 3.0 ." #: src/about.ui:139 msgid "Created by" msgstr "Erstellt von" #: src/about.ui:166 msgid "Artwork by" msgstr "Künstlerische Darstellung von" #: src/about.ui:194 msgid "Translated by" msgstr "Übersetzung von" #: src/about.ui:227 msgid "Contributions by" msgstr "Erstellt von" #: src/menu.ui:21 msgid "Filter" msgstr "Filter" #: src/menu.ui:34 msgid "Show Hidden Files" msgstr "Verborgene Dateien anzeigen" #: src/menu.ui:57 msgid "Sort" msgstr "Sortieren" #: src/menu.ui:69 msgid "A-Z" msgstr "A-Z" #: src/menu.ui:84 msgid "Last Modified" msgstr "Zuletzt Geändert" #: src/menu.ui:109 msgid "Help" msgstr "Hilfe" #: src/menu.ui:118 msgid "About Portfolio" msgstr "Über Portfolio" #: src/placeholder.ui:16 msgid "No files found" msgstr "Keine Dateien gefunden" #: src/properties.ui:18 msgid "Name" msgstr "Name" #: src/properties.ui:49 msgid "Location" msgstr "Ort" #: src/properties.ui:80 msgid "Type" msgstr "Typ" #: src/properties.ui:111 msgid "Size" msgstr "Größe" #: src/properties.ui:150 msgid "Created" msgstr "Erstellt" #: src/properties.ui:181 msgid "Modified" msgstr "Geändert" #: src/properties.ui:212 msgid "Accessed" msgstr "Zugegriffen" #: src/properties.ui:251 msgid "Owner" msgstr "Besitzer" #: src/properties.ui:282 msgid "Group" msgstr "Gruppe" #: src/properties.ui:313 msgid "Owner can" msgstr "Besitzer kann" #: src/properties.ui:344 msgid "Group can" msgstr "Gruppe kann" #: src/properties.ui:375 msgid "Others can" msgstr "Andere können" #: src/window.ui:648 msgid "Properties" msgstr "Eigenschaften" #: src/window.ui:699 msgid "About" msgstr "Über" #: src/files.py:292 src/window.py:491 #, python-format msgid "%s already exists" msgstr "%s ist bereits vorhanden." #: src/files.py:410 msgid "New Folder" msgstr "Neuer Ordner" #: src/passphrase.py:68 msgid "Sorry, that didn't work" msgstr "Entschuldigung, das hat nicht geklappt" #: src/places.py:52 msgid "Trash" msgstr "Papierkorb" #: src/places.py:89 msgid "Places" msgstr "Orte" #: src/places.py:93 msgid "Devices" msgstr "Geräte" #: src/places.py:103 msgid "Home" msgstr "Persönlicher Ordner" #: src/places.py:155 msgid "System" msgstr "Rechner" #: src/places.py:163 msgid "Host" msgstr "Host" #: src/places.py:177 msgid "No places found" msgstr "Kein Ort gefunden" #: src/window.py:501 msgid "No permissions on this directory" msgstr "Keine Rechte für diesen Ordner" #: src/window.py:514 msgid "Opening" msgstr "Öffne" #: src/window.py:532 src/window.py:1075 src/window.py:1085 #, python-format msgid "Could not open %s" msgstr "Kann %s nicht öffnen" #: src/window.py:551 src/window.py:580 msgid "Loading" msgstr "Lade" #: src/window.py:580 #, python-format msgid "Could not load %s" msgstr "Kann %s nicht laden" #: src/window.py:630 src/window.py:941 #, python-format msgid "these %d files" msgstr "diese %d Dateien" #: src/window.py:632 src/window.py:943 #, python-format msgid "Delete %s permanently?" msgstr "%s unwideruflich löschen?" #: src/window.py:652 #, python-format msgid "%s will be moved" msgstr "%s wird verschoben" #: src/window.py:654 #, python-format msgid "%d files will be moved" msgstr "%d Dateien werden verschoben" #: src/window.py:669 #, python-format msgid "%s will be copied" msgstr "%s wird kopiert" #: src/window.py:671 #, python-format msgid "%d files will be copied" msgstr "%d Dateien werden kopiert" #: src/window.py:695 src/window.py:874 msgid "Files will be overwritten, proceed?" msgstr "Dateien werden überschrieben, Fortfahren?" #: src/window.py:710 msgid "Pasting" msgstr "Einfügen" #: src/window.py:734 #, python-format msgid "%s of %s" msgstr "%s von %s" #: src/window.py:749 #, python-format msgid "Could not paste %s" msgstr "%s kann nicht eingefügt werden" #: src/window.py:801 src/window.py:969 msgid "Deleting" msgstr "Löschen" #: src/window.py:826 src/window.py:993 #, python-format msgid "Could not delete %s" msgstr "Kann %s nicht löschen" #: src/window.py:849 msgid "Stopping" msgstr "Halte an" #: src/window.py:897 msgid "Restoring" msgstr "Wiederherstellen" #: src/window.py:926 #, python-format msgid "Could not restore %s" msgstr "%s kann nicht wiederhergestellt werden" #: src/window.py:1006 msgid "Removing device, please wait" msgstr "Gerät wird entfernt, bitte warten" #: src/window.py:1010 msgid "Device can be removed" msgstr "Gerät kann nun entfernt werden" #: src/window.py:1022 msgid "Device is busy, can't be removed" msgstr "Gerät ist beschäftigt, kann nicht entfernt werden" #: src/worker.py:512 msgid "read" msgstr "lesen" #: src/worker.py:514 msgid "write" msgstr "schreiben" #: src/worker.py:516 msgid "execute" msgstr "ausführen" #: src/worker.py:521 msgid "do nothing" msgstr "nichts tun" #: src/worker.py:601 msgid "Calculating..." msgstr "Berrechne..." Portfolio-1.0.2/po/es.po000066400000000000000000000200011476103320100150420ustar00rootroot00000000000000# Spanish translations for portfolio package. # Copyright (C) 2020 THE portfolio'S COPYRIGHT HOLDER # This file is distributed under the same license as the portfolio package. # Automatically generated, 2020. # msgid "" msgstr "" "Project-Id-Version: portfolio\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-09 16:34-0300\n" "PO-Revision-Date: 2020-12-30 12:55-0300\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.desktop.in:4 #: data/dev.tchx84.Portfolio.metainfo.xml.in:11 src/about.ui:30 msgid "Portfolio" msgstr "Portfolio" #: data/dev.tchx84.Portfolio.desktop.in:8 msgid "File Manager" msgstr "Gestor de Archivos" #: data/dev.tchx84.Portfolio.desktop.in:9 #: data/dev.tchx84.Portfolio.metainfo.xml.in:12 msgid "Manage files on the go" msgstr "Gestiona archivos en móviles" #: data/dev.tchx84.Portfolio.desktop.in:15 msgid "folder;manager;explore;disk;filesystem;" msgstr "carpeta;administrador;explorar;disco;archivo;" #: data/dev.tchx84.Portfolio.metainfo.xml.in:14 src/about.ui:58 msgid "" "A minimalist file manager for those who want to use Linux mobile devices." msgstr "Un simple gestor de archivos para móviles." #: data/dev.tchx84.Portfolio.metainfo.xml.in:20 msgid "Portfolio showing its home view" msgstr "Portfolio mostrando su vista de inicio" #: data/dev.tchx84.Portfolio.metainfo.xml.in:24 msgid "Portfolio showing directories and files" msgstr "Portfolio mostrando directorios y archivos" #: data/dev.tchx84.Portfolio.metainfo.xml.in:28 msgid "Portfolio showing file operations" msgstr "Portfolio mostrando operaciones de archivo" #: data/dev.tchx84.Portfolio.metainfo.xml.in:32 msgid "Portfolio showing its file properties view" msgstr "Portfolio mostrando su vista de propiedades de archivo" #: data/dev.tchx84.Portfolio.metainfo.xml.in:36 msgid "Portfolio showing its trash management view" msgstr "Portfolio mostrando su papelera" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.metainfo.xml.in:41 msgid "Martin Abente Lahaye" msgstr "" #: src/about.ui:73 msgid "Website" msgstr "Sitio web" #: src/about.ui:102 msgid "" "This program comes with absolutely no warranty. See the GNU General Public License, version 3 or " "later for details." msgstr "" "Este programa no provee absolutamente ninguna garantía. Consulte la Licencia pública general " "GNU, versión 3 o en adelante para obtener más detalles." #: src/about.ui:118 msgid "" "Portfolio logo is a modifed version of Jorgen Carlberg's work. The original " "version can be found here , licensed under CC BY 3.0 ." msgstr "" "El logotipo de Portfolio es una versión modificada del trabajo de Jorgen " "Carlberg. La versión original se puede encontrar aquí, bajo licencia CC BY 3.0." #: src/about.ui:139 msgid "Created by" msgstr "Creado por" #: src/about.ui:166 msgid "Artwork by" msgstr "Arte por" #: src/about.ui:194 msgid "Translated by" msgstr "Traducido por" #: src/about.ui:227 msgid "Contributions by" msgstr "Contribuciones por" #: src/menu.ui:21 msgid "Filter" msgstr "Filtrar" #: src/menu.ui:34 msgid "Show Hidden Files" msgstr "Mostrar archivos ocultos" #: src/menu.ui:57 msgid "Sort" msgstr "Ordenar" #: src/menu.ui:69 msgid "A-Z" msgstr "A-Z" #: src/menu.ui:84 msgid "Last Modified" msgstr "Última modificación" #: src/menu.ui:109 msgid "Help" msgstr "Ayuda" #: src/menu.ui:118 msgid "About Portfolio" msgstr "Acerca de Portfolio" #: src/placeholder.ui:16 msgid "No files found" msgstr "No se encontraron archivos" #: src/properties.ui:18 msgid "Name" msgstr "Nombre" #: src/properties.ui:49 msgid "Location" msgstr "Ubicación" #: src/properties.ui:80 msgid "Type" msgstr "Tipo" #: src/properties.ui:111 msgid "Size" msgstr "Tamaño" #: src/properties.ui:150 msgid "Created" msgstr "Creado" #: src/properties.ui:181 msgid "Modified" msgstr "Modificado" #: src/properties.ui:212 msgid "Accessed" msgstr "Accedido" #: src/properties.ui:251 msgid "Owner" msgstr "Propietario" #: src/properties.ui:282 msgid "Group" msgstr "Grupo" #: src/properties.ui:313 msgid "Owner can" msgstr "Propietario puede" #: src/properties.ui:344 msgid "Group can" msgstr "Grupo puede" #: src/properties.ui:375 msgid "Others can" msgstr "Otros pueden" #: src/window.ui:648 msgid "Properties" msgstr "Propiedades" #: src/window.ui:699 msgid "About" msgstr "Acerca de" #: src/files.py:292 src/window.py:491 #, python-format msgid "%s already exists" msgstr "%s ya existe" #: src/files.py:410 msgid "New Folder" msgstr "Nueva carpeta" #: src/passphrase.py:68 msgid "Sorry, that didn't work" msgstr "Lo siento, eso no ha funcionado" #: src/places.py:52 msgid "Trash" msgstr "Papelera" #: src/places.py:89 msgid "Places" msgstr "Lugares" #: src/places.py:93 msgid "Devices" msgstr "Dispositivos" #: src/places.py:103 msgid "Home" msgstr "Hogar" #: src/places.py:155 msgid "System" msgstr "Sistema" #: src/places.py:163 msgid "Host" msgstr "Anfitrión" #: src/places.py:177 msgid "No places found" msgstr "No se encontraron lugares" #: src/window.py:501 msgid "No permissions on this directory" msgstr "Sin permisos en este directorio" #: src/window.py:514 msgid "Opening" msgstr "Abriendo" #: src/window.py:532 src/window.py:1075 src/window.py:1085 #, python-format msgid "Could not open %s" msgstr "No se pudo abrir %s" #: src/window.py:551 src/window.py:580 msgid "Loading" msgstr "Cargando" #: src/window.py:580 #, python-format msgid "Could not load %s" msgstr "No se pudo cargar %s" #: src/window.py:630 src/window.py:941 #, python-format msgid "these %d files" msgstr "estos %d" #: src/window.py:632 src/window.py:943 #, python-format msgid "Delete %s permanently?" msgstr "¿Borrar %s?" #: src/window.py:652 #, python-format msgid "%s will be moved" msgstr "%s se moverá" #: src/window.py:654 #, python-format msgid "%d files will be moved" msgstr "%d archivos se moverán" #: src/window.py:669 #, python-format msgid "%s will be copied" msgstr "%s se copiará" #: src/window.py:671 #, python-format msgid "%d files will be copied" msgstr "%d archivos se copiarán" #: src/window.py:695 src/window.py:874 msgid "Files will be overwritten, proceed?" msgstr "Sobrescribirá archivos, ¿continuar?" #: src/window.py:710 msgid "Pasting" msgstr "Pegando" #: src/window.py:734 #, python-format msgid "%s of %s" msgstr "%s de %s" #: src/window.py:749 #, python-format msgid "Could not paste %s" msgstr "No se pudo pegar %s" #: src/window.py:801 src/window.py:969 msgid "Deleting" msgstr "Eliminando" #: src/window.py:826 src/window.py:993 #, python-format msgid "Could not delete %s" msgstr "No se pudo borrar %s" #: src/window.py:849 msgid "Stopping" msgstr "Deteniendo" #: src/window.py:897 msgid "Restoring" msgstr "Restaurando" #: src/window.py:926 #, python-format msgid "Could not restore %s" msgstr "No se pudo restaurar %s" #: src/window.py:1006 msgid "Removing device, please wait" msgstr "Removiendo dispositivo, por favor esperar" #: src/window.py:1010 msgid "Device can be removed" msgstr "Ya puede quitar el dispositivo" #: src/window.py:1022 msgid "Device is busy, can't be removed" msgstr "Dispositivo ocupado, no se puede quitar" #: src/worker.py:512 msgid "read" msgstr "leer" #: src/worker.py:514 msgid "write" msgstr "escribir" #: src/worker.py:516 msgid "execute" msgstr "ejecutar" #: src/worker.py:521 msgid "do nothing" msgstr "hacer nada" #: src/worker.py:601 msgid "Calculating..." msgstr "Calculando..." Portfolio-1.0.2/po/fr.po000066400000000000000000000206051476103320100150540ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the portfolio package. # FIRST AUTHOR , YEAR. # Irénée THIRION , 2024. # msgid "" msgstr "" "Project-Id-Version: portfolio\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-09 16:34-0300\n" "PO-Revision-Date: 2024-02-10 08:42+0100\n" "Last-Translator: Irénée THIRION \n" "Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" "X-Generator: Gtranslator 45.3\n" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.desktop.in:4 #: data/dev.tchx84.Portfolio.metainfo.xml.in:11 src/about.ui:30 msgid "Portfolio" msgstr "Portfolio" #: data/dev.tchx84.Portfolio.desktop.in:8 msgid "File Manager" msgstr "Gestionnaire de fichiers" #: data/dev.tchx84.Portfolio.desktop.in:9 #: data/dev.tchx84.Portfolio.metainfo.xml.in:12 msgid "Manage files on the go" msgstr "Gérez vos fichiers rapidement" #: data/dev.tchx84.Portfolio.desktop.in:15 msgid "folder;manager;explore;disk;filesystem;" msgstr "" "folder;manager;explore;disk;filesystem;dossier;gestionnaire;système de " "fichiers;fichiers;explorer;disque" #: data/dev.tchx84.Portfolio.metainfo.xml.in:14 src/about.ui:58 msgid "" "A minimalist file manager for those who want to use Linux mobile devices." msgstr "" "Un gestionnaire de fichiers minimaliste pour ceux qui veulent utiliser Linux " "sur des appareils mobiles." #: data/dev.tchx84.Portfolio.metainfo.xml.in:20 msgid "Portfolio showing its home view" msgstr "Vue d’accueil de Portfolio" #: data/dev.tchx84.Portfolio.metainfo.xml.in:24 msgid "Portfolio showing directories and files" msgstr "Affichage des répertoires et fichiers dans Portfolio" #: data/dev.tchx84.Portfolio.metainfo.xml.in:28 msgid "Portfolio showing file operations" msgstr "Opérations sur des fichiers dans Portfolio" #: data/dev.tchx84.Portfolio.metainfo.xml.in:32 msgid "Portfolio showing its file properties view" msgstr "Vue des propriétés d’un fichier dans Portfolio" #: data/dev.tchx84.Portfolio.metainfo.xml.in:36 msgid "Portfolio showing its trash management view" msgstr "Gestion de la corbeille dans Portfolio" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.metainfo.xml.in:41 msgid "Martin Abente Lahaye" msgstr "Martin Abente Lahaye" #: src/about.ui:73 msgid "Website" msgstr "Site web" #: src/about.ui:102 msgid "" "This program comes with absolutely no warranty. See the GNU General Public License, version 3 or " "later for details." msgstr "" "Ce programme est fourni sans aucune garantie.\n" "Pour plus de détails, visitez Licence publique générale GNU, version 3 ou ultérieure." #: src/about.ui:118 msgid "" "Portfolio logo is a modifed version of Jorgen Carlberg's work. The original " "version can be found here , licensed under CC BY 3.0 ." msgstr "" "Le logo de Portfolio est une version modifiée du travail de Jorgen " "Carlsberg. La version originale peut être trouvée ici , sous la licence CC BY 3.0 ." #: src/about.ui:139 msgid "Created by" msgstr "Créé par" #: src/about.ui:166 msgid "Artwork by" msgstr "Travail artistique par" #: src/about.ui:194 msgid "Translated by" msgstr "Traduit par" #: src/about.ui:227 msgid "Contributions by" msgstr "Contributions de" #: src/menu.ui:21 msgid "Filter" msgstr "Filtrer" #: src/menu.ui:34 msgid "Show Hidden Files" msgstr "Afficher les fichiers cachés" #: src/menu.ui:57 msgid "Sort" msgstr "Trier" #: src/menu.ui:69 msgid "A-Z" msgstr "A-Z" #: src/menu.ui:84 msgid "Last Modified" msgstr "Dernier modifié" #: src/menu.ui:109 msgid "Help" msgstr "Aide" #: src/menu.ui:118 msgid "About Portfolio" msgstr "À propos de Portfolio" #: src/placeholder.ui:16 msgid "No files found" msgstr "Aucun fichier trouvé" #: src/properties.ui:18 msgid "Name" msgstr "Nom" #: src/properties.ui:49 msgid "Location" msgstr "Emplacement" #: src/properties.ui:80 msgid "Type" msgstr "Type" #: src/properties.ui:111 msgid "Size" msgstr "Taille" #: src/properties.ui:150 msgid "Created" msgstr "Créé le" #: src/properties.ui:181 msgid "Modified" msgstr "Modifié" #: src/properties.ui:212 msgid "Accessed" msgstr "Dernier accès" #: src/properties.ui:251 msgid "Owner" msgstr "Propriétaire" #: src/properties.ui:282 msgid "Group" msgstr "Groupe" #: src/properties.ui:313 msgid "Owner can" msgstr "Propriétaire peut" #: src/properties.ui:344 msgid "Group can" msgstr "Groupe peut" #: src/properties.ui:375 msgid "Others can" msgstr "D'autres peuvent" #: src/window.ui:648 msgid "Properties" msgstr "Propriétés" #: src/window.ui:699 msgid "About" msgstr "À propos" #: src/files.py:292 src/window.py:491 #, python-format msgid "%s already exists" msgstr "%s existe déjà" #: src/files.py:410 msgid "New Folder" msgstr "Nouveau dossier" #: src/passphrase.py:68 msgid "Sorry, that didn't work" msgstr "Désolé, ça n’a pas marché" #: src/places.py:52 msgid "Trash" msgstr "Corbeille" #: src/places.py:89 msgid "Places" msgstr "Emplacements" #: src/places.py:93 msgid "Devices" msgstr "Appareils" #: src/places.py:103 msgid "Home" msgstr "Dossier personnel" #: src/places.py:155 msgid "System" msgstr "Système" #: src/places.py:163 msgid "Host" msgstr "Hôte" #: src/places.py:177 msgid "No places found" msgstr "Aucun emplacement trouvé" #: src/window.py:501 msgid "No permissions on this directory" msgstr "Pas de permissions sur ce répertoire" #: src/window.py:514 msgid "Opening" msgstr "Ouverture" #: src/window.py:532 src/window.py:1075 src/window.py:1085 #, python-format msgid "Could not open %s" msgstr "Impossible d’ouvrir %s" #: src/window.py:551 src/window.py:580 msgid "Loading" msgstr "Chargement" #: src/window.py:580 #, python-format msgid "Could not load %s" msgstr "Impossible de charger %s" #: src/window.py:630 src/window.py:941 #, python-format msgid "these %d files" msgstr "ces %d fichiers" #: src/window.py:632 src/window.py:943 #, python-format msgid "Delete %s permanently?" msgstr "Supprimer %s de manière permanente ?" #: src/window.py:652 #, python-format msgid "%s will be moved" msgstr "%s sera déplacé" #: src/window.py:654 #, python-format msgid "%d files will be moved" msgstr "%d fichiers seront déplacés" #: src/window.py:669 #, python-format msgid "%s will be copied" msgstr "%s sera copié" #: src/window.py:671 #, python-format msgid "%d files will be copied" msgstr "%d fichiers seront copiés" #: src/window.py:695 src/window.py:874 msgid "Files will be overwritten, proceed?" msgstr "Les fichiers seront écrasés, poursuivre ?" #: src/window.py:710 msgid "Pasting" msgstr "Collage" #: src/window.py:734 #, python-format msgid "%s of %s" msgstr "%s sur %s" #: src/window.py:749 #, python-format msgid "Could not paste %s" msgstr "Impossible de coller %s" #: src/window.py:801 src/window.py:969 msgid "Deleting" msgstr "Suppression" #: src/window.py:826 src/window.py:993 #, python-format msgid "Could not delete %s" msgstr "Impossible de supprimer %s" #: src/window.py:849 msgid "Stopping" msgstr "Arrêt" #: src/window.py:897 msgid "Restoring" msgstr "Restauration" #: src/window.py:926 #, python-format msgid "Could not restore %s" msgstr "Impossible de restaurer %s" #: src/window.py:1006 msgid "Removing device, please wait" msgstr "Suppression de l'appareil, merci de patienter" #: src/window.py:1010 msgid "Device can be removed" msgstr "L’appareil ne peut être enlevé" #: src/window.py:1022 msgid "Device is busy, can't be removed" msgstr "L’appareil est occupé, il ne peut être enlevé" #: src/worker.py:512 msgid "read" msgstr "lire" #: src/worker.py:514 msgid "write" msgstr "écrire" #: src/worker.py:516 msgid "execute" msgstr "exécuter" #: src/worker.py:521 msgid "do nothing" msgstr "ne fais rien" #: src/worker.py:601 msgid "Calculating..." msgstr "Calcul..." Portfolio-1.0.2/po/he.po000066400000000000000000000215461476103320100150460ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the portfolio package. # FIRST AUTHOR , YEAR. # Yosef Or Boczko , 2024. # msgid "" msgstr "" "Project-Id-Version: portfolio\n" "Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/gnome-firmware/-/" "issues\n" "POT-Creation-Date: 2024-08-18 10:28+0000\n" "PO-Revision-Date: 2024-09-10 10:32+0300\n" "Last-Translator: Yosef Or Boczko \n" "Language-Team: Hebrew \n" "Language: he\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : n==2 ? 1 : n>10 && n%10==0 ? " "2 : 3)\n" "X-Generator: Gtranslator 46.1\n" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.desktop.in:4 #: data/dev.tchx84.Portfolio.metainfo.xml.in:11 src/about.ui:30 msgid "Portfolio" msgstr "Portfolio" #: data/dev.tchx84.Portfolio.desktop.in:8 msgid "File Manager" msgstr "מנהל קבצי×" #: data/dev.tchx84.Portfolio.desktop.in:9 #: data/dev.tchx84.Portfolio.metainfo.xml.in:12 msgid "Manage files on the go" msgstr "ניהול ×”×§×‘×¦×™× ×©×œ×š בדרך" #: data/dev.tchx84.Portfolio.desktop.in:15 msgid "folder;manager;explore;disk;filesystem;" msgstr "תיקייה;תיקיה;מנהל;ניהול;סייר;קבצי×;כןנ×;מערכת קבצי×;פורטפליו;" #: data/dev.tchx84.Portfolio.metainfo.xml.in:14 src/about.ui:58 msgid "" "A minimalist file manager for those who want to use Linux mobile devices." msgstr "מנהל ×§×‘×¦×™× ×‘×¡×™×¡×™ למי שרוצה להשתמש במכשירי לינוקס ניידי×." #: data/dev.tchx84.Portfolio.metainfo.xml.in:20 msgid "Portfolio showing its home view" msgstr "â€Portfolio מציג ×ת תצוגת הבית" #: data/dev.tchx84.Portfolio.metainfo.xml.in:24 msgid "Portfolio showing directories and files" msgstr "â€Portfolio מציג תיקייות וקבצי×" #: data/dev.tchx84.Portfolio.metainfo.xml.in:28 msgid "Portfolio showing file operations" msgstr "â€Portfolio מציג פעולות על קבצי×" #: data/dev.tchx84.Portfolio.metainfo.xml.in:32 msgid "Portfolio showing its file properties view" msgstr "â€Portfolio מציג ×ת תצוגת מ×פייני קובץ שלו" #: data/dev.tchx84.Portfolio.metainfo.xml.in:36 msgid "Portfolio showing its trash management view" msgstr "â€Portfolio מציג ×ת תצוגת ניהול ×”×שפה שלו" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.metainfo.xml.in:41 msgid "Martin Abente Lahaye" msgstr "Martin Abente Lahaye" #: src/about.ui:73 msgid "Website" msgstr "×תר ×ינטרנט" #: src/about.ui:102 msgid "" "This program comes with absolutely no warranty. See the GNU General Public License, version 3 or " "later for details." msgstr "" "תכנית זו מופצת ×œ×œ× ×©×•× ×חריות. ניתן ×œ×§×¨×•× ×¢×œ הרישיון הציבורי הכללי של גנו, גרסה 3 ×ו כל גרסה " "מ×וחרת יותר למידע נוסף." #: src/about.ui:118 msgid "" "Portfolio logo is a modifed version of Jorgen Carlberg's work. The original " "version can be found here , licensed under CC BY 3.0 ." msgstr "" "הסמליל של Portfolio מבוסס על עבודה של Jorgen Carlberg, ×¢× ×©×™× ×•×™×™×. ניתן " "×œ×ž×¦×•× ×ת הגרסה המקורית ×›×ן, תחת הרישיון ‎CC BY 3.0." #: src/about.ui:139 msgid "Created by" msgstr "נוצר על ידי" #: src/about.ui:166 msgid "Artwork by" msgstr "×ומנות על ידי" #: src/about.ui:194 msgid "Translated by" msgstr "×ª×•×¨×’× ×¢×œ ידי" #: src/about.ui:227 msgid "Contributions by" msgstr "תרומות על ידי" #: src/menu.ui:21 msgid "Filter" msgstr "מסנן" #: src/menu.ui:34 msgid "Show Hidden Files" msgstr "הצגת ×§×‘×¦×™× ×ž×•×¡×ª×¨×™×" #: src/menu.ui:57 msgid "Sort" msgstr "מיון" #: src/menu.ui:69 msgid "A-Z" msgstr "A-Z" #: src/menu.ui:84 msgid "Last Modified" msgstr "שונה ל×חרונה" #: src/menu.ui:109 msgid "Help" msgstr "עזרה" #: src/menu.ui:118 msgid "About Portfolio" msgstr "על Portfolio" #: src/placeholder.ui:16 msgid "No files found" msgstr "×œ× × ×ž×¦×ו קבצי×" #: src/properties.ui:18 msgid "Name" msgstr "ש×" #: src/properties.ui:49 msgid "Location" msgstr "מקו×" #: src/properties.ui:80 msgid "Type" msgstr "סוג" #: src/properties.ui:111 msgid "Size" msgstr "גודל" #: src/properties.ui:150 msgid "Created" msgstr "ת×ריך יצירה" #: src/properties.ui:181 msgid "Modified" msgstr "מועד שינוי" #: src/properties.ui:212 msgid "Accessed" msgstr "מועד גישה" #: src/properties.ui:251 msgid "Owner" msgstr "בעלי×" #: src/properties.ui:282 msgid "Group" msgstr "קבוצה" #: src/properties.ui:313 msgid "Owner can" msgstr "×‘×¢×œ×™× ×™×›×•×œ" #: src/properties.ui:344 msgid "Group can" msgstr "קבוצה יכולה" #: src/properties.ui:375 msgid "Others can" msgstr "××—×¨×™× ×™×›×•×œ×™×" #: src/window.ui:648 msgid "Properties" msgstr "מ×פייני×" #: src/window.ui:699 msgid "About" msgstr "×ודות" #: src/files.py:292 src/window.py:491 #, python-format msgid "%s already exists" msgstr "â€%s כבר ×§×™×™×" #: src/files.py:410 msgid "New Folder" msgstr "תיקייה חדשה" #: src/passphrase.py:68 msgid "Sorry, that didn't work" msgstr "×נו מצטערי×, ×–×” ×œ× ×¢×‘×“" #: src/places.py:52 msgid "Trash" msgstr "×שפה" #: src/places.py:89 msgid "Places" msgstr "מקומות" #: src/places.py:93 msgid "Devices" msgstr "התקני×" #: src/places.py:103 msgid "Home" msgstr "בית" #: src/places.py:155 msgid "System" msgstr "מערכת" #: src/places.py:163 msgid "Host" msgstr "מ×רח" #: src/places.py:177 msgid "No places found" msgstr "×œ× × ×ž×¦×ו מקומות" #: src/window.py:501 msgid "No permissions on this directory" msgstr "×ין לך הרש×ות לתיקייה זו" #: src/window.py:514 msgid "Opening" msgstr "פתיחה" #: src/window.py:532 src/window.py:1075 src/window.py:1085 #, python-format msgid "Could not open %s" msgstr "×œ× × ×™×ª×Ÿ לפתוח ×ת %s" #: src/window.py:551 src/window.py:580 msgid "Loading" msgstr "בטעינה" #: src/window.py:580 #, python-format msgid "Could not load %s" msgstr "×œ× × ×™×ª×Ÿ לטעון ×ת %s" #: src/window.py:630 src/window.py:941 #, python-format msgid "these %d files" msgstr "â€%d ×§×‘×¦×™× ×לה" #: src/window.py:632 src/window.py:943 #, python-format msgid "Delete %s permanently?" msgstr "למחוק ×ת %s לצמיתות?" #: src/window.py:652 #, python-format msgid "%s will be moved" msgstr "â€%s יועבר" #: src/window.py:654 #, python-format msgid "%d files will be moved" msgstr "â€%d ×§×‘×¦×™× ×™×•×¢×‘×¨×•" #: src/window.py:669 #, python-format msgid "%s will be copied" msgstr "â€%s יועתק" #: src/window.py:671 #, python-format msgid "%d files will be copied" msgstr "â€%d ×§×‘×¦×™× ×™×•×¢×ª×§×•" #: src/window.py:695 src/window.py:874 msgid "Files will be overwritten, proceed?" msgstr "מספר ×§×‘×¦×™× ×™×“×¨×¡×•, לבצע בכל ×–×ת?" #: src/window.py:710 msgid "Pasting" msgstr "מדביק" #: src/window.py:734 #, python-format msgid "%s of %s" msgstr "â€%s מתוך %s" #: src/window.py:749 #, python-format msgid "Could not paste %s" msgstr "×œ× × ×™×ª×Ÿ להדביק ×ת %s" #: src/window.py:801 src/window.py:969 msgid "Deleting" msgstr "מוחק" #: src/window.py:826 src/window.py:993 #, python-format msgid "Could not delete %s" msgstr "×œ× × ×™×ª×Ÿ למחוק ×ת %s" #: src/window.py:849 msgid "Stopping" msgstr "עוצר" #: src/window.py:897 msgid "Restoring" msgstr "משחזר" #: src/window.py:926 #, python-format msgid "Could not restore %s" msgstr "×œ× × ×™×ª×Ÿ לשחזר ×ת %s" #: src/window.py:1006 msgid "Removing device, please wait" msgstr "מסיק התקן, × × ×œ×”×ž×ª×™×Ÿ" #: src/window.py:1010 msgid "Device can be removed" msgstr "ההתקן ניסר להסרה" #: src/window.py:1022 msgid "Device is busy, can't be removed" msgstr "ההתקן עסוק, ×œ× × ×™×ª×Ÿ להסיר ×ותו" #: src/worker.py:512 msgid "read" msgstr "קרי××”" #: src/worker.py:514 msgid "write" msgstr "כתיבה" #: src/worker.py:516 msgid "execute" msgstr "הרצה" #: src/worker.py:521 msgid "do nothing" msgstr "×œ× ×œ×¢×©×•×ª דבר" #: src/worker.py:601 msgid "Calculating..." msgstr "מחשב…" Portfolio-1.0.2/po/hi.po000066400000000000000000000250661476103320100150530ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the portfolio package. # FIRST AUTHOR , YEAR. # Scrambled777 , 2024. # msgid "" msgstr "" "Project-Id-Version: portfolio\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-09 16:34-0300\n" "PO-Revision-Date: 2024-05-13 12:07+0530\n" "Last-Translator: Scrambled777 \n" "Language-Team: Hindi \n" "Language: hi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: Gtranslator 46.1\n" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.desktop.in:4 #: data/dev.tchx84.Portfolio.metainfo.xml.in:11 src/about.ui:30 msgid "Portfolio" msgstr "Portfolio" #: data/dev.tchx84.Portfolio.desktop.in:8 msgid "File Manager" msgstr "फाइल पà¥à¤°à¤¬à¤‚धक" #: data/dev.tchx84.Portfolio.desktop.in:9 #: data/dev.tchx84.Portfolio.metainfo.xml.in:12 msgid "Manage files on the go" msgstr "चलते-फिरते फाइलें पà¥à¤°à¤¬à¤‚धित करें" #: data/dev.tchx84.Portfolio.desktop.in:15 msgid "folder;manager;explore;disk;filesystem;" msgstr "फोलà¥à¤¡à¤°;पà¥à¤°à¤¬à¤‚धक;अनà¥à¤µà¥‡à¤·à¤£;डिसà¥à¤•;फाइलपà¥à¤°à¤£à¤¾à¤²à¥€;" #: data/dev.tchx84.Portfolio.metainfo.xml.in:14 src/about.ui:58 msgid "" "A minimalist file manager for those who want to use Linux mobile devices." msgstr "" "उन लोगों के लिठà¤à¤• नà¥à¤¯à¥‚नतम फाइल पà¥à¤°à¤¬à¤‚धक जो Linux मोबाइल उपकरणों का उपयोग करना चाहते " "हैं।" #: data/dev.tchx84.Portfolio.metainfo.xml.in:20 msgid "Portfolio showing its home view" msgstr "Portfolio अपना गृह दृशà¥à¤¯ दिखते हà¥à¤" #: data/dev.tchx84.Portfolio.metainfo.xml.in:24 msgid "Portfolio showing directories and files" msgstr "Portfolio निरà¥à¤¦à¥‡à¤¶à¤¿à¤•ाà¤à¤‚ और फाइलें दिखते हà¥à¤" #: data/dev.tchx84.Portfolio.metainfo.xml.in:28 msgid "Portfolio showing file operations" msgstr "Portfolio फाइल संचालन दिखते हà¥à¤" #: data/dev.tchx84.Portfolio.metainfo.xml.in:32 msgid "Portfolio showing its file properties view" msgstr "Portfolio अपना फाइल गà¥à¤£ दृशà¥à¤¯ दिखते हà¥à¤" #: data/dev.tchx84.Portfolio.metainfo.xml.in:36 msgid "Portfolio showing its trash management view" msgstr "Portfolio अपना कचरा पà¥à¤°à¤¬à¤‚धन दृशà¥à¤¯ दिखते हà¥à¤" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.metainfo.xml.in:41 msgid "Martin Abente Lahaye" msgstr "Martin Abente Lahaye" #: src/about.ui:73 msgid "Website" msgstr "वेबसाइट" #: src/about.ui:102 msgid "" "This program comes with absolutely no warranty. See the GNU General Public License, version 3 or " "later for details." msgstr "" "यह पà¥à¤°à¥‹à¤—à¥à¤°à¤¾à¤® बिलà¥à¤•à¥à¤² बिना किसी वारंटी के आता है। विवरण के लिठGNU जनरल पबà¥à¤²à¤¿à¤• लाइसेंस, संसà¥à¤•रण 3 या बाद का " "संसà¥à¤•रण देखें।" #: src/about.ui:118 msgid "" "Portfolio logo is a modifed version of Jorgen Carlberg's work. The original " "version can be found here , licensed under CC BY 3.0 ." msgstr "" "Portfolio लोगो जोरà¥à¤—ेन कारà¥à¤²à¤¬à¤°à¥à¤— के काम का à¤à¤• संशोधित संसà¥à¤•रण है। मूल संसà¥à¤•रण यहां " "पाया जा सकता है, जो CC BY 3.0 ।" #: src/about.ui:139 msgid "Created by" msgstr "इनके दà¥à¤µà¤¾à¤°à¤¾ निरà¥à¤®à¤¿à¤¤" #: src/about.ui:166 msgid "Artwork by" msgstr "इनके दà¥à¤µà¤¾à¤°à¤¾ कलाकृति" #: src/about.ui:194 msgid "Translated by" msgstr "इनके दà¥à¤µà¤¾à¤°à¤¾ अनà¥à¤µà¤¾à¤¦à¤¿à¤¤" #: src/about.ui:227 msgid "Contributions by" msgstr "इनके दà¥à¤µà¤¾à¤°à¤¾ योगदान" #: src/menu.ui:21 msgid "Filter" msgstr "फिलà¥à¤Ÿà¤°" #: src/menu.ui:34 msgid "Show Hidden Files" msgstr "छà¥à¤ªà¥€ हà¥à¤ˆ फाइलें दिखाà¤à¤‚" #: src/menu.ui:57 msgid "Sort" msgstr "छांटें" #: src/menu.ui:69 msgid "A-Z" msgstr "A-Z" #: src/menu.ui:84 msgid "Last Modified" msgstr "अंतिम संशोधित" #: src/menu.ui:109 msgid "Help" msgstr "सहायता" #: src/menu.ui:118 msgid "About Portfolio" msgstr "Portfolio के बारे में" #: src/placeholder.ui:16 msgid "No files found" msgstr "कोई फाइलें नहीं मिलीं" #: src/properties.ui:18 msgid "Name" msgstr "नाम" #: src/properties.ui:49 msgid "Location" msgstr "सà¥à¤¥à¤¾à¤¨" #: src/properties.ui:80 msgid "Type" msgstr "पà¥à¤°à¤•ार" #: src/properties.ui:111 msgid "Size" msgstr "आकार" #: src/properties.ui:150 msgid "Created" msgstr "निरà¥à¤®à¤¿à¤¤" #: src/properties.ui:181 msgid "Modified" msgstr "संशोधित" #: src/properties.ui:212 msgid "Accessed" msgstr "अभिगमित" #: src/properties.ui:251 msgid "Owner" msgstr "मालिक" #: src/properties.ui:282 msgid "Group" msgstr "समूह" #: src/properties.ui:313 msgid "Owner can" msgstr "मालिक कर सकता है" #: src/properties.ui:344 msgid "Group can" msgstr "समूह कर सकता है" #: src/properties.ui:375 msgid "Others can" msgstr "अनà¥à¤¯ कर सकते हैं" #: src/window.ui:648 msgid "Properties" msgstr "गà¥à¤£" #: src/window.ui:699 msgid "About" msgstr "à¤à¤ª के बारे में" #: src/files.py:292 src/window.py:491 #, python-format msgid "%s already exists" msgstr "%s पहले से मौजूद है" #: src/files.py:410 msgid "New Folder" msgstr "नया फोलà¥à¤¡à¤°" #: src/passphrase.py:68 msgid "Sorry, that didn't work" msgstr "कà¥à¤·à¤®à¤¾ करें, वह काम नहीं किया" #: src/places.py:52 msgid "Trash" msgstr "रदà¥à¤¦à¥€" #: src/places.py:89 msgid "Places" msgstr "सà¥à¤¥à¤¾à¤¨" #: src/places.py:93 msgid "Devices" msgstr "उपकरण" #: src/places.py:103 msgid "Home" msgstr "गृह" #: src/places.py:155 msgid "System" msgstr "सिसà¥à¤Ÿà¤®" #: src/places.py:163 msgid "Host" msgstr "होसà¥à¤Ÿ" #: src/places.py:177 msgid "No places found" msgstr "कोई सà¥à¤¥à¤¾à¤¨ नहीं मिला" #: src/window.py:501 msgid "No permissions on this directory" msgstr "इस निरà¥à¤¦à¥‡à¤¶à¤¿à¤•ा पर कोई अनà¥à¤®à¤¤à¤¿ नहीं" #: src/window.py:514 msgid "Opening" msgstr "खोल रहे हैं" #: src/window.py:532 src/window.py:1075 src/window.py:1085 #, python-format msgid "Could not open %s" msgstr "%s नहीं खोल सके" #: src/window.py:551 src/window.py:580 msgid "Loading" msgstr "लोड कर रहे हैं" #: src/window.py:580 #, python-format msgid "Could not load %s" msgstr "%s लोड नहीं कर सके" #: src/window.py:630 src/window.py:941 #, python-format msgid "these %d files" msgstr "ये %d फाइलें" #: src/window.py:632 src/window.py:943 #, python-format msgid "Delete %s permanently?" msgstr "कà¥à¤¯à¤¾ %s को सà¥à¤¥à¤¾à¤¯à¥€ रूप से मिटाà¤à¤‚?" #: src/window.py:652 #, python-format msgid "%s will be moved" msgstr "%s सà¥à¤¥à¤¾à¤¨à¤¾à¤‚तरित किया जाà¤à¤—ा" #: src/window.py:654 #, python-format msgid "%d files will be moved" msgstr "%d फाइलें सà¥à¤¥à¤¾à¤¨à¤¾à¤‚तरित कर दी जाà¤à¤‚गी" #: src/window.py:669 #, python-format msgid "%s will be copied" msgstr "%s की पà¥à¤°à¤¤à¤¿à¤²à¤¿à¤ªà¤¿ बनाई जाà¤à¤—ी" #: src/window.py:671 #, python-format msgid "%d files will be copied" msgstr "%d फाइलों की पà¥à¤°à¤¤à¤¿à¤²à¤¿à¤ªà¤¿ बनाई जाà¤à¤—ी" #: src/window.py:695 src/window.py:874 msgid "Files will be overwritten, proceed?" msgstr "फाइलें अधिलेखित हो जाà¤à¤‚गी, आगे बढ़ें?" #: src/window.py:710 msgid "Pasting" msgstr "चिपका रहे हैं" #: src/window.py:734 #, python-format msgid "%s of %s" msgstr "%s का %s" #: src/window.py:749 #, python-format msgid "Could not paste %s" msgstr "%s को चिपका नहीं सके" #: src/window.py:801 src/window.py:969 msgid "Deleting" msgstr "मिटा रहे हैं" #: src/window.py:826 src/window.py:993 #, python-format msgid "Could not delete %s" msgstr "%s को मिटा नहीं सके" #: src/window.py:849 msgid "Stopping" msgstr "रोक रहे हैं" #: src/window.py:897 msgid "Restoring" msgstr "पà¥à¤¨à¤°à¥à¤¸à¥à¤¥à¤¾à¤ªà¤¿à¤¤ कर रहे हैं" #: src/window.py:926 #, python-format msgid "Could not restore %s" msgstr "%s को पà¥à¤¨à¤°à¥à¤¸à¥à¤¥à¤¾à¤ªà¤¿à¤¤ नहीं कर सके" #: src/window.py:1006 msgid "Removing device, please wait" msgstr "उपकरण हटाया जा रहा है, कृपया पà¥à¤°à¤¤à¥€à¤•à¥à¤·à¤¾ करें" #: src/window.py:1010 msgid "Device can be removed" msgstr "उपकरण हटाया जा सकता है" #: src/window.py:1022 msgid "Device is busy, can't be removed" msgstr "उपकरण वà¥à¤¯à¤¸à¥à¤¤ है, हटाया नहीं जा सकता" #: src/worker.py:512 msgid "read" msgstr "पठन" #: src/worker.py:514 msgid "write" msgstr "लेखन" #: src/worker.py:516 msgid "execute" msgstr "निषà¥à¤ªà¤¾à¤¦à¤¿à¤¤ करें" #: src/worker.py:521 msgid "do nothing" msgstr "कà¥à¤› न करें" #: src/worker.py:601 msgid "Calculating..." msgstr "गिना जा रहा है..." Portfolio-1.0.2/po/id.po000066400000000000000000000200711476103320100150360ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the portfolio package. # Kukuh Syafaat , 2020-2022, 2024. # msgid "" msgstr "" "Project-Id-Version: portfolio\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-09 16:34-0300\n" "PO-Revision-Date: 2024-02-13 20:21+0700\n" "Last-Translator: Kukuh Syafaat \n" "Language-Team: \n" "Language: id_ID\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Poedit 3.4.2\n" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.desktop.in:4 #: data/dev.tchx84.Portfolio.metainfo.xml.in:11 src/about.ui:30 msgid "Portfolio" msgstr "Portfolio" #: data/dev.tchx84.Portfolio.desktop.in:8 msgid "File Manager" msgstr "Manajer Berkas" #: data/dev.tchx84.Portfolio.desktop.in:9 #: data/dev.tchx84.Portfolio.metainfo.xml.in:12 msgid "Manage files on the go" msgstr "Kelola berkas di mana saja" #: data/dev.tchx84.Portfolio.desktop.in:15 msgid "folder;manager;explore;disk;filesystem;" msgstr "folder;pengelola;jelajahi;diska;sistem berkas;" #: data/dev.tchx84.Portfolio.metainfo.xml.in:14 src/about.ui:58 msgid "" "A minimalist file manager for those who want to use Linux mobile devices." msgstr "" "Pengelola berkas minimalis bagi mereka yang ingin menggunakan perangkat " "seluler Linux." #: data/dev.tchx84.Portfolio.metainfo.xml.in:20 msgid "Portfolio showing its home view" msgstr "Portofolio menampilkan tampilan beranda" #: data/dev.tchx84.Portfolio.metainfo.xml.in:24 msgid "Portfolio showing directories and files" msgstr "Portofolio menampilkan direktori dan berkas" #: data/dev.tchx84.Portfolio.metainfo.xml.in:28 msgid "Portfolio showing file operations" msgstr "Portofolio menampilkan operasi berkas" #: data/dev.tchx84.Portfolio.metainfo.xml.in:32 msgid "Portfolio showing its file properties view" msgstr "Portofolio menampilkan tampilan properti berkasnya" #: data/dev.tchx84.Portfolio.metainfo.xml.in:36 msgid "Portfolio showing its trash management view" msgstr "Portofolio menunjukkan tampilan pengelolaan sampahnya" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.metainfo.xml.in:41 msgid "Martin Abente Lahaye" msgstr "Martin Abente Lahaye" #: src/about.ui:73 msgid "Website" msgstr "Situs Web" #: src/about.ui:102 msgid "" "This program comes with absolutely no warranty. See the GNU General Public License, version 3 or " "later for details." msgstr "" "Program ini sama sekali tidak memiliki garansi. Lihat GNU General Public License, versi 3 atau " "yang lebih baru untuk detailnya." #: src/about.ui:118 msgid "" "Portfolio logo is a modifed version of Jorgen Carlberg's work. The original " "version can be found here , licensed under CC BY 3.0 ." msgstr "" "Logo portofolio adalah versi modifikasi dari karya Jorgen Carlberg. Versi " "asli dapat ditemukan di sini, dilisensikan dibawah CC BY 3.0." #: src/about.ui:139 msgid "Created by" msgstr "Dibuat oleh" #: src/about.ui:166 msgid "Artwork by" msgstr "Karya Seni oleh" #: src/about.ui:194 msgid "Translated by" msgstr "Diterjemahkan oleh" #: src/about.ui:227 msgid "Contributions by" msgstr "Kontribusi oleh" #: src/menu.ui:21 msgid "Filter" msgstr "Saring" #: src/menu.ui:34 msgid "Show Hidden Files" msgstr "Tampilkan Berkas Tersembunyi" #: src/menu.ui:57 msgid "Sort" msgstr "Urutkan" #: src/menu.ui:69 msgid "A-Z" msgstr "A-Z" #: src/menu.ui:84 msgid "Last Modified" msgstr "Terakhir Diubah" #: src/menu.ui:109 msgid "Help" msgstr "Bantuan" #: src/menu.ui:118 msgid "About Portfolio" msgstr "Tentang Portofolio" #: src/placeholder.ui:16 msgid "No files found" msgstr "Tak ada berkas yang ditemukan" #: src/properties.ui:18 msgid "Name" msgstr "Nama" #: src/properties.ui:49 msgid "Location" msgstr "Lokasi" #: src/properties.ui:80 msgid "Type" msgstr "Tipe" #: src/properties.ui:111 msgid "Size" msgstr "Ukuran" #: src/properties.ui:150 msgid "Created" msgstr "Dibuat" #: src/properties.ui:181 msgid "Modified" msgstr "Diubah" #: src/properties.ui:212 msgid "Accessed" msgstr "Diakses" #: src/properties.ui:251 msgid "Owner" msgstr "Pemilik" #: src/properties.ui:282 msgid "Group" msgstr "Kelompok" #: src/properties.ui:313 msgid "Owner can" msgstr "Pemilik bisa" #: src/properties.ui:344 msgid "Group can" msgstr "Kelompok bisa" #: src/properties.ui:375 msgid "Others can" msgstr "Orang lain bisa" #: src/window.ui:648 msgid "Properties" msgstr "Properti" #: src/window.ui:699 msgid "About" msgstr "Tentang" #: src/files.py:292 src/window.py:491 #, python-format msgid "%s already exists" msgstr "%s sudah ada" #: src/files.py:410 msgid "New Folder" msgstr "Folder Baru" #: src/passphrase.py:68 msgid "Sorry, that didn't work" msgstr "Maaf, itu tidak berhasil" #: src/places.py:52 msgid "Trash" msgstr "Tempat Sampah" #: src/places.py:89 msgid "Places" msgstr "Lokasi" #: src/places.py:93 msgid "Devices" msgstr "Perangkat" #: src/places.py:103 msgid "Home" msgstr "Beranda" #: src/places.py:155 msgid "System" msgstr "Sistem" #: src/places.py:163 msgid "Host" msgstr "Host" #: src/places.py:177 msgid "No places found" msgstr "Tak ada tempat yang ditemukan" #: src/window.py:501 msgid "No permissions on this directory" msgstr "Tak ada izin pada direktori ini" #: src/window.py:514 msgid "Opening" msgstr "Membuka" #: src/window.py:532 src/window.py:1075 src/window.py:1085 #, python-format msgid "Could not open %s" msgstr "Tak bisa membuka %s" #: src/window.py:551 src/window.py:580 msgid "Loading" msgstr "Memuat" #: src/window.py:580 #, python-format msgid "Could not load %s" msgstr "Tak bisa memuat %s" #: src/window.py:630 src/window.py:941 #, python-format msgid "these %d files" msgstr "%d berkas ini" #: src/window.py:632 src/window.py:943 #, python-format msgid "Delete %s permanently?" msgstr "Hapus %s permanen?" #: src/window.py:652 #, python-format msgid "%s will be moved" msgstr "%s akan dipindahkan" #: src/window.py:654 #, python-format msgid "%d files will be moved" msgstr "%d berkas akan dipindahkan" #: src/window.py:669 #, python-format msgid "%s will be copied" msgstr "%s akan disalin" #: src/window.py:671 #, python-format msgid "%d files will be copied" msgstr "%d berkas akan disalin" #: src/window.py:695 src/window.py:874 msgid "Files will be overwritten, proceed?" msgstr "Berkas akan ditimpa, lanjutkan?" #: src/window.py:710 msgid "Pasting" msgstr "Menempel" #: src/window.py:734 #, python-format msgid "%s of %s" msgstr "%s dari %s" #: src/window.py:749 #, python-format msgid "Could not paste %s" msgstr "Tak bisa menempelkan %s" #: src/window.py:801 src/window.py:969 msgid "Deleting" msgstr "Menghapus" #: src/window.py:826 src/window.py:993 #, python-format msgid "Could not delete %s" msgstr "Tak bisa menghapus %s" #: src/window.py:849 msgid "Stopping" msgstr "Henti" #: src/window.py:897 msgid "Restoring" msgstr "Memulihkan" #: src/window.py:926 #, python-format msgid "Could not restore %s" msgstr "Tak bisa memulihkan %s" #: src/window.py:1006 msgid "Removing device, please wait" msgstr "Menghapus perangkat, harap tunggu" #: src/window.py:1010 msgid "Device can be removed" msgstr "Perangkat dapat dihapus" #: src/window.py:1022 msgid "Device is busy, can't be removed" msgstr "Perangkat sibuk, tidak dapat dilepas" #: src/worker.py:512 msgid "read" msgstr "baca" #: src/worker.py:514 msgid "write" msgstr "tulis" #: src/worker.py:516 msgid "execute" msgstr "mengeksekusi" #: src/worker.py:521 msgid "do nothing" msgstr "melakukan apa-apa" #: src/worker.py:601 msgid "Calculating..." msgstr "Menghitung..." Portfolio-1.0.2/po/meson.build000066400000000000000000000000521476103320100162410ustar00rootroot00000000000000i18n.gettext('portfolio', preset: 'glib') Portfolio-1.0.2/po/nl.po000066400000000000000000000203701476103320100150550ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the portfolio package. # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: portfolio\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-09 16:34-0300\n" "PO-Revision-Date: 2024-02-10 22:34+0100\n" "Last-Translator: Heimen Stoffels \n" "Language-Team: \n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.4.2\n" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.desktop.in:4 #: data/dev.tchx84.Portfolio.metainfo.xml.in:11 src/about.ui:30 msgid "Portfolio" msgstr "Portfolio" #: data/dev.tchx84.Portfolio.desktop.in:8 msgid "File Manager" msgstr "Bestandsbeheerder" #: data/dev.tchx84.Portfolio.desktop.in:9 #: data/dev.tchx84.Portfolio.metainfo.xml.in:12 msgid "Manage files on the go" msgstr "Beheer onderweg je bestanden" #: data/dev.tchx84.Portfolio.desktop.in:15 msgid "folder;manager;explore;disk;filesystem;" msgstr "map;beheerder;verkennen;schijf;bestandssysteem;" #: data/dev.tchx84.Portfolio.metainfo.xml.in:14 src/about.ui:58 msgid "" "A minimalist file manager for those who want to use Linux mobile devices." msgstr "" "Een minimalistische bestandsbeheerder voor zij die Linux op mobiele " "apparaten gebruiken." #: data/dev.tchx84.Portfolio.metainfo.xml.in:20 msgid "Portfolio showing its home view" msgstr "Portfolio - Overzicht" #: data/dev.tchx84.Portfolio.metainfo.xml.in:24 msgid "Portfolio showing directories and files" msgstr "Portfolio - Bestanden en mappen" #: data/dev.tchx84.Portfolio.metainfo.xml.in:28 msgid "Portfolio showing file operations" msgstr "Portfolio - Bestandsacties" #: data/dev.tchx84.Portfolio.metainfo.xml.in:32 msgid "Portfolio showing its file properties view" msgstr "Portfolio - Bestandseigenschappen" #: data/dev.tchx84.Portfolio.metainfo.xml.in:36 msgid "Portfolio showing its trash management view" msgstr "Portfolio - Prullenbakbeheer" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.metainfo.xml.in:41 msgid "Martin Abente Lahaye" msgstr "Martin Abente Lahaye" #: src/about.ui:73 msgid "Website" msgstr "Website" #: src/about.ui:102 msgid "" "This program comes with absolutely no warranty. See the GNU General Public License, version 3 or " "later for details." msgstr "" "Deze toepassing biedt geen garantie.\n" "Lees voor meer informatie de GNU General Public License, versie 3 of hoger." #: src/about.ui:118 msgid "" "Portfolio logo is a modifed version of Jorgen Carlberg's work. The original " "version can be found here , licensed under CC BY 3.0 ." msgstr "" "Het Portfolio-logo is een aangepaste versie van Jorgen Carlberg's logo. De " "oorspronkelijke versie is hier te vinden, uitgebracht onder de CC BY 3.0-licentie." #: src/about.ui:139 msgid "Created by" msgstr "Gemaakt door" #: src/about.ui:166 msgid "Artwork by" msgstr "Vormgegeven door" #: src/about.ui:194 msgid "Translated by" msgstr "Vertaald door" #: src/about.ui:227 msgid "Contributions by" msgstr "Met bijdragen van" #: src/menu.ui:21 msgid "Filter" msgstr "Filteren" #: src/menu.ui:34 msgid "Show Hidden Files" msgstr "Verborgen bestanden tonen" #: src/menu.ui:57 msgid "Sort" msgstr "Sorteren" #: src/menu.ui:69 msgid "A-Z" msgstr "A-Z" #: src/menu.ui:84 msgid "Last Modified" msgstr "Wijzigingsdatum" #: src/menu.ui:109 msgid "Help" msgstr "Hulp" #: src/menu.ui:118 msgid "About Portfolio" msgstr "Over Portfolio" #: src/placeholder.ui:16 msgid "No files found" msgstr "Er zijn geen bestanden aangetroffen" #: src/properties.ui:18 msgid "Name" msgstr "Naam" #: src/properties.ui:49 msgid "Location" msgstr "Locatie" #: src/properties.ui:80 msgid "Type" msgstr "Soort" #: src/properties.ui:111 msgid "Size" msgstr "Grootte" #: src/properties.ui:150 msgid "Created" msgstr "Aanmaakdatum" #: src/properties.ui:181 msgid "Modified" msgstr "Wijzigingsdatum" #: src/properties.ui:212 msgid "Accessed" msgstr "Geopend" #: src/properties.ui:251 msgid "Owner" msgstr "Eigenaar" #: src/properties.ui:282 msgid "Group" msgstr "Groep" #: src/properties.ui:313 msgid "Owner can" msgstr "Eigenaar kan" #: src/properties.ui:344 msgid "Group can" msgstr "Groep kan" #: src/properties.ui:375 msgid "Others can" msgstr "Anderen kunnen" #: src/window.ui:648 msgid "Properties" msgstr "Eigenschappen" #: src/window.ui:699 msgid "About" msgstr "Over" #: src/files.py:292 src/window.py:491 #, python-format msgid "%s already exists" msgstr "‘%s’ bestaat al" #: src/files.py:410 msgid "New Folder" msgstr "Nieuwe map" #: src/passphrase.py:68 msgid "Sorry, that didn't work" msgstr "Er is iets misgegaan" #: src/places.py:52 msgid "Trash" msgstr "Prullenbak" #: src/places.py:89 msgid "Places" msgstr "Locaties" #: src/places.py:93 msgid "Devices" msgstr "Apparaten" #: src/places.py:103 msgid "Home" msgstr "Persoonlijke map" #: src/places.py:155 msgid "System" msgstr "Systeem" #: src/places.py:163 msgid "Host" msgstr "Host" #: src/places.py:177 msgid "No places found" msgstr "Geen locaties aangetroffen" #: src/window.py:501 msgid "No permissions on this directory" msgstr "Je hebt geen rechten op deze map" #: src/window.py:514 msgid "Opening" msgstr "Bezig met openen…" #: src/window.py:532 src/window.py:1075 src/window.py:1085 #, python-format msgid "Could not open %s" msgstr "‘%s’ kan niet worden geopend" #: src/window.py:551 src/window.py:580 msgid "Loading" msgstr "Bezig met laden…" #: src/window.py:580 #, python-format msgid "Could not load %s" msgstr "‘%s’ kan niet worden geladen" #: src/window.py:630 src/window.py:941 #, python-format msgid "these %d files" msgstr "deze %d bestanden" #: src/window.py:632 src/window.py:943 #, python-format msgid "Delete %s permanently?" msgstr "Wil je ‘%s’ permanent verwijderen?" #: src/window.py:652 #, python-format msgid "%s will be moved" msgstr "‘%s’ wordt verplaatst" #: src/window.py:654 #, python-format msgid "%d files will be moved" msgstr "%d bestanden worden verplaatst" #: src/window.py:669 #, python-format msgid "%s will be copied" msgstr "‘%s’ wordt gekopieerd" #: src/window.py:671 #, python-format msgid "%d files will be copied" msgstr "%d bestanden worden gekopieerd" #: src/window.py:695 src/window.py:874 msgid "Files will be overwritten, proceed?" msgstr "De bestanden worden overschreven. Wil je doorgaan?" #: src/window.py:710 msgid "Pasting" msgstr "Bezig met plakken…" #: src/window.py:734 #, python-format msgid "%s of %s" msgstr "%s van %s" #: src/window.py:749 #, python-format msgid "Could not paste %s" msgstr "‘%s’ kan niet worden geplakt" #: src/window.py:801 src/window.py:969 msgid "Deleting" msgstr "Bezig met verwijderen…" #: src/window.py:826 src/window.py:993 #, python-format msgid "Could not delete %s" msgstr "‘%s’ kan niet worden verwijderd" #: src/window.py:849 msgid "Stopping" msgstr "Bezig met afbreken…" #: src/window.py:897 msgid "Restoring" msgstr "Bezig met herstellen…" #: src/window.py:926 #, python-format msgid "Could not restore %s" msgstr "‘%s’ kan niet worden hersteld" #: src/window.py:1006 msgid "Removing device, please wait" msgstr "Bezig met afkoppelen van apparaat…" #: src/window.py:1010 msgid "Device can be removed" msgstr "Het apparaat kan worden afgekoppeld" #: src/window.py:1022 msgid "Device is busy, can't be removed" msgstr "Het apparaat is bezig en kan daarom niet worden afgekoppeld" #: src/worker.py:512 msgid "read" msgstr "lezen" #: src/worker.py:514 msgid "write" msgstr "schrijven" #: src/worker.py:516 msgid "execute" msgstr "uitvoeren" #: src/worker.py:521 msgid "do nothing" msgstr "niets doen" #: src/worker.py:601 msgid "Calculating..." msgstr "Bezig met berekenen…" Portfolio-1.0.2/po/portfolio.pot000066400000000000000000000142641476103320100166520ustar00rootroot00000000000000# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the portfolio package. # FIRST AUTHOR , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: portfolio\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-09 16:34-0300\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=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.desktop.in:4 #: data/dev.tchx84.Portfolio.metainfo.xml.in:11 src/about.ui:30 msgid "Portfolio" msgstr "" #: data/dev.tchx84.Portfolio.desktop.in:8 msgid "File Manager" msgstr "" #: data/dev.tchx84.Portfolio.desktop.in:9 #: data/dev.tchx84.Portfolio.metainfo.xml.in:12 msgid "Manage files on the go" msgstr "" #: data/dev.tchx84.Portfolio.desktop.in:15 msgid "folder;manager;explore;disk;filesystem;" msgstr "" #: data/dev.tchx84.Portfolio.metainfo.xml.in:14 src/about.ui:58 msgid "" "A minimalist file manager for those who want to use Linux mobile devices." msgstr "" #: data/dev.tchx84.Portfolio.metainfo.xml.in:20 msgid "Portfolio showing its home view" msgstr "" #: data/dev.tchx84.Portfolio.metainfo.xml.in:24 msgid "Portfolio showing directories and files" msgstr "" #: data/dev.tchx84.Portfolio.metainfo.xml.in:28 msgid "Portfolio showing file operations" msgstr "" #: data/dev.tchx84.Portfolio.metainfo.xml.in:32 msgid "Portfolio showing its file properties view" msgstr "" #: data/dev.tchx84.Portfolio.metainfo.xml.in:36 msgid "Portfolio showing its trash management view" msgstr "" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.metainfo.xml.in:41 msgid "Martin Abente Lahaye" msgstr "" #: src/about.ui:73 msgid "Website" msgstr "" #: src/about.ui:102 msgid "" "This program comes with absolutely no warranty. See the GNU General Public License, version 3 or " "later for details." msgstr "" #: src/about.ui:118 msgid "" "Portfolio logo is a modifed version of Jorgen Carlberg's work. The original " "version can be found here , licensed under CC BY 3.0 ." msgstr "" #: src/about.ui:139 msgid "Created by" msgstr "" #: src/about.ui:166 msgid "Artwork by" msgstr "" #: src/about.ui:194 msgid "Translated by" msgstr "" #: src/about.ui:227 msgid "Contributions by" msgstr "" #: src/menu.ui:21 msgid "Filter" msgstr "" #: src/menu.ui:34 msgid "Show Hidden Files" msgstr "" #: src/menu.ui:57 msgid "Sort" msgstr "" #: src/menu.ui:69 msgid "A-Z" msgstr "" #: src/menu.ui:84 msgid "Last Modified" msgstr "" #: src/menu.ui:109 msgid "Help" msgstr "" #: src/menu.ui:118 msgid "About Portfolio" msgstr "" #: src/placeholder.ui:16 msgid "No files found" msgstr "" #: src/properties.ui:18 msgid "Name" msgstr "" #: src/properties.ui:49 msgid "Location" msgstr "" #: src/properties.ui:80 msgid "Type" msgstr "" #: src/properties.ui:111 msgid "Size" msgstr "" #: src/properties.ui:150 msgid "Created" msgstr "" #: src/properties.ui:181 msgid "Modified" msgstr "" #: src/properties.ui:212 msgid "Accessed" msgstr "" #: src/properties.ui:251 msgid "Owner" msgstr "" #: src/properties.ui:282 msgid "Group" msgstr "" #: src/properties.ui:313 msgid "Owner can" msgstr "" #: src/properties.ui:344 msgid "Group can" msgstr "" #: src/properties.ui:375 msgid "Others can" msgstr "" #: src/window.ui:648 msgid "Properties" msgstr "" #: src/window.ui:699 msgid "About" msgstr "" #: src/files.py:292 src/window.py:491 #, python-format msgid "%s already exists" msgstr "" #: src/files.py:410 msgid "New Folder" msgstr "" #: src/passphrase.py:68 msgid "Sorry, that didn't work" msgstr "" #: src/places.py:52 msgid "Trash" msgstr "" #: src/places.py:89 msgid "Places" msgstr "" #: src/places.py:93 msgid "Devices" msgstr "" #: src/places.py:103 msgid "Home" msgstr "" #: src/places.py:155 msgid "System" msgstr "" #: src/places.py:163 msgid "Host" msgstr "" #: src/places.py:177 msgid "No places found" msgstr "" #: src/window.py:501 msgid "No permissions on this directory" msgstr "" #: src/window.py:514 msgid "Opening" msgstr "" #: src/window.py:532 src/window.py:1075 src/window.py:1085 #, python-format msgid "Could not open %s" msgstr "" #: src/window.py:551 src/window.py:580 msgid "Loading" msgstr "" #: src/window.py:580 #, python-format msgid "Could not load %s" msgstr "" #: src/window.py:630 src/window.py:941 #, python-format msgid "these %d files" msgstr "" #: src/window.py:632 src/window.py:943 #, python-format msgid "Delete %s permanently?" msgstr "" #: src/window.py:652 #, python-format msgid "%s will be moved" msgstr "" #: src/window.py:654 #, python-format msgid "%d files will be moved" msgstr "" #: src/window.py:669 #, python-format msgid "%s will be copied" msgstr "" #: src/window.py:671 #, python-format msgid "%d files will be copied" msgstr "" #: src/window.py:695 src/window.py:874 msgid "Files will be overwritten, proceed?" msgstr "" #: src/window.py:710 msgid "Pasting" msgstr "" #: src/window.py:734 #, python-format msgid "%s of %s" msgstr "" #: src/window.py:749 #, python-format msgid "Could not paste %s" msgstr "" #: src/window.py:801 src/window.py:969 msgid "Deleting" msgstr "" #: src/window.py:826 src/window.py:993 #, python-format msgid "Could not delete %s" msgstr "" #: src/window.py:849 msgid "Stopping" msgstr "" #: src/window.py:897 msgid "Restoring" msgstr "" #: src/window.py:926 #, python-format msgid "Could not restore %s" msgstr "" #: src/window.py:1006 msgid "Removing device, please wait" msgstr "" #: src/window.py:1010 msgid "Device can be removed" msgstr "" #: src/window.py:1022 msgid "Device is busy, can't be removed" msgstr "" #: src/worker.py:512 msgid "read" msgstr "" #: src/worker.py:514 msgid "write" msgstr "" #: src/worker.py:516 msgid "execute" msgstr "" #: src/worker.py:521 msgid "do nothing" msgstr "" #: src/worker.py:601 msgid "Calculating..." msgstr "" Portfolio-1.0.2/po/pt_BR.po000066400000000000000000000201011476103320100154420ustar00rootroot00000000000000# Brazilian Portuguese translation for portfolio. # Copyright (C) 2022 THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the portfolio package. # Rafael Fontenelle , 2022. # msgid "" msgstr "" "Project-Id-Version: portfolio\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-09 16:34-0300\n" "PO-Revision-Date: 2022-02-03 19:21-0300\n" "Last-Translator: Rafael Fontenelle \n" "Language-Team: Brazilian Portuguese \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" "X-Generator: Gtranslator 40.0\n" "X-Project-Style: gnome\n" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.desktop.in:4 #: data/dev.tchx84.Portfolio.metainfo.xml.in:11 src/about.ui:30 msgid "Portfolio" msgstr "Portfólio" #: data/dev.tchx84.Portfolio.desktop.in:8 msgid "File Manager" msgstr "Gerenciador de arquivos" #: data/dev.tchx84.Portfolio.desktop.in:9 #: data/dev.tchx84.Portfolio.metainfo.xml.in:12 msgid "Manage files on the go" msgstr "Gerencie arquivos em qualquer lugar" #: data/dev.tchx84.Portfolio.desktop.in:15 msgid "folder;manager;explore;disk;filesystem;" msgstr "pasta;gerenciador;explorar;disco;sistema de arquivos;" #: data/dev.tchx84.Portfolio.metainfo.xml.in:14 src/about.ui:58 msgid "" "A minimalist file manager for those who want to use Linux mobile devices." msgstr "" "Um gerenciador de arquivos minimalista para quem deseja usar dispositivos " "móveis Linux." #: data/dev.tchx84.Portfolio.metainfo.xml.in:20 msgid "Portfolio showing its home view" msgstr "" #: data/dev.tchx84.Portfolio.metainfo.xml.in:24 msgid "Portfolio showing directories and files" msgstr "" #: data/dev.tchx84.Portfolio.metainfo.xml.in:28 msgid "Portfolio showing file operations" msgstr "" #: data/dev.tchx84.Portfolio.metainfo.xml.in:32 msgid "Portfolio showing its file properties view" msgstr "" #: data/dev.tchx84.Portfolio.metainfo.xml.in:36 msgid "Portfolio showing its trash management view" msgstr "" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.metainfo.xml.in:41 msgid "Martin Abente Lahaye" msgstr "Martin Abente Lahaye" #: src/about.ui:73 msgid "Website" msgstr "Site" #: src/about.ui:102 msgid "" "This program comes with absolutely no warranty. See the GNU General Public License, version 3 or " "later for details." msgstr "" "Este programa vem com nenhuma garantia. Consulte a Licença Pública Geral GNU, versão 3 ou " "posterior para obter detalhes." #: src/about.ui:118 msgid "" "Portfolio logo is a modifed version of Jorgen Carlberg's work. The original " "version can be found here , licensed under CC BY 3.0 ." msgstr "" "O logotipo do portfólio é uma versão modificada do trabalho de Jorgen " "Carlberg. A versão original pode ser encontrada aqui, licenciada sob CC BY 3.0." #: src/about.ui:139 msgid "Created by" msgstr "Criado por" #: src/about.ui:166 msgid "Artwork by" msgstr "Art por" #: src/about.ui:194 msgid "Translated by" msgstr "Traduzido por" #: src/about.ui:227 msgid "Contributions by" msgstr "Contribuições por" #: src/menu.ui:21 msgid "Filter" msgstr "Filtro" #: src/menu.ui:34 msgid "Show Hidden Files" msgstr "Mostrar arquivos ocultos" #: src/menu.ui:57 msgid "Sort" msgstr "Ordenar" #: src/menu.ui:69 msgid "A-Z" msgstr "A-Z" #: src/menu.ui:84 msgid "Last Modified" msgstr "última modificação" #: src/menu.ui:109 msgid "Help" msgstr "Ajuda" #: src/menu.ui:118 msgid "About Portfolio" msgstr "Sobre o Portfólio" #: src/placeholder.ui:16 msgid "No files found" msgstr "Nenhum arquivo encontrado" #: src/properties.ui:18 msgid "Name" msgstr "Nome" #: src/properties.ui:49 msgid "Location" msgstr "Local" #: src/properties.ui:80 msgid "Type" msgstr "Tipo" #: src/properties.ui:111 msgid "Size" msgstr "Tamanho" #: src/properties.ui:150 msgid "Created" msgstr "Criado" #: src/properties.ui:181 msgid "Modified" msgstr "Modificado" #: src/properties.ui:212 msgid "Accessed" msgstr "Acessado" #: src/properties.ui:251 msgid "Owner" msgstr "Proprietário" #: src/properties.ui:282 msgid "Group" msgstr "Grupo" #: src/properties.ui:313 msgid "Owner can" msgstr "Proprietário pode" #: src/properties.ui:344 msgid "Group can" msgstr "Grupo pode" #: src/properties.ui:375 msgid "Others can" msgstr "Outros podem" #: src/window.ui:648 msgid "Properties" msgstr "Propriedades" #: src/window.ui:699 msgid "About" msgstr "Sobre" #: src/files.py:292 src/window.py:491 #, python-format msgid "%s already exists" msgstr "%s já existe" #: src/files.py:410 msgid "New Folder" msgstr "Nova pasta" #: src/passphrase.py:68 msgid "Sorry, that didn't work" msgstr "Sinto muito, isso não funcionou" #: src/places.py:52 msgid "Trash" msgstr "Lixeira" #: src/places.py:89 msgid "Places" msgstr "Locais" #: src/places.py:93 msgid "Devices" msgstr "Dispositivos" #: src/places.py:103 msgid "Home" msgstr "Home" #: src/places.py:155 msgid "System" msgstr "Sistema" #: src/places.py:163 msgid "Host" msgstr "Host" #: src/places.py:177 msgid "No places found" msgstr "Nenhum local encontrado" #: src/window.py:501 msgid "No permissions on this directory" msgstr "Nenhuma permissão neste diretório" #: src/window.py:514 msgid "Opening" msgstr "Abrindo" #: src/window.py:532 src/window.py:1075 src/window.py:1085 #, python-format msgid "Could not open %s" msgstr "Não foi possível abrir %s" #: src/window.py:551 src/window.py:580 msgid "Loading" msgstr "Carregando" #: src/window.py:580 #, python-format msgid "Could not load %s" msgstr "Não foi possível carregar %s" #: src/window.py:630 src/window.py:941 #, python-format msgid "these %d files" msgstr "esses %d arquivos" #: src/window.py:632 src/window.py:943 #, python-format msgid "Delete %s permanently?" msgstr "Excluir %s permanentemente?" #: src/window.py:652 #, python-format msgid "%s will be moved" msgstr "%s será movido" #: src/window.py:654 #, python-format msgid "%d files will be moved" msgstr "%d arquivos serão movidos" #: src/window.py:669 #, python-format msgid "%s will be copied" msgstr "%s será copiado" #: src/window.py:671 #, python-format msgid "%d files will be copied" msgstr "%d arquivos serão copiados" #: src/window.py:695 src/window.py:874 msgid "Files will be overwritten, proceed?" msgstr "Arquivos serão substituídos. Continuar?" #: src/window.py:710 msgid "Pasting" msgstr "Colando" #: src/window.py:734 #, python-format msgid "%s of %s" msgstr "%s de %s" #: src/window.py:749 #, python-format msgid "Could not paste %s" msgstr "Não foi possível colar %s" #: src/window.py:801 src/window.py:969 msgid "Deleting" msgstr "Excluindo" #: src/window.py:826 src/window.py:993 #, python-format msgid "Could not delete %s" msgstr "Não foi possível excluir %s" #: src/window.py:849 msgid "Stopping" msgstr "Parando" #: src/window.py:897 msgid "Restoring" msgstr "Restaurando" #: src/window.py:926 #, python-format msgid "Could not restore %s" msgstr "Não foi possível restaurar %s" #: src/window.py:1006 msgid "Removing device, please wait" msgstr "Removendo dispositivo, por favor aguarde" #: src/window.py:1010 msgid "Device can be removed" msgstr "O dispositivo pode ser removido" #: src/window.py:1022 msgid "Device is busy, can't be removed" msgstr "O dispositivo está ocupado, não pode ser removido" #: src/worker.py:512 msgid "read" msgstr "ler" #: src/worker.py:514 msgid "write" msgstr "escrever" #: src/worker.py:516 msgid "execute" msgstr "executar" #: src/worker.py:521 msgid "do nothing" msgstr "fazer nada" #: src/worker.py:601 msgid "Calculating..." msgstr "Calculando..." Portfolio-1.0.2/po/sv.po000066400000000000000000000200121476103320100150650ustar00rootroot00000000000000# Swedish translation for Portfolio. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the portfolio package. # Ã…ke Engelbrektson , 2020-2024. # msgid "" msgstr "" "Project-Id-Version: portfolio\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-09 16:34-0300\n" "PO-Revision-Date: 2024-02-09 22:23+0100\n" "Last-Translator: Ã…ke Engelbrektson \n" "Language-Team: Svenska SprÃ¥kfiler \n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.4.2\n" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.desktop.in:4 #: data/dev.tchx84.Portfolio.metainfo.xml.in:11 src/about.ui:30 msgid "Portfolio" msgstr "Portfolio" #: data/dev.tchx84.Portfolio.desktop.in:8 msgid "File Manager" msgstr "Filhanterare" #: data/dev.tchx84.Portfolio.desktop.in:9 #: data/dev.tchx84.Portfolio.metainfo.xml.in:12 msgid "Manage files on the go" msgstr "Hantera filer pÃ¥ sprÃ¥ng" #: data/dev.tchx84.Portfolio.desktop.in:15 msgid "folder;manager;explore;disk;filesystem;" msgstr "mapp;hanterare:utforska;disk;filsystem;" #: data/dev.tchx84.Portfolio.metainfo.xml.in:14 src/about.ui:58 msgid "" "A minimalist file manager for those who want to use Linux mobile devices." msgstr "" "En minimalistisk filhanterare för de som vill använda mobila enheter med " "Linux." #: data/dev.tchx84.Portfolio.metainfo.xml.in:20 msgid "Portfolio showing its home view" msgstr "Portfolio i hemvy" #: data/dev.tchx84.Portfolio.metainfo.xml.in:24 msgid "Portfolio showing directories and files" msgstr "Portfolio i mapp- och filvy" #: data/dev.tchx84.Portfolio.metainfo.xml.in:28 msgid "Portfolio showing file operations" msgstr "Portfolios filÃ¥tgärder" #: data/dev.tchx84.Portfolio.metainfo.xml.in:32 msgid "Portfolio showing its file properties view" msgstr "Portfolios vy över filegenskaper" #: data/dev.tchx84.Portfolio.metainfo.xml.in:36 msgid "Portfolio showing its trash management view" msgstr "Portfolios vy över papperskorgshantering" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.metainfo.xml.in:41 msgid "Martin Abente Lahaye" msgstr "Martin Abente Lahaye" #: src/about.ui:73 msgid "Website" msgstr "Webbplats" #: src/about.ui:102 msgid "" "This program comes with absolutely no warranty. See the GNU General Public License, version 3 or " "later for details." msgstr "" "Detta program ges ut utan nÃ¥gon som helst garanti. Se GNU General Public License, version 3 eller " "senare för detaljerad info." #: src/about.ui:118 msgid "" "Portfolio logo is a modifed version of Jorgen Carlberg's work. The original " "version can be found here , licensed under CC BY 3.0 ." msgstr "" "Portfolios logotyp är en modifierad version av Jorgen Carlbergs arbete. " "Originalversionen kan hittas här , licensierad under CC BY 3.0 ." #: src/about.ui:139 msgid "Created by" msgstr "Skapad av" #: src/about.ui:166 msgid "Artwork by" msgstr "Bildkonst av" #: src/about.ui:194 msgid "Translated by" msgstr "Översatt av" #: src/about.ui:227 msgid "Contributions by" msgstr "Kodbidrag av" #: src/menu.ui:21 msgid "Filter" msgstr "Filter" #: src/menu.ui:34 msgid "Show Hidden Files" msgstr "Visa dolda filer" #: src/menu.ui:57 msgid "Sort" msgstr "Sortera" #: src/menu.ui:69 msgid "A-Z" msgstr "A-Z" #: src/menu.ui:84 msgid "Last Modified" msgstr "Ändrad" #: src/menu.ui:109 msgid "Help" msgstr "Hjälp" #: src/menu.ui:118 msgid "About Portfolio" msgstr "Om Portfolio" #: src/placeholder.ui:16 msgid "No files found" msgstr "Inga filer hittades" #: src/properties.ui:18 msgid "Name" msgstr "Namn" #: src/properties.ui:49 msgid "Location" msgstr "Plats" #: src/properties.ui:80 msgid "Type" msgstr "Typ" #: src/properties.ui:111 msgid "Size" msgstr "Storlek" #: src/properties.ui:150 msgid "Created" msgstr "Skapad" #: src/properties.ui:181 msgid "Modified" msgstr "Ändrad" #: src/properties.ui:212 msgid "Accessed" msgstr "Öppnad" #: src/properties.ui:251 msgid "Owner" msgstr "Ägare" #: src/properties.ui:282 msgid "Group" msgstr "Grupp" #: src/properties.ui:313 msgid "Owner can" msgstr "Ägare kan" #: src/properties.ui:344 msgid "Group can" msgstr "Grupp kan" #: src/properties.ui:375 msgid "Others can" msgstr "Andra kan" #: src/window.ui:648 msgid "Properties" msgstr "Egenskaper" #: src/window.ui:699 msgid "About" msgstr "Om" #: src/files.py:292 src/window.py:491 #, python-format msgid "%s already exists" msgstr "%s finns redan" #: src/files.py:410 msgid "New Folder" msgstr "Ny mapp" #: src/passphrase.py:68 msgid "Sorry, that didn't work" msgstr "Beklagar, det fungerade inte." #: src/places.py:52 msgid "Trash" msgstr "Papperskorg" #: src/places.py:89 msgid "Places" msgstr "Platser" #: src/places.py:93 msgid "Devices" msgstr "Enheter" #: src/places.py:103 msgid "Home" msgstr "Hem" #: src/places.py:155 msgid "System" msgstr "System" #: src/places.py:163 msgid "Host" msgstr "Värd" #: src/places.py:177 msgid "No places found" msgstr "Inga platser hittades" #: src/window.py:501 msgid "No permissions on this directory" msgstr "Du saknar rättigheter i den här mappen" #: src/window.py:514 msgid "Opening" msgstr "Öppnar" #: src/window.py:532 src/window.py:1075 src/window.py:1085 #, python-format msgid "Could not open %s" msgstr "Kunde inte öppna %s" #: src/window.py:551 src/window.py:580 msgid "Loading" msgstr "Läser in" #: src/window.py:580 #, python-format msgid "Could not load %s" msgstr "Kunde inte läsa in %s" #: src/window.py:630 src/window.py:941 #, python-format msgid "these %d files" msgstr "dessa %d filer" #: src/window.py:632 src/window.py:943 #, python-format msgid "Delete %s permanently?" msgstr "Vill du ta bort %s permanent?" #: src/window.py:652 #, python-format msgid "%s will be moved" msgstr "%s kommer att flyttas" #: src/window.py:654 #, python-format msgid "%d files will be moved" msgstr "%d filer kommer att flyttas" #: src/window.py:669 #, python-format msgid "%s will be copied" msgstr "%s kommer att kopieras" #: src/window.py:671 #, python-format msgid "%d files will be copied" msgstr "%d filer kommer att kopieras" #: src/window.py:695 src/window.py:874 msgid "Files will be overwritten, proceed?" msgstr "Filer kommer att skrivas över, fortsätt?" #: src/window.py:710 msgid "Pasting" msgstr "Klistrar in" #: src/window.py:734 #, python-format msgid "%s of %s" msgstr "%s av %s" #: src/window.py:749 #, python-format msgid "Could not paste %s" msgstr "Kunde inte klistra in %s" #: src/window.py:801 src/window.py:969 msgid "Deleting" msgstr "Tar bort" #: src/window.py:826 src/window.py:993 #, python-format msgid "Could not delete %s" msgstr "Kunde inte ta bort %s" #: src/window.py:849 msgid "Stopping" msgstr "Stoppar" #: src/window.py:897 msgid "Restoring" msgstr "Ã…terställer" #: src/window.py:926 #, python-format msgid "Could not restore %s" msgstr "Kunde inte Ã¥terställa %s" #: src/window.py:1006 msgid "Removing device, please wait" msgstr "Tar bort enheten, vänta" #: src/window.py:1010 msgid "Device can be removed" msgstr "Enheten kan tas bort" #: src/window.py:1022 msgid "Device is busy, can't be removed" msgstr "Enheten är upptagen, kan inte tas bort" #: src/worker.py:512 msgid "read" msgstr "läs" #: src/worker.py:514 msgid "write" msgstr "skriv" #: src/worker.py:516 msgid "execute" msgstr "kör" #: src/worker.py:521 msgid "do nothing" msgstr "gör ingenting" #: src/worker.py:601 msgid "Calculating..." msgstr "Beräknar..." Portfolio-1.0.2/po/tr.po000066400000000000000000000204051476103320100150700ustar00rootroot00000000000000# Turkish translation for dev.tchx84.Portfolio. # Copyright (C) 2024 dev.tchx84.Portfolio's COPYRIGHT HOLDER # This file is distributed under the same license as the dev.tchx84.Portfolio package. # # Sabri Ünal , 2024. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-11-17 20:30+0300\n" "PO-Revision-Date: 2024-11-17 20:44+0300\n" "Last-Translator: Sabri Ünal \n" "Language-Team: Turkish \n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Poedit 3.5\n" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.desktop.in:4 #: data/dev.tchx84.Portfolio.metainfo.xml.in:11 src/about.ui:30 msgid "Portfolio" msgstr "Portfolio" #: data/dev.tchx84.Portfolio.desktop.in:8 msgid "File Manager" msgstr "Dosya Yöneticisi" #: data/dev.tchx84.Portfolio.desktop.in:9 #: data/dev.tchx84.Portfolio.metainfo.xml.in:12 msgid "Manage files on the go" msgstr "Hareketliyken dosyaları yönetin" #: data/dev.tchx84.Portfolio.desktop.in:15 msgid "folder;manager;explore;disk;filesystem;" msgstr "" "portfolio;portfolyo;folder;manager;explore;disk;filesystem;klasör;dizin;" "yönetici;gezin;keÅŸfet;disk;dosya sistemi;dosyasistemi;" #: data/dev.tchx84.Portfolio.metainfo.xml.in:14 src/about.ui:58 msgid "" "A minimalist file manager for those who want to use Linux mobile devices." msgstr "" "Linux mobil aygıtlarını kullanmak isteyenler için minimalist dosya " "yöneticisi." #: data/dev.tchx84.Portfolio.metainfo.xml.in:20 msgid "Portfolio showing its home view" msgstr "Portfolio ana sayfayı gösterirken" #: data/dev.tchx84.Portfolio.metainfo.xml.in:24 msgid "Portfolio showing directories and files" msgstr "Portfolio dizinleri ve dosyaları gösterirken" #: data/dev.tchx84.Portfolio.metainfo.xml.in:28 msgid "Portfolio showing file operations" msgstr "Portfolio dosya iÅŸlemlerini gösterirken" #: data/dev.tchx84.Portfolio.metainfo.xml.in:32 msgid "Portfolio showing its file properties view" msgstr "Portfolio dosya özelliklerini gösterirken" #: data/dev.tchx84.Portfolio.metainfo.xml.in:36 msgid "Portfolio showing its trash management view" msgstr "Portfolio çöp kutusu yönetimini gösterirken" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.metainfo.xml.in:41 msgid "Martin Abente Lahaye" msgstr "Martin Abente Lahaye" #: src/about.ui:73 msgid "Website" msgstr "Web Sitesi" #: src/about.ui:102 msgid "" "This program comes with absolutely no warranty. See the GNU General Public License, version 3 or " "later for details." msgstr "" "Bu program kesinlikle hiçbir garanti vermemektedir. Ayrıntılar için GNU Genel Kamu Lisansı, sürüm " "3 ya da sonrası sayfasına bakınız." #: src/about.ui:118 msgid "" "Portfolio logo is a modifed version of Jorgen Carlberg's work. The original " "version can be found here , licensed under CC BY 3.0 ." msgstr "" "Portfolio logosu Jorgen Carlberg çalışmasının deÄŸiÅŸtirilmiÅŸ bir sürümüdür. " "Özgün sürüme buradan, CC BY 3.0 lisansı altında ulaÅŸabilirsiniz." #: src/about.ui:139 msgid "Created by" msgstr "OluÅŸturan" #: src/about.ui:166 msgid "Artwork by" msgstr "Grafikler" #: src/about.ui:194 msgid "Translated by" msgstr "Çevirenler" #: src/about.ui:231 msgid "Contributions by" msgstr "Katkıcılar" #: src/menu.ui:21 msgid "Filter" msgstr "Süz" #: src/menu.ui:34 msgid "Show Hidden Files" msgstr "Gizli Dosyaları Göster" #: src/menu.ui:57 msgid "Sort" msgstr "Sırala" #: src/menu.ui:69 msgid "A-Z" msgstr "A-Z" #: src/menu.ui:84 msgid "Last Modified" msgstr "Son DeÄŸiÅŸtirilme" #: src/menu.ui:109 msgid "Help" msgstr "Yardım" #: src/menu.ui:118 msgid "About Portfolio" msgstr "Portfolio Hakkında" #: src/placeholder.ui:16 msgid "No files found" msgstr "Dosya bulunamadı" #: src/properties.ui:18 msgid "Name" msgstr "Ad" #: src/properties.ui:49 msgid "Location" msgstr "Konum" #: src/properties.ui:80 msgid "Type" msgstr "Tür" #: src/properties.ui:111 msgid "Size" msgstr "Boyut" #: src/properties.ui:150 msgid "Created" msgstr "OluÅŸturulma" #: src/properties.ui:181 msgid "Modified" msgstr "DeÄŸiÅŸtirilme" #: src/properties.ui:212 msgid "Accessed" msgstr "EriÅŸilme" #: src/properties.ui:251 msgid "Owner" msgstr "Sahip" #: src/properties.ui:282 msgid "Group" msgstr "Grup" #: src/properties.ui:313 msgid "Owner can" msgstr "Sahibi yapabilir" #: src/properties.ui:344 msgid "Group can" msgstr "Grup yapabilir" #: src/properties.ui:375 msgid "Others can" msgstr "DiÄŸerleri yapabilir" #: src/window.ui:648 msgid "Properties" msgstr "Özellikler" #: src/window.ui:699 msgid "About" msgstr "Hakkında" #: src/files.py:292 src/window.py:491 #, python-format msgid "%s already exists" msgstr "%s zaten var" #: src/files.py:410 msgid "New Folder" msgstr "Yeni Klasör" #: src/passphrase.py:68 msgid "Sorry, that didn't work" msgstr "Üzgünüm, iÅŸe yaramadı" #: src/places.py:52 msgid "Trash" msgstr "Çöp" #: src/places.py:89 msgid "Places" msgstr "Yerler" #: src/places.py:93 msgid "Devices" msgstr "Aygıtlar" #: src/places.py:103 msgid "Home" msgstr "Ev" #: src/places.py:155 msgid "System" msgstr "Sistem" #: src/places.py:163 msgid "Host" msgstr "Ana makine" #: src/places.py:177 msgid "No places found" msgstr "Yer bulunamadı" #: src/window.py:501 msgid "No permissions on this directory" msgstr "Bu dizinde izin yok" #: src/window.py:514 msgid "Opening" msgstr "Açılıyor" #: src/window.py:532 src/window.py:1075 src/window.py:1085 #, python-format msgid "Could not open %s" msgstr "“%s†açılamadı" #: src/window.py:551 src/window.py:580 msgid "Loading" msgstr "Yükleniyor" #: src/window.py:580 #, python-format msgid "Could not load %s" msgstr "“%s†yüklenemedi" #: src/window.py:630 src/window.py:941 #, python-format msgid "these %d files" msgstr "%d dosya" #: src/window.py:632 src/window.py:943 #, python-format msgid "Delete %s permanently?" msgstr "“%s†kalıcı olarak silinsin mi?" #: src/window.py:652 #, python-format msgid "%s will be moved" msgstr "“%s†taşınacak" #: src/window.py:654 #, python-format msgid "%d files will be moved" msgstr "%d dosya taşınacak" #: src/window.py:669 #, python-format msgid "%s will be copied" msgstr "“%s†kopyalanacak" #: src/window.py:671 #, python-format msgid "%d files will be copied" msgstr "%d dosya kopyalanacak" #: src/window.py:695 src/window.py:874 msgid "Files will be overwritten, proceed?" msgstr "Dosyaların üzerine yazılacak, devam edilsin mi?" #: src/window.py:710 msgid "Pasting" msgstr "Yapıştırılıyor" #: src/window.py:734 #, python-format msgid "%s of %s" msgstr "%s / %s" #: src/window.py:749 #, python-format msgid "Could not paste %s" msgstr "“%s†yapıştırılamadı" #: src/window.py:801 src/window.py:969 msgid "Deleting" msgstr "Siliniyor" #: src/window.py:826 src/window.py:993 #, python-format msgid "Could not delete %s" msgstr "“%s†silinemedi" #: src/window.py:849 msgid "Stopping" msgstr "Durduruluyor" #: src/window.py:897 msgid "Restoring" msgstr "Geri yükleniyor" #: src/window.py:926 #, python-format msgid "Could not restore %s" msgstr "“%s†geri yüklenemedi" #: src/window.py:1006 msgid "Removing device, please wait" msgstr "Aygıt çıkarılıyor, lütfen bekleyin" #: src/window.py:1010 msgid "Device can be removed" msgstr "Aygıt çıkarılabilir" #: src/window.py:1022 msgid "Device is busy, can't be removed" msgstr "Aygıt meÅŸgul, çıkarılamıyor" #: src/worker.py:512 msgid "read" msgstr "oku" #: src/worker.py:514 msgid "write" msgstr "yaz" #: src/worker.py:516 msgid "execute" msgstr "çalıştır" #: src/worker.py:521 msgid "do nothing" msgstr "hiçbir ÅŸey yapma" #: src/worker.py:601 msgid "Calculating..." msgstr "Hesaplanıyor..." Portfolio-1.0.2/po/zh_CN.po000066400000000000000000000173611476103320100154530ustar00rootroot00000000000000# Chinese translations for portfolio package. # Copyright (C) 2024 THE portfolio'S COPYRIGHT HOLDER # This file is distributed under the same license as the portfolio package. # , 2024. # msgid "" msgstr "" "Project-Id-Version: portfolio\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-09 16:34-0300\n" "PO-Revision-Date: 2024-02-21 18:30+0800\n" "Last-Translator: \n" "Language-Team: none\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.desktop.in:4 #: data/dev.tchx84.Portfolio.metainfo.xml.in:11 src/about.ui:30 msgid "Portfolio" msgstr "Portfolio" #: data/dev.tchx84.Portfolio.desktop.in:8 msgid "File Manager" msgstr "文件管ç†å™¨" #: data/dev.tchx84.Portfolio.desktop.in:9 #: data/dev.tchx84.Portfolio.metainfo.xml.in:12 msgid "Manage files on the go" msgstr "éšæ—¶éšåœ°ç®¡ç†æ–‡ä»¶" #: data/dev.tchx84.Portfolio.desktop.in:15 msgid "folder;manager;explore;disk;filesystem;" msgstr "文件夹;管ç†åš£;æµè§ˆ;硬盘;文件系统;" #: data/dev.tchx84.Portfolio.metainfo.xml.in:14 src/about.ui:58 msgid "" "A minimalist file manager for those who want to use Linux mobile devices." msgstr "专为Linux移动设备适é…çš„æžç®€æ–‡ä»¶ç®¡ç†å™¨" #: data/dev.tchx84.Portfolio.metainfo.xml.in:20 msgid "Portfolio showing its home view" msgstr "Portfolio正在显示它的主视图" #: data/dev.tchx84.Portfolio.metainfo.xml.in:24 msgid "Portfolio showing directories and files" msgstr "Portfolioæ­£åœ¨æ˜¾ç¤ºç›®å½•åŠæ–‡ä»¶" #: data/dev.tchx84.Portfolio.metainfo.xml.in:28 msgid "Portfolio showing file operations" msgstr "Portfolio正在显示文件æ“作" #: data/dev.tchx84.Portfolio.metainfo.xml.in:32 msgid "Portfolio showing its file properties view" msgstr "Portfolio正在显示它的文件属性视图" #: data/dev.tchx84.Portfolio.metainfo.xml.in:36 msgid "Portfolio showing its trash management view" msgstr "Portfolio正在显示它的垃圾桶管ç†è§†å›¾" #. TRANSLATORS: Don't translate this text #: data/dev.tchx84.Portfolio.metainfo.xml.in:41 msgid "Martin Abente Lahaye" msgstr "Martin Abente Lahaye" #: src/about.ui:73 msgid "Website" msgstr "网站" #: src/about.ui:102 msgid "" "This program comes with absolutely no warranty. See the GNU General Public License, version 3 or " "later for details." msgstr "" "本程åºä¸æä¾›ä»»ä½•æ‹…ä¿ã€‚详细内容è§GNU 通用公共许å¯åè®®, 第3版åŠ" "以上" #: src/about.ui:118 msgid "" "Portfolio logo is a modifed version of Jorgen Carlberg's work. The original " "version can be found here , licensed under CC BY 3.0 ." msgstr "" "Portfolio的徽标由Jorgen Carlbergçš„ä½œå“æ”¹ç‰ˆè€Œæ¥,最åˆçš„" "版本å¯è§äºŽ æ­¤ , 许å¯å议为 CC BY 3.0 ." #: src/about.ui:139 msgid "Created by" msgstr "作者:" #: src/about.ui:166 msgid "Artwork by" msgstr "美工:" #: src/about.ui:194 msgid "Translated by" msgstr "翻译:" #: src/about.ui:227 msgid "Contributions by" msgstr "贡献者:" #: src/menu.ui:21 msgid "Filter" msgstr "过滤" #: src/menu.ui:34 msgid "Show Hidden Files" msgstr "显示éšè—文件" #: src/menu.ui:57 msgid "Sort" msgstr "排åº" #: src/menu.ui:69 msgid "A-Z" msgstr "A-Z" #: src/menu.ui:84 msgid "Last Modified" msgstr "最åŽä¿®æ”¹" #: src/menu.ui:109 msgid "Help" msgstr "帮助" #: src/menu.ui:118 msgid "About Portfolio" msgstr "关于Portfolio" #: src/placeholder.ui:16 msgid "No files found" msgstr "没有å‘现文件" #: src/properties.ui:18 msgid "Name" msgstr "åç§°" #: src/properties.ui:49 msgid "Location" msgstr "ä½ç½®" #: src/properties.ui:80 msgid "Type" msgstr "类型" #: src/properties.ui:111 msgid "Size" msgstr "大å°" #: src/properties.ui:150 msgid "Created" msgstr "创建于" #: src/properties.ui:181 msgid "Modified" msgstr "修改于" #: src/properties.ui:212 msgid "Accessed" msgstr "访问于" #: src/properties.ui:251 msgid "Owner" msgstr "所有者" #: src/properties.ui:282 msgid "Group" msgstr "组" #: src/properties.ui:313 msgid "Owner can" msgstr "所有者æƒé™" #: src/properties.ui:344 msgid "Group can" msgstr "组æƒé™" #: src/properties.ui:375 msgid "Others can" msgstr "其他人æƒé™" #: src/window.ui:648 msgid "Properties" msgstr "属性" #: src/window.ui:699 msgid "About" msgstr "关于" #: src/files.py:292 src/window.py:491 #, python-format msgid "%s already exists" msgstr "%s已存在" #: src/files.py:410 msgid "New Folder" msgstr "创建新文件夹" #: src/passphrase.py:68 msgid "Sorry, that didn't work" msgstr "抱歉,那没有用" #: src/places.py:52 msgid "Trash" msgstr "垃圾桶" #: src/places.py:89 msgid "Places" msgstr "ä½ç½®" #: src/places.py:93 msgid "Devices" msgstr "设备" #: src/places.py:103 msgid "Home" msgstr "å®¶(Home)" #: src/places.py:155 msgid "System" msgstr "系统" #: src/places.py:163 msgid "Host" msgstr "主机" #: src/places.py:177 msgid "No places found" msgstr "没有å‘现ä½ç½®" #: src/window.py:501 msgid "No permissions on this directory" msgstr "在这个目录没有æƒé™" #: src/window.py:514 msgid "Opening" msgstr "打开中" #: src/window.py:532 src/window.py:1075 src/window.py:1085 #, python-format msgid "Could not open %s" msgstr "无法打开%s" #: src/window.py:551 src/window.py:580 msgid "Loading" msgstr "加载中" #: src/window.py:580 #, python-format msgid "Could not load %s" msgstr "无法加载%s" #: src/window.py:630 src/window.py:941 #, python-format msgid "these %d files" msgstr "è¿™%d个文件" #: src/window.py:632 src/window.py:943 #, python-format msgid "Delete %s permanently?" msgstr "永久删除%s?" #: src/window.py:652 #, python-format msgid "%s will be moved" msgstr "%s将被移动" #: src/window.py:654 #, python-format msgid "%d files will be moved" msgstr "%d个文件将被移动" #: src/window.py:669 #, python-format msgid "%s will be copied" msgstr "%s将被å¤åˆ¶" #: src/window.py:671 #, python-format msgid "%d files will be copied" msgstr "%d个文件将被å¤åˆ¶" #: src/window.py:695 src/window.py:874 msgid "Files will be overwritten, proceed?" msgstr "文件将被覆盖,是å¦ç»§ç»­?" #: src/window.py:710 msgid "Pasting" msgstr "粘贴中" #: src/window.py:734 #, python-format msgid "%s of %s" msgstr "%s归属于%s" #: src/window.py:749 #, python-format msgid "Could not paste %s" msgstr "无法粘贴%s" #: src/window.py:801 src/window.py:969 msgid "Deleting" msgstr "删除中" #: src/window.py:826 src/window.py:993 #, python-format msgid "Could not delete %s" msgstr "无法删除%s" #: src/window.py:849 msgid "Stopping" msgstr "æš‚åœä¸­" #: src/window.py:897 msgid "Restoring" msgstr "å¤åŽŸä¸­" #: src/window.py:926 #, python-format msgid "Could not restore %s" msgstr "无法å¤åŽŸ%s" #: src/window.py:1006 msgid "Removing device, please wait" msgstr "移除设备中,请等待" #: src/window.py:1010 msgid "Device can be removed" msgstr "设备å¯è¢«ç§»é™¤" #: src/window.py:1022 msgid "Device is busy, can't be removed" msgstr "设备正忙,无法被移除" #: src/worker.py:512 msgid "read" msgstr "读" #: src/worker.py:514 msgid "write" msgstr "写" #: src/worker.py:516 msgid "execute" msgstr "执行" #: src/worker.py:521 msgid "do nothing" msgstr "无事åš" #: src/worker.py:601 msgid "Calculating..." msgstr "计算中..." Portfolio-1.0.2/screenshots/000077500000000000000000000000001476103320100160245ustar00rootroot00000000000000Portfolio-1.0.2/screenshots/en/000077500000000000000000000000001476103320100164265ustar00rootroot00000000000000Portfolio-1.0.2/screenshots/en/1.png000066400000000000000000001231741476103320100173040ustar00rootroot00000000000000‰PNG  IHDRâ6*Ù€³sBIT|dˆtEXtSoftwaregnome-screenshotï¿>-tEXtCreation TimeFri 09 Feb 2024 04:02:00 PM -03nÖÅ IDATxœìÝyxSeÂ6ð;KÓ&méBé   ²½€tXJ)вÂ8*ãhË"ÈÌ8ê ȼT¿‘QÑêÌâ:¸!Vô,c°”EG¤ ”B¡+H)mÓfÏ÷GÉ1 IÚ´IOZîßuåJÎöœçœôÊÝç9@DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDˆ]nˆû”ˆº;³ØèNmÃýDDÔ> íV0`lquô57zðÜèÛODä+nØ`¾Ñ‚èFÛ^"¢®ê† æ!˜n„m$"êκu(wçêÎÛFDt#ê–Üݪ»m9ÖmB¹»—/n‡/Ö‰ˆ¨#|1ü|±NnéaÑ™ÛÐöQgèÌ€ìÒaÜ•ƒÅ›u—x¹|"¢‘Þ Í.È]1l¼Qg©Ê$"¢Ö™¼Pf— ä®Äžª/[¼DD¾Ç“-æ.Æ])Œ:ZW‰Ý{g®›ˆèFÕž@4Û½wæº;]W˜ŽÔ±½áÛÖù»Âþ#"ê m =wÃÑ¡ìÓìëAÒÞú¹À­ÍÇcÈDDíÓÚ1`w¼½¡ê³aìËAÜ‘nmYWÓ%N>Qû™|v5Ÿ³éÝ*Œ}5h:r·­!k?¾=Ë‘k­…®³ém ëŽö¾0í awƒÔáë‹û“ˆÈ<‚ÎB¹½AÞÞzt:_ ŽöžT媥ëhœ³ñ­­§µùˆˆ¨cÝÐîr{[Ç>ƾ*îÔÇÝvÀ­-Û‘Kžˆˆèú lkW´£@öd3ˆp7„=ÀŽB×—öQW樕ܖ–¯»ÜåÂØW‚Æ[!lÙ‘³@nOwk|eßyŠ'ǶÖR¶¶¿ ªÛ„±/„…7BØ~>WìNkÙÕ€µ–]MÍð%"rÍÕ¥JŽBÕ2lÈm ã¶3öé0öõ ní¸°³–:˜Ïú]ê`YgÇ‘¥.êADD®™p}«ÕQˆZ>» cÀq ØÕY×Öõh‹"ˆ=ÕnK»ÀÖËIá¸L""r}pšà¸Ü–@îHûl«Ø—ƒ¸­!l=Þ–:oýÙzy1QûX¯u³ ׇ®}0·%ŒÛr¶µu]Ú¢SƒXÞ™+ƒ{gI·uš£v4Mj7Ÿ%t]±õx""rŸuàZ¿$>[/#±ûl™nù,u0 †ÑÆií™Ï#:;ˆ;ÂÙuÁÎÞÛÂ2»w Zö‰ßµqR¸Œ˜'sQwçNH™¬^Fz´ü®š¯“\{·„«õ»¥ Ëo«up;{:9L;ƒؾÛÙ~š³ù/vÂr؆°ÿ]»vOKKû­¿¿ÿP‰DÒC"‘°5LDÔf³Ùd6›ëµZíñ¼7uêÔý´h eKKðs@; cÀ6t…UØ­ÒY[‡¹ÏèÌÖ›;ÝÒŽºŸÝ¹ ÉY7³?·teVãT»víJÏÈÈÈ‘Éd}ÚºADDä>£ÑxaïÞ½k¦NZ  ?w_ñsËÙQ—¶£ãÍ@ë—=Yó¹›|øZ;jÕZ†u?;: ÚÑ1_KëXÛ0V]¹råÉÐÐÐ9nnu@]]ݶ°°°ÇÐÆÖ!lùlB—}0ÃÅ8ÀyÃÁxGnØ v7„íç±> Ë2ì4„Õjõ+*•*Í­-!""hjj:¸®ÃØú¬jû³©ÝôíâN âÎ:þéÍÀ·?&lR—ukØú$-emmí†0‘xT*UZmmíJØ2´þͶÿ]wv[cOë”Æª/ˆÔÚ ZÎZÃp0ì¨KÚú±ß§Ÿ~šv›gªNDDívÛ§Ÿ~šÛ+Vì¿°£pv>‘=Ÿ¹Â¥³*ÒÞniÀñí*ípëÖ°õ—(‡íIZrz½~§\.ïíÞfµ®¹¹Z­ƒ¡SÖ'—Ëáïï¥RÙ)ëóƒÁPîçç7€-gO[Ÿ´e¶ï–vv³W·Å„Ýü­ñz÷´¯´ˆuk|° ;û¯ÈþŒjùÎ;ÓÂDäiF£uuuP«ÕÂ`0 V«QWW£ÑØiëõ$¹\Þ{çÎihi,YßÛp|×C8†ƒaËòŽøD«¸3*Ñ‘³¥]µ†­‡µ†­oÈ!ÃÏ_®ª¡¡á•   qîm‘kuuuÀŽÈår„††ŠZ‡öjll<l}â–¶'má¼UÜÚI[ö­bŸ9{ÚWZÄmeÿŸPkã]O,S©Tƒ½YI"ºñ477‹Â@K븹¹Yìj´ËµßfëÖ°}C«-¿ûöŸ}ž/±;-iûqö·¥´9YK"‘ôèxõˆˆ~¦ÕjÅ®‚À—êâŽk¿Íö'kYXŸ¸uÝ¢m)¾Ãô_¸Å¥»g´¹s|ÀáݵxÛJ"ò4_h [x².F£Z­z½ƒfsK/­D"\.‡ŸŸüýý!“É:¼®k¿ÍÎîŠh9øíì7¿µ{L»;¾Óx;ˆÝù¤µy]…§£ƒøöå::ýˆˆ0hjjrÚº6›ÍÐëõÐëõhjj‚¿¿?T*•'ÙÑsãíYs¢–{U;ãN{5¬}¡EìHk}ý®ZËÎNèrÖDDtF£Acc£[ËhµZhµZ!  #«wö›m?®­­[ëq¢·~éʭö´u+ØÕñ""ÐÔÔäv[kllDSSSGªàè7»-—(uÙßv±ƒ¸µãÀŽÞýwd\ÁÑqWë$"º¡i4šŽ†(€–0×h4í]Ü¿ñ®2ÄÕ:E!v·Ó¨TWW£ººZìjŒFc‡ZÂö}áæ"]";¼yŒ¸½;ÀÓ§¡w‰/‚º‡ßþö·8yò$@*•"::)))øÝï~‡^½zu¸|FƒÁ•J©Ôöÿè-[¶à½÷ÞƒZ­ÆŒ3°lÙ²VËKOO‡R©DAAšššpûí·>ûì3¨Tª×—›û,6lع\ޱcÇâÇÄC=„cÇŽÙ,÷É'Ÿàµ×^ƒR©Drr2233…i'NÄâÅ‹À¦•ššŠ%K– wïÞm^+ž(ƒ|_kwâzú駇³gÏbùòåX±bJJJ§žzªCeS×ìšnËõdD¢3™L8uê¶nÝ ¹\Ž””ÔÔÔàã?FBBÞ}÷]ÈårlÛ¶ O>ù$Þ}÷]Œ1¦Œ×_ÝfÜ´iÓPPP€%K– 99@Kûí·Ø²e –,Y‚!C†¸½G öƒO{úé§±nÝ:ìß¿Ä!CðÐCµzÜyç8uêŠŠŠ°mÛ6L˜0l÷z|ðA<÷Üsø¿ÿû?Œ;Ö£u%ç|íd-—wÖr´Þððp¼òÊ+m*ÛÃÚs2–OŸÀåÍÐj­lgOJ²ž&u0ìèùÂŽž²$»ö.¿öÙòb6›¹¹-DD.ýôÓOmž·3‚Ør¹\[544xüîZþþþnßâR"‘¤¸ ÀÀ`õnºöÙdõ2_{Y†Õ8X³¶žvÓœñJ˜‹Õ"f«•ˆnX¾v²¨T*q¼_¹(-ç®~²ˆÈd2‚‚‚}Û¶mƒ\.ÇÍ7ß|Ý}ðÞ{ï ­d8}ú4:„Q£FaíÚµFƒ½{÷¢¢¢0 6l~ÿûßC.çWODä økÜ ¦M›&|–J¥xøá‡ïpÞââb|ðÁøî»ïP__³ÙŒ«W¯Μ9“É„±cÇÚ„0¡«ûÈ‘#3fŒÍ´úúzÜtÓMFaa!¦NŠÔÔTdee!99Ùƒ[JDDîbw‚›o¾‘‘‘èÝ»7222п‡óUVVâ÷¿ÿ=¢££‘““ƒ~ýúaæÌ™ÂtµZ  ¥um/$$ðË_þË—/·™ ???¼ÿþûxÿý÷±oß>ìÞ½{÷îÅo¼AƒyjS‰ˆÈM âNðôÓO Aéˆ%X¿ûî;h4¤¦¦"%%E8žl0@hE:t:ΦUܯ_?H¥Rœþøc\¹r°lÙ2,]ºR©›6m‰' R©`6›¡×ëŽ]»vaÓ¦MH$X¶lÆŽ+ÆfÑ5‘Êu4]b5Þò.u0l=Ôj9˰å%»ö.¿öÙò¢Óé¹±-DDÔI E*€«Œ Vï¦kŸMV/³ÕË2 «aX³6[}¶æhœ;ÓÝÆ1‘ˆÄDDD"b‰ˆALDD$"1‘ˆÄDDD"b‰ˆALDD$"1‘ˆÄDDD"b‰ˆALDD$"1‘ˆø±—X‡°Éd‚Ùl†Á`€N§ƒT*…R©DPP¤R)ÿÀ‰H4f³&“ z½ÍÍÍÐjµP(ËåH$B+Ùl6ó·ÊKÄ^` aK Ød2A«ÕBBB —s·‘oH$BK8 ƒ 0 ð÷÷æ±´ŽÆžÇË—<Ì:„M& šššàïï°°0†0ù4¹\ް°0øûû£©©I8„fýÛFžÅ ö ûcÂ&“ AAAP©T"ÖŒˆÈ=*• AAAÐh46A 0Œ=Í3/0™L0ÐjµP*•»JDDn ~Ë,ÝÔ–cÆä9Ü£b}Y’åÄ, ³ZDDbù 3 BOÀV±'1ˆ½Àòdpp°ØU!"ê°àà`hµZF±«Ò-1ˆ=À¾5l4!“Éàçç'r͈ˆ:ÎÏÏ2™ F£‘­b/`{˜åz<žœED݉J¥‚^¯çMˆ¼€Aì!öwÎR(bW‰ˆÈc …Ð"¶¼È3Ääè6–&“‰gQ·"•J…Ão¼”ɳxù’‡Ø'îì»ÏhµZœ={‰ýúõc‹œˆ<Êrg-“É™LÆ»ly›m^ЙÇPL&6oÞŒ[n¹÷Üs~ûÛßâ–[nÁûï¿ßiõÈÉÉÁ /¼àpZJJ ¾ÿþûN©y{ƒØƒ:»‹¦¹¹+W®Ä /¼€††a|}}=žþyüéO‚Z­îÔ:Q÷ÇîhÏbwQ/^Ä’%KðùçŸ;çàÁƒ¸ï¾ûPUUÕ‰5#""wðqtüøq,_¾—/_¶ ¨««Æ>}óçÏGnn.†Ú©õtÄd2áí·ßÆ'Ÿ|‚ÚÚZ 6 >ú(bccYYY˜0a¾øâ œ>}ñññøßÿý_|÷Ýwضmªªª’’‚Ç{ aaa½^ 6à³Ï>ƒV«Åĉñ§?ý‰—Q—Àq³k×.,]ºôºNJJ»ヒ͛7càÀ6Ó._¾Œûï¿ùùù^«—åY¦ö/{¯½ööìÙƒµk×bÓ¦MP(Xºt)ôz=€–"^~ùe,[¶ [·nE||<²²²páÂ<ûì³x÷ÝwQ^^Ž—^zI(ó¹çžCee%Þyç¼÷Þ{¨¨¨Àºuë¼¶­DDžÄ î"Ìf3^{í5äääÏ6¶7nÞxã ÄÄÄ 22¯½ö&Mšd3N§Ã_þòäææzå„‹?üãÇ¿îe_‡M›6aÕªUHLLD\\V¯^ µZ¢¢"-­úœœ 4ááá¸÷Þ{‹-B||<âãã1{öl|ûí·µZmÛ¶á‘GAxx8"##±téRz|‰ˆ¼]Ó]@ss3þßÿûÏ›7Ë–-³¹nY©TâÙgŸÅ믿Ž×_ÝæÄŠ¼¼<\¸pO=õ”GHq÷ÝwcÙ²e×OII>WTTÀ`0ছn²©ëСCqîÜ9aœõ¶ôïßÐÔÔ„ððp@¿~ýÐÔÔ(++ƒÉd¢E‹„K) OR#¢.ƒAìã.^¼ˆGy?üðƒÍx…BœœÌœ9Óár‰K–,Aß¾}±zõj›V´å$®^x111^­¿5N'<"Ò:l …[×=[_»hy°Æ¦M›â¹ÊuvMû°óçÏ#++ëºîÙ³'^}õU§!lmêÔ©xå•WгgO›ñ§OŸÆ‚ pþüyÖÙ•>}ú@"‘àØ±cÂ8“É„ââb$&&¶«Ì˜˜bß¾}žª&Q§bû(­V‹|?ýô“Íøàí·ßvë è¡C‡âí·ßÆ€lÆÿôÓOx衇 ÓéêÃ?Dyy¹Í¸I“&áõ×_Gtt´ÛåEGGãõ×_¿î$® .à£>êP]ݱbÅ ¤¦¦bùòå˜7o°nݺÝ*oáÂ…ÈÎÎFnn.&Nœˆ%K–àÇô`­‰ˆ¼Ç[7 m­\GÓ%Vã-ïRÃÖóH­–³ [^²kïòkŸ-ï!:îÛâ’õ=¦F# лwï•›'NÃ3fÌÀO<Ñá‡I˜L&<ñÄعs§0nÈ!xë­·:T.uååå†\.‡L&~Êþò›ÈÈH•m_Vcc£ÇÊ&""÷0ˆ‰ˆˆDÄ눻¸ƒbÍš5¸xñ¢ÍøÈÈH<öØcHMM©fDDÔlwqŽBh¹Èš5kD¨¹ƒAÜÅ9 a‹šššN¬ µƒ˜ˆˆHD b"""1ˆ‰ˆˆDÄ &(++ÃŽ;¼Væ‹/¾èѲ;R—¶»ÎDÔ5ðò%òˆŠŠŠßÖ³3Êl¯ÎªKuu5 ÑÐЀ€€Œ?}ûöµ™Çl6#//III6Ï{&¢®‰-bòˆòòr•7Êl/뺡¶¶Ö+ëÉÏÏGjj*/^Œ´´4äççÃh4ÚÌsôèQ477{eýDÔùÄÔaƒuuuˆˆˆðé2=U—êêj¯<:Òh4¢¹¹±±±€øøxH¥R› þé§ŸPYY‰Áƒ{|ýD$vMû(•J…¦¦&a¸¦¦QQQ)ÛþúâÀÀÀ•W]]˜˜H$äçç#>>^x^ò7ß|FƒaÆ¡  µµµ0FââbŒ1 …BØ{÷îÅ´iÓø˜G¢n„-bÕ¯_?›á5kÖxä555×Ýq«ÿþ*ÓºÛvÈ!8yò¤0íäÉ“4h”J%233qß}÷!;;§NBUU ¾¾¡¡¡X¸p!x]™#FŒ@vv6þ¾´ IDATRSS±gÏ€N§ÃöíÛ‘‘‘ `ôèÑØ±c €–ÖìèÑ£1þ|¤¤¤`çÎ7n,X€¡C‡bß¾}€¦¦&àÖ[oÅ¢E‹‹\W—””ÄÆÆbÞ¼y˜5kôz=¶oߎ &`þüù˜5kT*•Ë:5 µµµ8{ö,<ˆääd¡œššŠêêjäå塺ºÿó?ÿ#”uäÈ$%%!$$¤Cßù¶ˆ}ÔôéÓmžG|èÐ!Ìœ9ÓírÚr2ÏôéÓÝ.×ZEEÒÓÓ´t§îÙ³jµz½r¹¡¡¡ZBñðáÃhhh€Z­¶y”}W«u™@Ëã-]ÃÉÉÉ(((TVV",,L˜Ö¿9r—/_FTT¤R)„åöïß/Yrr2Ž=*”£V«ñùçŸh xK—°}]¬UUU!((Hx¢•R©lµÎ‰·Ür Þ|óMDFFâ7¿ù @£Ñ`ëÖ­˜9s&bccqâÄ lÚ´ óæÍƒF£ÁùóçqÇw´é;!¢®ƒAì£~õ«_aûöí8uê”W×3`ÀÌ;·ÝË›L&\¹rE‰D‚AƒáÔ©SÐjµBÀÖÔÔ`÷îÝÈÈÈ@Ïž=Q[[ ­V ×=gÙ¾L{‰Ä¦ËÚÒuk¡Óé>»Y&“9ö÷÷Gll,æÌ™ãV]t:Mø:c]gBe2™0þÂ… ZßC‡EUUΜ9ƒºº:466âwÞ\¹rpâÄ dgg·º~"ò]ìšöQ …ÿøÇ?œœìµu 0ÿûßáçç×î2ª««e2– .--ERRàòåË@TTŒF#*++¯ FWe:ƒªª*ápEEÌf3zöìéÖvDGGãêÕ«(--Ðr‰^¯wX¥R‰úúza¹ŠŠ á,j½^³ÙÜêú „nìãÇBCCQUU%lƒZ­Fyy9ÂÂÂ0iÒ$,\¸ÙÙÙÈÎÎÆ˜1c––Æ&êØ"öa‘‘‘xë­·°uëV|úé§8sæŒÍ \í¡R©˜˜ˆ[n¹sçÎíPޝ¯íÑ£ššš!”Ÿ˜˜ˆ²²2¼õÖ[P*•ˆ‹‹ƒ\îøÏÏkv•J%¦OŸŽÝ»wÃh4"((³fÍrØ"vÅÏÏ·Ýv ñå—_B¡P`øðá¸råÊuu9r$ qüøqÌ;“'OF~~>üüü T*1yòd—'À=z*• ˆÅû￸¸8ôêÕ éé騽{74  n¾ùæë®#&¢î¥õ&‡wÊu4]b5Þò.u0l=Ôj9˰å%»ö.¿öÙò¢Óé¹±-.YZ?&“ F£ƒ >sý«·={öé2»C]ˆÄV^^Žàà`ÈårÈd2áÞ¶ô^µ•B¡Hp€€ÁêÝtí³Éêe¶zY†a5 «qöÃf«ÏÖZëÒj½ËËMlS‡ØŸÝí«e¶—/Õ…ˆº'#&""ƒ˜ˆˆHD b"""1ˆ‰ˆˆDÄ &""ƒ˜ˆˆHD b"""1ˆ‰ˆˆDÄ &""ï¬å%–‡¹Â ö’ððp±«@DäQ}è 9Æ®i"""1ˆ‰ˆˆDÄ &""ƒ˜ˆˆHD b"""1ˆ‰ˆˆDÄ &""ƒ˜ˆˆHD b"""1ˆ‰ˆˆDÄ î&<ˆ3f`ÆŒ8tèØÕ!"¢6bwkÖ¬ÁÅ‹qñâE¬Y³¦Sד“ƒ””¤¤¤`Ô¨Q˜;w.}ôQœ>}ºSëa-++ ÿþ÷¿½V¾N§CJJ ÊË˽¶"º10ˆ»‰‹/ Ÿkjj:}ý¿þõ¯QXXˆ‚‚üå/Ahh(²²²°k×®N¯ QW§/‘G( ÂÂÂ0|øpDGGã¹çžÃرc,r ‰ˆ|[Ää5÷ÜsÌf3ŠŠŠ&“ o¾ù&n¿ývLš4 ?ü0*++=öV­Z%,kéú}á…„qMMM3f ***••…7bÉ’%˜0aî¾ûn|ûí·NëâjÝz½/¼ðæÎ‹qãÆáöÛoÇÎ;m–7x饗0kÖ,̘1¹¹¹6Ó:„¬¬,¤¥¥aΜ9X¿~}ÇvÝ0Ää5 …‰‰‰(++¼öÚkسgÖ®]‹M›6A¡P`éÒ¥ÐëõHOOÇÑ£Ga6›_}õ °°P(ï›o¾AïÞ½‡ºº:lذ>ø ¶nÝŠ!C†`åÊ•ÐëõëâjÝ~~~èÓ§rss±eËÜzë­X½zµPoX¿~=:„矯¾ú*š››…iõõõX±bfÏž;wâùçŸGFF†§w'uS bòª^½z¡®®:›6mªU«˜˜ˆ¸¸8¬^½jµEEEHMMECCJJJ´´0ÿð‡? ®®¥¥¥€Ã‡#-- Šœœ 2ááá˜?>._¾Œ .\W‡ÖÖ wÜq‡  &&'Nœ–ß²e rrrœœŒ>}ú ''G(ÿÒ¥KÐëõHKKC=€Aƒyu¿Q÷Á &¯ºté~ñ‹_ ¢¢ƒ7Ýt“0M©TbèС8wî‚‚‚póÍ7ãèÑ£Z.ÇÊÌÌDjj*öïß %ˆÇ',/•þüç ¥R‰¦¦¦ëêÐÚº ®®ï¿ÿ>V®\‰ßÿþ÷(//‡F£”——Ãh4Ú,o½î~ýú!%%wÞy'rrrpäÈ‘Žì2"ºÁ0ˆÉkt:JJJN“É£Ñh3B¡€B¡L˜0GŽÁùóç!“ɉ'¢¨¨—.]BMM FŒát}‰Äi=\­»¹¹óçÏÇ¥K—pÿý÷#77ÉÉÉÂüz½&“ &“ÉaùR©6lÀßÿþwøùùaÅŠxä‘GÚ¼ŸˆèÆÆ &¯Ù¼y3âââ0zôhôéӉǎ¦›L&#11žžŽcÇŽ¡¨¨ãǤ¥¥áÇDAAF ¹Üýý[[÷?ü€ºº:<øàƒèß¿?T*N:%\æhyGFމ'žx7nľ}ûl.)#"r†AL¡×ëÑÜÜŒÆÆFœ8q¹¹¹ÈËËCNN$ T*fÏžgžyÅÅŨ­­Enn.ÂÃÃ1fÌ@LL bccñúë¯ A„#Fàÿø‡p|¸-BCCQQQ³ÙÜêºÃÃÃÑØØˆ>úW¯^ÅîÝ»´tw€J¥ÂÌ™3ñôÓOãĉøé§ŸðÏþSXW}}=¶oߎ‹/B£Ñà믿†R©Dhh¨§v/uc bòˆ?üãÇÇ”)SðÔSOÁd2áƒ>@rr²0ÏŠ+ššŠåË—cÞ¼yhhhÀºuëlº”ÓÓÓ¡V«1|øpaܤI“cÇŽms}î¾ûn|úé§X±bE«ëîÛ·/|ðA¼ú꫘;w. …uZ<úè£HIIÁŸþô',]º‘‘‘BP744`ïÞ½¸çž{™™‰mÛ¶aíÚµB—;‘+Žªy¿\GÓ%Vã-ïRÃÖóH­–³ [^²kïòkŸ-ï!:Îc7c¶\nc9i0ÐÐЀ޽{{jm’’’b3l¹ü‡ˆÈSÊËË ¹\™L&œ´èìüŒöP(©®00X½›®}6Y½ÌV/Ë0¬†a5Î~ØlõÙš£qîLw[ÄDDD"bw‡ž®d}“‹¶*,,äÓ™ˆˆ|ƒ¸‹xòÉ'…§+­\¹Ò­‡)ìÚµ +W®–òÉ'½XS""rƒ¸ 2xüñÇÛÆ»víÂã?ns ­'çQÇ0ˆ»ˆG}2™LnK; a™L&œILDDâcw'NÄSO=u]?öØc×=)vïÞ}]K¥R<ñĘ8qbgT™ˆˆÚ€—/uPg_¾ä¬•kûFûq2™ ûÛß0uêT¯Ô‹ˆº?^¾Ô¦énc‹¸‹™:uªÃ–±=G-a†0‘ïawA™™™×…±3R©«W¯ÆôéÓ;¡fDDä.qÕ–0îÌ.++ÃŽ;¼Væ‹/¾èѲ;R—¶êì:{ã;èOn¿ÑhÄË/¿ì±òˆ|‰û²!Ÿa ãU«V]×=ÝÙ-ኊ ÷F™íÕYu©®®Faa!€ñãÇ£oß¾6ó˜Ífäåå!))ÉæÖ¦Öu|ã7`2™ ‘H •JÑ«W/Œ7={öôú6‘{Ø"îⵌÅèŽ.//÷xPy£Ìö²®KQQjkk½²žüü|¤¦¦bñâÅHKKC~~þuÿd=zÍÍÍ.ëóæÍÃâÅ‹‘þýûcË–-^«7µƒ¸ÈÌÌÄÚµk‰¨¨(<ûì³Âƒuuuˆˆˆðé2=U—êêjèt:¯Çh4¢¹¹YxªS||<¤R©MÿôÓO¨¬¬ÄàÁƒ]ÖÑš\.Ç!C0räHûì3TVVÂd2!33ñññ€ššìÛ·jµÈÈÈ@TT`ýúõHLLDuu5 55%%%¨©©L&ÃÔ©S 8þ<öïßFƒž={bÊ”) ´©ËW_}…ÊÊJäåå!!!³fÍÂ¥K—PXXµZ-<»800Ði>ŒúúzL™2F£yyy˜4ibcc‘€üü|Œ=ÅÅÅ1b„ð8E“É„½{÷bÚ´iøñÇ~Î$%%á»ï¾†í·O>ù}úôÁ°aôë5j”ðÜè>úãÆCLL Ö¯_¤¤$TWW_÷½Xs¶.µZíô²ûöíƒV«EXX˜ðÏÙlFQQΜ9™L†>}ú ##£m¬D>ˆALfÝ%:dÈ=zTâ“'ObúôéP*•ÈÌÌDHH4 6oÞŒ„„ÄÄÄ ¾¾¡¡¡X¸p¡ÐÅî¨[zĈ˜2e Nž<‰={ö`Á‚Ðétؾ};æÎ‹ˆˆ”––bÇŽ˜?>är9 F°°0|ÿý÷عs',X€;v ûöíÃ]wÝ…¦¦&àÎ;ïDHHŽ9‚`Ú´i6uIIIAii)ÒÓÓ ½^íÛ·cÖ¬YˆŒŒDss3\ÖyÔ¨Qزe Ξ=‹ . 99Yh§¦¦"//yyyˆŒŒÄœ9s„²Ž9‚¤¤$„„„¸üœ º´]í·äädcذa¸tézõê…S§Na̘1Ðjµ¨¯¯GLL €––ø¨Q£f³Ö\­ËÙßExx8>ùäüêW¿BDDÊÊÊpöìY-'¥UUUaÁ‚H$0 .·›È×1ˆ}œýs†=ÍÏ-®¨¨@zz:€–îÔ={ö@­VC¯×C.—#44@ËöáÇÑÐеZ µZ-”aßÕj]&ÐrÜÛÒíšœœŒ‚‚-­¦°°0aZÿþýqäÈ\¾|QQQJ¥ –Û¿¿dÉÉÉ8zô¨PŽZ­ÆçŸ %<,]Âöu±VUU…   DFF”Je«u–H$¸å–[ðæ›o"22¿ùÍo[·nÅÌ™3‹'N`Ó¦M˜7o4 Ο?;ÕïÀ™ÆÆFá»pµßú÷ï½{÷Â`0 ¤¤¿üå/ñõ×_ãòå˸|ù2úõëg³ÖûײÖZûŽý]hµZ„‡‡ ËôîÝþþþ€°°0466âóÏ?GBB‚Ã8QW ¦1™L¸råŠðƒ)‘H0hÐ œ:u Z­VØššìÞ½èÙ³'jkk¡Õj@¸;³2íI$›.kK×­…N§»®L×]êe=ìïïØØX›h[ê¢ÓélÂ×ë:ê,“É„ñ.\@xx¸Ð²:t(ªªªpæÌÔÕÕ¡±±ï¼óàÊ•+€'NàÞ{ïuYG‹’’!´\í7¹\Žøøx”——£¬¬ ¿üå/ÑØØˆÓ§OãêÕ«4hP›¶ÑÂÕºœý]H¥Òë–± Á‚ PZZŠ'NàÀ¸ûî»ù0ê²x²uHuu5¢¢¢l~-A\ZZФ¤$ÀåË—€¨¨(FTVV:½ÚQ™ÎÄÄÄ ªªJ8\QQ³Ùìöe:ÑÑѸzõ*JKK´‡Ôëõë¢T*Q__/,WQQ!œ¬×ë…ÛžºRPP€Y³fA¥Ráøñã€ÐÐPTUU Û V«Q^^ް°0Lš4 .Dvv6²³³1f̤¥¥!;;»Õýe2™ðã?¢¸¸X8ÎÛÚ~KNNƱcǹ\Žäädœ>}ÕÕÕm>“]*•Â`0¸\—³¿‹˜˜TVV¢¦¦pñâEá7­V Fƒ¤¤$L™2uuuÂ4¢®ˆ-bêG××öèÑMMMˆˆˆ€ŸŸ 11eeexë­· T*¹ÜñŸŸ;×ì*•JLŸ>»wï†ÑhDPPfÍšå°E슟Ÿn»í6âË/¿„B¡ÀðáÃqåÊ•ëê2räHâøñã˜;w.&OžŒüü|øùùA©TbòäÉ tº®£GB¥R!!!±±±xÿý÷‡^½z!==»wï†F£B¡ÀÍ7ß|ÝuÄöœí¯-[¶-ÜØØXÜu×]B÷nkû­oß¾øä“Opë­·T*$ """ÚÜò>|8>øàÌž=Û麜ý](•JL›6 J¥ˆŠŠB=µµµøâ‹/ÐØØ£Ñˆ1cÆØ—'êjøÐ‡êì‡>øš³gÏ"""ÁÁÁ>]fw¨‹3]¡ŽÔ=ð¡mšî6¶ˆ©C¬OÜñå2ÛË—êâLW¨#9ÇcÄDDD"b‰ˆALDD$"1‘ˆÄDDD"b‰ˆALDD$"1‘ˆÄDDD"âµ¼Äò"""WÄ^.vˆˆ<ª©©Iì*tKìš&""ƒ˜ˆˆHD b"""1ˆ‰ˆˆDÄ &""ƒ˜ˆˆHD b"""1ˆ‰ˆˆDÄ &""ƒ˜ˆˆHD b"""ñ^ÓÔa999(((H¥RÄÅÅ!)) ‹/FRR’ȵóN“É„€€±«BD]ƒ¸‹Ðëõغu+vî܉3gΠ¹¹¹Õe"##ñè£bâĉ^¯ß¯ýk<ðÀÐëõ(++ÃÎ;‘••…Õ«WcêÔ©^_¿þõ¯¡¼¼kÖ¬»*DÔ…1ˆ»€K—.áá‡ÆÉ“'ÝZîâÅ‹X¹r%þö·¿y=  ‚‚‚aaa>|8¢££ñÜsÏaìØ±öêú‰ˆº*#öqz½¾]!la4ñøãc×®]®Yëî¹ç˜ÍfL&Þ|óMÜ~ûí˜4i~øaTVVÚ,³yófÜqÇÈÈȰ™>mÚ49rD˜ï‡~À¸qã„ᬬ,üë_ÿÂ}÷݇ &àÞ{ïEqq1òòò0oÞû,Þ}÷]”——㥗^–yî¹çPYY‰wÞyï½÷***°nÝ:›27lØ€|[·nÅ!C°råJèõzdeeaÑ¢E˜6m>Œ•+W¢¾¾+V¬ÀìÙ³±sçN<ÿüóÈÈÈðà%¢îˆ]Ó>nçÎ6Ã&LÀŸÿügôêÕËår)))6Ö0ЩÇl{õê…ºº:èt:lÚ´ ¯½ö«W¯Æ­·ÞŠ¢¢"Lœ87nĺuë0pà@-­à¶ Å‚ 0hÐ À½÷Þ‹‚‚,Z´ááá€Ù³gcëÖ­µZmÛ¶á“O>¦/]º+W®ÄÊ•+mÊ2d`þüùøøãqáÂôïßÿº:\ºt z½iiièÑ£zôèÑž]FD7±+--µnK;#F_ºt ãÇGEE nºé&ašR©ÄСCqîÜ9TTT ©©IÒöJîà±eSS“´ýúõºžËÊÊ`2™°hÑ"H$€Á`€Z­vZfll,”J¥M÷µµ~ýú!%%wÞy'ÆÛn» £Fj÷öÑAìãìôÛÂF£=ö f̘ѡ²Z£ÓéPRR‚ùóç —úF›pS(P(hllÐrY&“y¥>–À œ<¶iÓ&„„„´« {R©6lÀ×_;v`ÅŠHIIÁóÏ?ßþJQ·ÇcÄÝ”«nQ“ɄիW£°°Ð«uؼy3âââ0zôhôéӉǎ³©Gqq1“É„~øÁaYJ¥gÏžµg2™Ú]·˜˜bß¾}í.hiEÛ9r$žxâ lܸûöíÃÅ‹;´"êÞÄÝ”ý1b{F£Ï=÷œÇÖ§×ëÑÜÜŒÆÆFœ8q¹¹¹ÈËËCNN$ T*fÏžgžyÅÅŨ­­Enn.ÂÃÃ1fÌ„„„`Ò¤IX»v-ÊÊÊÐÔÔ„¢¢"¡G`ذaø÷¿ÿªª*TTT`Íš5ÂI^í!—Ë‘••…uëÖáðáÃ0¨©©Aqqq›ËˆŠŠÂ7ß|ƒªª*477£¾¾Û·oÇÅ‹¡Ñhðõ×_C©T"44´Ýõ$¢î]ÓÝÔŠ+_}õêëëÎSSSã±õ}øá‡øðÃáçç‡þýûcذaøàƒlº}W¬X_|Ë—/‡Á`ÀèÑ£±nÝ:¡»÷ñÇÇóÏ?ûî»z½ÉÉÉHJJ‚J¥ÂC=„Õ«Wã®»îB\\¦M›† .t¨Î .„¿¿?rssQ]]ððpdgg·ù8õÌ™3qðàAÜu×]9r$–/_޽{÷býúõP«ÕøÅ/~µk×B¡Pt¨žDÔ½9?àåÝrM—X·¼K [Ï#µZÎ2lyÉ®½Ë¯}¶¼‡ètºCnl‹Kf³„㟃 èÝ»·GÊ·oÙ~õÕW>UÝ8ÊËË ¹\™L&œïáêÜ w)ŠTW¬ÞM×>›¬^f«—eVðg?l¶úlÍÑ8w¦»]ÓDDD"b‰ˆALDD$"1‘ˆÄDDD"b‰ˆALDD$"1‘ˆÄDDD"b“G”••aÇŽ^+óÅ_ôhÙ©K[uv½ñ‘÷ñ^Óä»­§7Ël¯ÎªKuu5 ÑÐЀ€€Œ?}ûöµ™Çl6#//III6·,µ®ão¼“ɉD©TŠ^½zaܸqèÙ³§×·ˆÜÃ1yDyy¹ÇƒÊe¶—u]ŠŠŠP[[ë•õäçç#55‹/FZZòóóa4mæ9zô(š››]ÖæÍ›‡Å‹#;;ýû÷Ç–-[¼VïÎàÍýN$&1u˜Á`@]]"""|ºLOÕ¥ºº:Îãë1hnnFll, >>R©Ô&ˆúé'TVVbðàÁ.ëhM.—cÈ!9r$<èñzwoíw"±±kÚÇB­V Ã/^Dddd»Ê²@}```‡êfQ]]˜˜H$äçç#>>C‡|óÍ7Ðh46l P[[ £ÑˆÁƒ#-- °qãF 0ÅÅÅHKKÃÀmÊZžnõÙgŸ¡²²&“ ™™™ˆÐò8Ç}ûöA­V# ˆŠŠ¬_¿‰‰‰¨®®¤¦¦¢¤¤555Éd˜:u*¢££çÏŸÇþýû¡ÑhгgOL™26uùꫯPYY‰¼¼<$$$`Ö¬Y¸té ¡V«¡R©0sæL:­óáÇQ__)S¦Àh4"//“&MBll,ŸŸÑ£G£¸¸#FŒ£h2™°wï^L›6 ?þø£ÓïÀ™¤¤$|÷Ýw°«ýæh›$ 6oÞŒûî»O˜g÷îÝøÍo~ã±}½~ýz$%%¡ººÚfŸÙï÷[o½EEE8sæ d2úô郌Œ ÷ÿx‰|ƒØÇõë×ßÿ½0üÌ3ÏàÏþ³Ûa|ñâE<ýôÓ6ãär¹ðÜã°°0|ÿý÷عs',X€;v ûöíÃ]wÝ…¦¦&àÎ;ïDHHŽ9‚`Ú´i6uIIIAii)ÒÓÓ ½^íÛ·cÖ¬YˆŒŒDss3\ÖyÔ¨Qزe Ξ=‹ . 99Yh§¦¦"//yyyˆŒŒÄœ9s„²Ž9‚¤¤$›g<;úœ º´]í7³Ùìp›u‡[óľ6 5jÂÂÂlö™ý~?wª°`ÁH$ —u#òe b7cÆ › .**BQQ‘GÊž9s¦GÊ©¨¨@zz:€–îÔ={ö@­VC¯×C.—#44@ËõáÇÑÐеZmÓÒ·ïjµ.¤R©ÐíšœœŒ‚‚@ee%„iýû÷Ç‘#GpùòeDEEA*•",,LXnÿþýB%''ãèÑ£B9jµŸþ9€– ²t Û×ÅZUU‚‚‚„Œ”Je«u–H$¸å–[ðæ›o"22RhQj4lݺ3gÎDll,Nœ8M›6aÞ¼yÐh48þ<î¸ãŽV¿g…ïÂÕ~ÓjµN·ÉOìkû2,ûÌ^XXñùçŸ#!!Aè!êŠÄ>nîܹؾ}ûu]‘5xð`ÜvÛm.Çd2áÊ•+ºD"Á AƒpêÔ)hµZ!`kjj°{÷nddd gÏž¨­­…V«ááâÎÊ´'‘Hlº¬-]·:îº2­mGÃþþþˆµi¶¥.:®MAe]gBe2™0þÂ… Z¶C‡EUUΜ9ƒºº:466âwÞ\¹rpâÄ Ü{ï½.ëhQRR"–«ýæj›L&“ÃñöÚ³¯íÙï3k!!!X°`JKKqâÄ 8pwß}·GPOÔYx²–óóóÃ?þñ 8Ðce4Ï?ÿ<äòŽÿV]]¨¨(›@K—––")) pùòe ** F£•••×ýX»*Ó™˜˜TUU Ç%+**`6›Ý¾L'::W¯^Eii)€–K„ôz½Ãº(•JÔ×× ËUTTgóêõz˜ÍæV×WPP€Y³fA¥Ráøñã€ÐÐPTUU Û V«Q^^ް°0Lš4 .Dvv6²³³1f̤¥¥!;;»Õýe2™ðã?¢¸¸cÆŒiu¿9Û&¥R ½^ÊÊJ--[Ë~ðľnõ~×jµÐh4HJJ”)SPWW'ücGÔÕ°EÜDDDàÍ7ßĶmÛ°sçN””” ©©É­2T*1sæLÜvÛm aÀñõµ=zô@SS"""àççHLLDYYÞzë-(•JÄÅÅ9­ƒ;×ì*•JLŸ>»wï†ÑhDPPfÍšå°E슟Ÿn»í6âË/¿„B¡ÀðáÃqåÊ•ëê2räHâøñã˜;w.&OžŒüü|øùùA©TbòäÉ.O„;zô(T*‹÷ßqqqèÕ«ÒÓÓ±{÷nh4( Ü|óÍ×]GlÏÙþÚ²e ¤R)är9bccq×]wÁßß¿Õýät›,ãU*ÂÃÃÛuŸ³}˜˜èr9ëý>vìX|ùå—hll„ÑhĘ1clŽÍu%ÞêÇi­\GÓ%Vã-ïRÃÖóH­–³ [^²kïòkŸ-ï!:îÛâ’¥õc2™`4a0ÐÐÐà3׿zÛÙ³gàà`Ÿ.³;ÔÅ™®PGêÊËË ¹\™L&üÃëÉC …"ÿŸ½;ª¾÷?þš%C&{BH @vPYd ˆˆˆ‚¸¢@ÛË£¥¶´E´ÔþÚÛòP®xµ…k-WQ¸ˆ{ ²D‚ìˆØ"«²˜u¶ß0sg²A’I&Ø÷óñ8s¾gùžÏ9Éc>óýž3çÀ·€°{×§^ƒËkp—ñ*ã5¯rÙå5ííF]Z7îòª#µˆ¥A:vìxKÔY_Í)–šÜ 1ŠHÍtXDD$€”ˆEDDH‰XDD$€”ˆEDDH‰XDD$€”ˆEDDH‰XDD$€”ˆEDDH‰XDD$€ôd­Fâ~`¾ˆˆHm”ˆILLL Cñ«º¾lFnŽº¦EDDH‰XDD$€”ˆEDDH‰XDD$€”ˆEDDH‰XDD$€”ˆEDDH‰XDD$€”ˆEDDH‰XDD$€”ˆEDDH‰Xlîܹ¤¤¤’’€xôÑGùàƒp:žu&OžÌ{ï½wSõUTTPVVÖHÑŠˆ4/zéC3g³ÙX½z5iiiœ>}šÒÒÒÕgµZINNf̘1L˜0   ¿Ä9qâDfΜIii){÷îeÁ‚”––2mÚ4†J·nÝnª®wÞy‡ÌÌLæÍ›ç—ØDDš3%âf,??ŸŸýìgœ8qÂou–––røða>ÌÚµkyã7ˆmp½‹…°°0ÂÂÂ7nEEE,Y²Ä“ˆô£5x""ßEêšn¦l6›ß“peÇgÖ¬YØív¿×ݧO®\¹âymÚ”)Søè£|ÖY±b“&Mbøðáüìg?#;;›eË–ñÎ;ï°iÓ&îºë.æÏŸÀèÑ£Ù¿¿gÛcÇŽqÏ=÷xÊS¦Lá“O>á¹çžcèСìÛ·¸vÿøÇ?òÀššÊ¼yó|^å¶{÷n¦L™Â Aƒx衇X´h‘ßÏ…ˆHmÔ"n¦V¯^ݨIØíرc¬Y³†‰'úµÞ‹/KHHHµË—-[ÆÆù÷ÿwZ¶lÉ¡C‡ˆŽŽfÊ”)׫kúW¿ú/¾ø"3gÎ$11€W_}•o¿ý–eË–a·ÛùíoËÂ… ™3g………Ìž=›Y³f1jÔ(òóó)//o𱋈ԅq3•––æS:t(/¼ð­ZµjP½ùùù¼üòËìØ±Ãg_þJćƒS§Nñ_ÿõ_<ýôÓ5®³dÉ.\H÷îÝk-Þ†?~<<òˆ§\\\Ìš5kX¿~=111̘1ƒ9sæ0gÎòóó±Ùl 4ˆˆˆ"""ƒˆH])7S>e$a€V­Zñ /ø$âÓ§O7¸Þ+V°bÅ  <ùä“5&÷¬¬,JJJèÑ£Gƒ÷ëíöÛo÷)Ÿ={§ÓÉôéÓ1 ØívŠ‹‹èر#)))<úè£ <˜ñãÇÓ¿¿Æ$"r#JÄÍ”÷uLÀ/IØ-..ΧìNL ñàƒòóŸÿ‹Å‚Åb©uÝ«W¯àt:1™L ÞwMÂÃÃX¾|9‘‘‘U–FÞ|óM<Ⱥuë˜={6)))¼öÚk“ˆHeºYKüÂ}Çô’0@Û¶mq:;v¬Æu*ß@fµZ9sæŒÏ<ïß)W§M›6„††’žž^ëzýúõã·¿ý-K–,!== .ÜàDDüG‰øµk×.î¿ÿ~î¿ÿ~vïÞèpê$22’aÆ1þ|Ξ=KII ;vìðôÄÇÇóÅ_““ãùÝôwÜÁG}DNNYYYÌ›7›ÍVë~Ìf3S¦LaáÂ…ìÛ·‡ÃA^^G °°µk×ráÂÊÊÊ8xð V«•¨¨¨Æ=""^Ô5}‹š7ož§å6oÞ<6lØàˆê楗^âµ×^ãûßÿ>6›.]ºÐ¹sgBBB;v,»víbòäÉôë××_Ÿþô§üîw¿còäÉ´mÛ–Ñ£GsþüùîgÚ´i´hÑ‚ ››KLL Ï<ó =zô ¨¨ˆO?ý”E‹Q\\L‡˜?þMµêEDüÅ z«[nðšï«){¯côÚÎ]v¦ëcóõi÷8²¢¢ÂoMH—Ë\ë&u8ØívŠŠŠh×®]ƒêMIIñ)þùç>åûï¿ß“ˆããã뜈oT¿ˆHe™™™„‡‡c6›1™L×>¢Ý7CúƒÅb| 8»×Øy}Úé5¸¼w¯2^ó*—]^ÓÞª›W—åu¦®é[Ô¯ýkâãã‰gîܹGDDêI]Ó·¨ÞrÝÑ""R•ZÄ"""¤Düµxñb.\è0DDäÔ5ý´xñb/^ì)ÿä'? `4""Rµˆoa‹/fÖ¬YäççûÌóNÂK—.UËXD¤S‹øåpŸxâ æÌ™CFF†Ov[ºt)-Z´à‡?üaS‡)""7 D| ªÜê½|ù2/¼ð ·)//W7µˆH3£®é[Lå$\K—.õs4""ÒPJÄ·˜ú&áÆvöìYÖ­[×huþéOòkÝ ‰åf5uÌñ7‘Ƨ®é[LŸ>}øòË/ëµí]wÝåçhþOVVVƒëÙuÖWSÅ’››ËöíÛ)**"88˜ÁƒsÛm·ù¬ãr¹X¹r%;wöyT©wŒo¿ý6N§ƒÁ€Éd¢W¯^ôïߟ’’–/_ΓO>Ihhh£ˆÜ˜q3êóžà .ÇÛo¿Ýàº+¿æÏÈ™™™ 6¬Áõ4võåËŽ;èÕ«111~ßφ HMM%11‘3gΰaÃf̘áóÞæxÞJUSŒ=ö²zõjÂÂÂèÞ½;#FŒ $$¤Ö8óEÄ—º¦›©Ž;ú”_y忼'÷Â… ¼üòË>ó’’’T§ÝnçÊ•+ÄÆÆ6¨žÆ®Ó_±äææRQQá÷ý8JKKIHH 11£ÑˆÃáð¬sñâE²³³éÙ³g­1z‹ˆˆàÎ;ïä›o¾Á`0””tÇô7Ö1ŠHUj7S÷ß?_}õ•§¼cÇvìØÑ(û;vlƒ¶ÏÍÍ¥M›6 6lØ@bb"½{÷à‹/¾ ¬¬Œ;M›6QPP€Ãá gÏž 4€%K–еkWŽ=Ê AƒèÞ½»OpííV›7o&;;§ÓɈ#HLL //ôôtŠ‹‹ føðáÄÇǰhÑ"’““ÉÍÍ®=£ûÔ©Säååa2™5j­[·àܹs|öÙg”••ѲeKRSS õ‰åóÏ?';;›•+W’””ĸqãÈÏÏgûöí{^ãZcÌûöí£°°ÔÔT+W®dذa$$$””Ć ¸ë®»8zô(}ûöõ¼–Ñétòé§Ÿ2zôhŽ?^ãß :f³™   àÚµëçž{ ÚØ;æsŒ#FŒ`ÅŠ|ÿûß÷l³eËüñÿ~ÕËvìØÁéÓ§1™L´oßžáÇ7èOä»@‰¸™š0ak×®­òëo={ödüøñ ª#33Ósm²W¯^8pÀ“ˆOœ8Á˜1c°Z­Œ1‚ÈÈHÊÊÊX±bIII´iÓ†ÂÂB¢¢¢˜6mš§ Ö»N·¾}û’ššÊ‰'غu+S§N¥¢¢‚µk×2aÂbccÉÈÈ`ݺu<û쳘Ífìv;wÝuÑÑÑ|õÕW¤¥¥1uêT"##9tèéééLž<™’’6mÚÄ£>Jdd$û÷ïgçÎŒ=Ú'–””2222d­[·Æf³±víZÆG\\¥¥¥×sÿþýYµjgΜáüùótéÒÅÓ 8p +W®dåÊ•ÄÅÅñÐCyêÚ¿?;w&22²Ö¿7—ËEVV‡bðàÁ>Ëjнò1–””Ôú÷¯ü÷«é\víÚ•œœ¦NŠÁ`Àn·×Z¯È¿ uM7SAAA¼ñÆtïÞ½ÑöÑ£G^{í5Ìæ†}ó¾I(11‘ÂÂBŠ‹‹¹rå f³™¨¨(ŒF#v»}ûö±sçNŠ‹‹}®÷ìÙÓç:h団ŒF£§ÛµK—.Mtt´gY§N ãÒ¥Kží¢££=ÛµhÑ“Ⱥté·ß~ë©§¸¸˜mÛ¶±f;ùæ ªÅ[NNaaaÄÅÅ`µZ=­Òšb6 Üwß}|ôÑGdffzn¸*++cõêÕŒ;–çž{ŽÞ½{³|ùr ¹páçΣOŸ>7ü\½z•%K–ðî»ï²oß>î¾ûnÚ·oӱו÷߯¦sÍÕ«WÙ¶mgÏžõù{‹ü+S‹¸‹åÝwßeÍš5¤¥¥qêÔ©¶Nn$$$„äädÆŽËøñ㜄N'—/_ö$ƒÁ@=8yò$åååžk™yyylÙ²…áÇӲeK (//ð¼\¼¦:+3 >]Öî®[·ŠŠŠ*uU>ø½Ë-Z´ !!Á§z3±TTT`µZ«]VSÌ€'f“Éä™þüybbb< µwïÞäääpúôi®\¹ÂÕ«WY¶lpí!.GŽáé§Ÿ®cXX>ú(5Æt³±ÃµóP“Ê约s 0uêT2228rä;wîä‰'žðëKåEnEJÄÍœÙlfâĉLœ81СT+77—øøxŸÓ=zðñÇãp8˜4i—.]"88˜øøxÊËËÉÎÎæöÛo¿é:kÒ¦M6oÞLnn.­[·&++ —ËEË–-ët­[·æÛo¿%##ƒN:ár¹°ÛíäççW‰ÅjµRXXHëÖ­iݺ5›7o¦  €˜˜l6f³ù†±oÚ´‰qãÆqôèQ>LïÞ½‰ŠŠ"''‡¬¬,())!33“.]ºÐ·o_Ÿ;¢÷îÝ‹Ùl&%%…ììì›>_•¹¦Ø½Ñjµb³ÙÈÎÎ&!!ììl ë|.N'v»Î;sÛm·yžöæÝ•/ò¯H‰X¤ºnÛˆˆJJJˆõÜ ”œœÌÙ³gyï½÷°Z­´mÛ¶ÆÖx]~³kµZ3f [¶lÁápƸqãªm×&((ˆñãdz}ûvöîÝ‹Åb¡OŸ>\¾|¹J,ýúõcûöí>|˜ &0räH6lØ@PPV«•‘#GÖú“°BRR |ðÁ´mÛ–V­Z1dȶlÙBYY‹…;ï¼³Êïˆ+«ïoœÃÂÂjŒ½¦c !&&¦Öã«é\†††²gÏ®^½ŠÃá`À€JÂ"@cõ ݨÞê–¼æ»ÇÆjÊÞë½¶s—݃éúØ|}Ú=ެ¨¨Ø]‡c©•Ëå®uÝ9ìv;EEEÍæAíÌ™3ÄÆÆÞ¬ëü.ÄR“[!FùnÈÌÌ$<<³ÙŒÉdò|áõçå‹Å2øpv¯±óú´Ókpy î2^e¼æU.»¼¦½U7¯.ËëL-biÊ¿wn®uÖWsŠ¥&·BŒ"R3Ý5-""@JÄ"""¤D,""@JÄ"""¤D,""@JÄ"""¤D,""@JÄ"""¤D,""@z²V#q¿BODD¤6JÄ$,,,Ð!ˆˆøUC_Ã*ÕS×´ˆˆH)‹ˆˆ±ˆˆH)‹ˆˆ±ˆˆH)‹ˆˆ±ˆˆH)‹ˆˆ±ˆˆH)‹ˆˆ±ˆˆH)Kƒýæ7¿á©§žÂårU»üäÉ“Ü}÷Ý:tÈ/û{òÉ'Y¶l™_ê 4½ôág³Ùøè£Ø¸q#§OŸ¦¬¬¬ÖõƒƒƒINNfôèÑŒ?ž   ¿ÄqúôivíÚÅ Aƒª,[ºt©_öá6xð`ºvíê×:EDE‰øvñâE~ñ‹_pòäɛަ¬¬Œ¯¾úН¾úŠõë׳`ÁbccKXXK—.­’ˆÏŸ?Ï—_~IHHHƒ÷á6cÆ ¿Õ%"hêš¾EÙl¶:'áÊNœ8ÁìÙ³±Ûí ŽgüøñdffVé~þŸÿù&Mš„Õjõ™ÿÀðùçŸ{ÊÇgèСžòÞ½{™6mÆ câĉüùÏö,›6mëÖ­ó©ï¯ý+?þ8£Gæ¿ø999 >&‘¦ ñ-ê£>jPv;~ü8k×®åá‡nP=V«•É“'³téRúöí @~~>;vìàÃ?äoûÛM×UTTÄœ9søéOJjj*ùùù”——׸þòåËùä“OøÿïÿѲeK¾üòK¢¢¢t<""ME‰øµqãFŸò=÷ÜÜ9shÕªU­Ûåçç3þ|víÚå™÷ñÇ78Lœ8‘÷ߟ'NеkWV¬XÁ<@xxxêÉÏÏÇf³qÏ=÷^ëö‡ƒ¥K—òúë¯Ó­[7RSSt""MI]Ó·¨ŒŒ ŸòÍ$a€V­ZñüóÏ×ZW}………ñðólÙ2 IKKã±Ç«s=·ÝvýúõãñÇç7¿ùOveÙÙÙÓ½{÷†„."0JÄ·¨ÒÒRŸòÍ$a·¸¸8ŸrII‰_bxì±Çسg ,àÞ{ï­õF°š~îd4Y¸p! , ((ˆ^x9sæT»îÕ«Wp: ^D$”ˆÅ¯¢££yàؼy3O>ùdëóÍ7ßøÌ«œ˜ï¼óN^zé%/^ÌŽ;ÈÏϯROBB.—‹ãÇû%~‘¦¦D,~7}útÞ|óMk\çöÛogݺuäææ’••Å+¯¼‚Íf °°õëדŸŸOYY_|ñÁÁÁDFFV©'22’¡C‡²`ÁÎ;GII ;wî¬Òc "Ò\éf-ñ»ÈÈHÏÓ5™9s&óæÍã‰'ž mÛ¶¤¦¦’™™ \»kzÛ¶m¼ùæ›Ó¡C^~ùe,KµuÍ;—×_û·Ãn·Ó¹sg’““«üdJD¤92¨Þê–¼æ»ÇÆjÊÞë½¶s—݃éúØ|}Ú=ެ¨¨Ø]‡c©•»;Õétâp8°ÛíU¹ëowß}·OyÏž=Mº½ˆüë¹páááá˜ÍfL&FãµhƒÁ©Äb± ¾€Ýkì¼>íô\^ƒ»ŒW¯y•Ë.¯ioÕß¼róËëL]Ó"""¤D,""@JÄ"""¤›µš±+W®°jÕ*víÚŹsç0™L´mÛÖç™Ì¹½ˆˆ4>%âf*==ßÿþ÷ûÌ?yòäM=cº¡Û‹ˆHÓP"n†ÒÓÓyñÅk|òTco/""MG׈›™+W®ðûßÿ¾ÞI´¡Û‹ˆHÓR‹¸™Yµj•OwrPP3fÌ`Ô¨Q|òÉ'¼õÖ[ž§Pù{{iZJÄÍÌÎ;}Ê3fÌà‰'žð”Ÿxâ \.‹-j”íED¤i©kº™q?æÑmôèÑUÖ¹ï¾ûmûú:wî6lh´:ýÅ¡>Ç×Ô17Æß@DŸq3SùQqÕ]ë­ë|þùçÕ¾]Ê;F‡ÃÁþýû«|;yò$………õŽÏjµ2|øpŸ/"Ò0ºFÜÌ <˜¯¿þÚSþË_þü_ó¦M›<ó¼·iÈöƒ jPÌv»o¿ý––-[6¨žÆ®Ó_±äææ’œœì÷ý8JKK=_¦1 8L&—.]"''‡=zÔ£Éd"&&†“'OÒµkWÏz‡¢cÇŽõŽÑ`0ЩS§zo/"U)73“&MbÕªU\½z›ÍÆ¢E‹j¼ÞÎĉë½}XX“&MjPÌyyy´n݃ÁÀÇLûöíéÕ«_~ù%¥¥¥Ü~ûílÞ¼™Ë—/ãp8èÞ½;÷Üsï½÷]ºtáØ±cÜsÏ=tëÖͧN¸Ö¥ºuëV²³³q¹\Ü{~÷_¸p;vP\\Lpp0÷Þ{/ñññüùÏ&))‰ÜÜ\àÚ[§N:Å… 0›ÍŒ1‚Ö­[pþüyvîÜIYY-[¶dĈ„††úÄrðàArrrøðÃéÔ©cÇŽåâÅ‹¤§§S\\LHHcÆŒ!44´Æ˜÷ïßOQQ#FŒÀáp°jÕ*†JBBIII¤¥¥Ñ¿Ž;FŸ>}<¯t:lÛ¶ÔÔÔ*]Ë•Ï@JJ éééžD|úôiÏykÝÌýë_™:u*/^dëÖ­Lž<—ËÅgŸ}Æ™3g0´k׎aÆ×®}Ïœ9Ó³MuÇ."7O]ÓÍLTT/½ôÒM½VÌ`0ðÒK/é·íë#33ÓÓŠëÙ³§Ï“»Nœ8A÷îݱZ­ 6Œ©S§òÔSOqêÔ)Or,**"**ŠgŸ}–nݺU©Ó­OŸ><ýôÓ 0€mÛ¶PQQÁúõë6lÏ<ó ýû÷'--ÍÓk·Ûéß¿?S¦L¡_¿~lܸ‘{gžy†ž={òÙgŸ×’Ò'Ÿ|Âý÷ßÏÔ©SIHH`×®]UbéׯmÚ´aÒ¤IŒ;›Íƺuë2dS¦LaìØ±>ݶÕÅü½ï}‚‚Μ9ÃîÝ»éܹ3 Àµ/ ¹¹¹¬ZµŠÜÜ\z÷îí©ëÀ$''Wû÷ªî|µlÙ’¨¨(2228xð ýúõ»©¿é¹sçÈÍÍeÊ”)<ýôÓ>½.n7:v¹9JÄÍÐ!Cxå•W «q°°0æÏŸ_íäÍnÿÊ+À¸çÏŸçŽ;î¸áßÀÛ÷¾÷=8ÀÙ³g‰ŠŠòûDGGS\\ÌöíÛ=Ï)¯Ë±‹ÈÍS×t35dÈ>üðC>üðCÏK ‰‰‰ 4ˆ‰'ÖÚ’­mû{I“&5¸% ׺K/_¾ìI8ƒîÝ»óõ×_S^^î¹–yá¶nÝʰaȉ‰áòåËTTTx¶©­ÎÊ ƒg§Óééºu³ÙlÕ&ŽÊó¼Ë‹…6mÚ0~üø:ÅRQQÕj­vYM1»÷çŽÁ=?33“˜˜O˶W¯^äææ’‘‘Á•+W(..æý÷ßð|I8vìO<ñD1¶jÕ «ÕÊÚµkyòÉ'«,¯|#˜[DDS¦LáÌ™39r„Ý»w3yòdŸc¸Ùc‘Ú)7cQQQüà?à?øA@¶¿yyyÄÇÇû|@÷èу7âp8xä‘G€k7Gyy9999>Ý®7ª³&­[·fË–-žm²²²p:ÄÄÄÔé8Z·nMaa!gΜ¡cÇŽ¸\.ìv;/^¬‹Õj¥¨¨ˆÖ­[ÏÖ­[)(( &&›Í†Ùl¾aìŸ|ò cÇŽåØ±c|õÕWôêÕ‹ÈÈHrrrÈÊÊ"!!’’²²²èܹ3}úôñÙ~ß¾}˜ÍfúõëGNNN­çëî»ï¦U«VUΉÕjÅf³‘MBB999×’¬Íf#99™:ðöÛoS^^Npp°gûú»ˆøR"–©î÷µááá”––CPPIIIœ;wŽeË–aµZIHH¨¶ÕZS5±Z­Œ=š­[·âp8 ãÀh¬ÛU—   ÆGzz:ûöíÃb±pûí·såÊ•*±Üy礧§óÕW_1~üx†ÎÇLPPV«•#FÔz­ôóÏ?'44”N:ѦMV­ZEBB­ZµbðàÁlÛ¶²²2, }úô¡C‡µÆ~£óÕªU+O÷±7ƒÁÀˆ#øøã !::ÚwAA{÷îåêÕ«8Nîºë.Ÿ$ ×.oÔõØE¤ªÆúêz£z«[nðšï«){¯côÚÎ]v¦ëcóõi÷8²¢¢bwŽ¥Vîßi:Nv»¢¢"âââüµ‹fí›o¾!66¶ÖëÑÍ¡ÎïB,5¹b”ï† .ŽÙlÆd2y¾ðú³Äb± ¾€Ýkì¼>íô\^ƒ»ŒW¯y•Ë.¯io7zcŽßߨ£±4Èm·ÝvKÔY_Í)–šÜ 1ŠHÍt×´ˆˆH)‹ˆˆ±ˆˆH)‹ˆˆ±ˆˆH)‹ˆˆ±ˆˆH)‹ˆˆ±ˆˆHéÉZÄû"""5Q"n$u}ûˆHsWRR输Ô5-""@JÄ"""¤D,""@JÄ"""¤D,""@JÄ"""¤D,""@JÄ"""¤D,""@JÄ"""¤D,""@JÄÒ 3gÎdîܹÕ.{ñÅùãÿÀäÉ“yï½÷j¬§¢¢‚””233#L‘fK‰XdÔ¨Qìܹ“ŠŠ ŸùìÚµ‹ÔÔT†J·nÝ¢ˆH³¦D, rï½÷R^^Î|æïÝ»—¨¨(zôèÀ~ô# ˆEDš5½Q$""‚»îº‹mÛ¶qÏ=÷xæúé§Œ9ÒSž2e <òãÇÀápðÖ[o±qãFƒ ò©×f³ñæ›o²yófÊË˹÷Þ{™5k!!!8N–.]Êúõë)((àŽ;îàùçŸ'!!€Ý»wóÖ[o‘‘‘All,#GŽdæÌ™}:DDêL-bi°Q£F±cÇœN'p-ÉîØ±ÃÓ-]E‹±{÷n^{í5þò—¿PZZê³üÕW_%;;›eË–ñþûï“••ÅÂ… =Ë/^ÌÖ­[™?>Ë—/Çb±0cÆ l6………Ìž=›|´´4^{í5†Þ8/"Ò@jߢl6«W¯&--Ó§OWId7bµZINNf̘1L˜0   zÇ2tèPæÍ›Ç?þñúöíËÁƒ §{÷îÕ®_QQÁªU«øïÿþoºtéÀܹsùøã(..fÍš5¬_¿ž˜˜f̘Áœ9s˜3g,_¾œÅ‹“œœ Àï~÷;xàvìØÁm·Ý†ÍfcРADDDQïcilJÄ· üü|~ö³Ÿqâĉz×QZZÊáÇ9|ø0k×®å7Þ 66¶^u………q÷Ýw³mÛ6úöíËÖ­[5jTëgffâp8|nÞ2ÿ¯sæìÙ³8N¦OŸŽÁ`Àn·S\\ @VVv»Ýg{«ÕJïÞ½ùæ›o6l)))<úè£ <˜ñãÇÓ¿ÿz›ˆHcS×t3“’’“O>IaaaµËm6[ƒ“peÇgÖ¬YØíöz×1räH¶mÛ†ÓédûöíµvKÛl6œN§§+»²ððp–/_ÎÚµkY»v-iiiìØ±¸Ö¢v:8Ÿí, ‹£ÑÈ›o¾É믿NPP³gÏæ¿øE½MD¤1)7C'Nœà§?ý)%%%U–­^½Ú¯IØíرc¬Y³¦ÞÛ:”‚‚>üðCBBB<]ÎÕiß¾=ƒC‡U»¼M›6„††’žž~ÓÛ;NŽ=êéªèׯ¿ýíoY²d ééé\¸p¡žG'"ÒxÔ5ÝL>|˜Ÿüä',\¸Ðs§0@ZZšÏzC‡å…^ U«Vuª???Ÿ—_~ÙÓÊt×=qâÄzÅÂÀyõÕW™6mÚ ×;v,/¿ü2¿ÿýï‰çÝwßõ,7›ÍL™2…… OJJ /^äÒ¥Kôèу|ðA^yåþð‡?кukÞ~ûmbbb0`………lß¾ÁÁƒ±Z­DEEÕëØDD“ZÄÍØ?þñ~ùË_ú<,###Ãgú$a€V­Zñ /øÌ;}útý½ÎÝ][·´ÛóÏ?OJJ ³fÍbÆŒÄÅÅy~z0mÚ4žyæ,XÀ½÷ÞËøCŽ?îY>{ölÈ/ùK{ì1ŠŠŠX¸p!ƒ¢¢">ýôSžzê)FŒÁš5k˜?>‹¥AÇ'"Ò ª·ºå¯ùî±±š²÷:F¯íÜe÷`º>6_Ÿv#+**v×áXjår¹<×,ív;EEE´k×®^õ¥¤¤T™w÷ÝwóÚk¯a±Xª,ÿüóÏ뵟šö×ÐúDä»+33“ððpÌf3&“És“¥û¦J°X,o`÷;¯O;½—×à.ãUÆk^å²ËkÚ[uóê²¼ÎÔ"¾ìÙ³‡¹sçV¹9IDDn}JÄ·ˆmÛ¶ñ«_ý*ÐaˆˆˆŸ)ßB¶nÝèDDÄÏ”ˆEDDH‰XDD$€”ˆEDDH‰øòä“O:ñ3%â[Äc=ƬY³†ˆˆø™ñ-àÁÔK DD¾£”ˆ›¹±cÇòë_ÿÚóäšÐÐPŸå y‘Aåm+×]gÏžeݺuõÞþFuþéOòkÝ ‰åf5uÌ ý8þüç?û1"¹JÄÍØðáÃùÍo~ãó®ÞŽ;ú¬óÊ+¯Ô+_¸p—_~Ùg^RRRýåÚ;‚ëûXϦ¬³¾š*–ÜÜ\V®\Éÿ÷ó?ÿó?|óÍ7UÖq¹\|ðÁUGêŽñÿ÷ùç?ÿYe›Å‹“ŸŸÏâÅ‹=ïv‘ÀÓÛ—š©ò‡?ü“Éä3ÿþûï端¾ò”wìØáó¥†;vl½·ÍÌÌdذa~‰£1ë¬/ïXvìØA¯^½ˆ‰‰ñû~6lØ@jj*‰‰‰œ9s† 60cÆ ŸÿƒPZZZcŒAAAœûŒ²²2Z¶lIjj*¡¡¡>±|þùçdgg³råJ’’’7nùùùlß¾ââbÏ»–CCCkŒyß¾}’ššŠÃá`åÊ• 6Œ„„’’’ذawÝuG¥oß¾ž/dN§“O?ý”Ñ£Gû¼²òß 88˜öíÛ“‘‘A=€k¯Ï0`píÚõsÏ=@vv6ééé”——íó£¦óQÓùv¹\ìØ±ƒÓ§Oc2™hß¾=ǯ×ÿ”È¿%â[Pll,ï¾û.kÖ¬!--S§NQRRR§:BBBHNNfìØ±Œ?¾ÞI®u{º¯ŸöêÕ‹xñ‰'3f V«•#FIYY+V¬ ))‰6mÚPXXHTTÓ¦MótÁz×éÖ·o_RSS9qâ[·neêÔ©TTT°víZ&L˜@ll,¬[·ŽgŸ}³ÙŒÝnç®»î"::š¯¾úŠ´´4¦NJdd$‡"==É“'SRR¦M›xôÑG‰ŒŒdÿþýìܹ“Ñ£GûÄ’’’BFFC† ¡uëÖØl6Ö®]˸q㈋‹£´´”àààZcîß¿?«V­âÌ™3œ?ž.]ºxZÁdåÊ•¬\¹’¸¸8zè!O]û÷ï§sçÎDFFÖú7èÚµ+'Ož¤GdggÓ²eK¬V«Ï6ååå¬_¿ž‡~˜ØØXΞ=Ë™3gj<Æ «ñ|gff’““ÃÔ©S1 ØíöúýC‰ü‹Q"¾E™Íf&NœÈĉ YYY 2¸ÖºuëVŠ‹‹±Ùl˜Íf¢¢¢€kݧûöí£¨¨ˆââbŸ†*wµz× `4=]Ã]ºtaÓ¦MÀµ]tt´gY§NØ¿?—.]">>£ÑHtt´g»Ï>û̓ȺtéÂ<õ³mÛ6***<]•cñ–““CXXqqq>É®¦˜ ÷Ýwï¾û.qqq<þøã”••±zõjÆŽKBBGŽaùòå<öØc”••qîÜ9&Mštÿû˜ =zôàäÉ“”——{l^^[¶laøðá´lÙ’‚‚ÊËË|î ¯®ÎÊ ƒO—uåkéUêªÜøæ]nÑ¢ >-Л‰¥¢¢¢JKóF1ž˜M&“gþùó牉‰ñ´l{÷îMNN§OŸæÊ•+\½z•eË–pùòeŽ9ÂÓO?]%F‹ÅBûöí9sæ gΜñtKW>¶êîC¨í|dddÔx¾###™:u*9r„;wòÄOèz´È èf-iÜÜ\âãã}>l݉8##ƒÎ;péÒ%‚ƒƒ‰÷´¬*'ÆÚê¬I›6mÈÉÉñ\ÎÊÊÂårѲeË:GëÖ­ùöÛoÉÈÈ®ýÜÇf³U‹Õj¥°°Ð³]VVØl6\.× ÷·iÓ&ÆGHH‡ **ŠœœÏ1“™™Itt4Æ cÚ´i<óÌ3<óÌ3 0€AƒñÌ3ÏÔx¾ºvíÊ®]»hÙ²¥§¥[ùÜegg“——\ûI›ûËQM磶ó]^^NYY;w&55•+W®xꑚ©E, RÝïk#""())!66–   ’““9{ö,ï½÷V«•¶mÛÖx]º.¿ÙµZ­Œ3†-[¶àp8 cܸqÕ¶ˆkÄøñãÙ¾};{÷îÅb±Ð§O._¾\%–~ýú±}ûv>Ì„ 9r$6l ((«ÕÊÈ‘#k}8Ê !))‰„„>øàÚ¶mK«V­2d[¶l¡¬¬ ‹ÅÂwÞÉm·ÝVkì5¯Ž;²aÃÏMq•Y­VFͦM›0ÄÇÇQëùHNN®ñ|°gÏ®^½ŠÃá`À€>×ËE¤zÕgt£z«[nðšï«){¯côÚÎ]v¦ëcóõi÷8²¢¢bwŽ¥VîÖÓéÄáp`·Û)**j6¢hlgΜ!66–ðððf]çw!–šÜ 1ÊwCff&ááá˜ÍfL&“ç ¯?/?X,–À·€°{×§^ƒËkp—ñ*ã5¯rÙå5ííF]Z7îòª#µˆ¥A*?髹ÖY_Í)–šÜ 1ŠHÍtXDD$€”ˆEDDH‰XDD$€”ˆEDDH‰XDD$€”ˆEDDH‰XDD$€”ˆEDDH‰XDD$€ôd­Fâ~ €ˆˆHm”ˆILLL Cñ«’’’@‡ð¤®i‘R" %b‘R" %b‘R" %b‘R" %b‘R" =âòµ}ûvV¬XÁ±cÇ(--­vzõêÅSO=ÅÀ›8B¹JÄ· ?ýéO,[¶ì†ë•””°ÿ~öïßÏŒ3øþ÷¿ï÷X&L˜Àùóçk\þ¿ÿû¿tèÐÁïû2e <òãÇ÷{Ý""MI‰ø³}ûö›J•ýå/áöÛo§ÿþ~çý÷ßÇét°bÅ öïßÏo¼áYâ×ý‰ˆ|×èñ-fÅŠõÚÎårÕ{ÛÚ„„„FXX-Z´Àd2yÊaaaú©ZÄ·˜ãÇû”ß~ûmúôéSíº»wïæ¹çžó”=Ú¨±ÕdÊ”)<õÔS¬_¿žüãüÇüwÞy' .ä³Ï>ãÂ… ´nÝšéÓ§sÿý÷{¶Û½{7o½õÄÆÆ2räHfΜéYþüƒ´´4Ž?NÛ¶myþùçk<""Í•ñ-¦òû@kKù„ßýîwôìÙ“:PXXÈìÙ³™5k£F"??ŸòòrO}—/_fíÚµ¼ûî»´mÛ–·Þz‹9sæ°~ýz‚‚‚vœ""u¥DÜÌìÚµ‹yóæqáÂ…›Z?%%å¦ë.++«qý¸¸8~ýë_7ÚÝÕãÇç‘Gñ™7iÒ$ÏôÔ©Sùè£8rä:t ??›ÍÆ Aƒˆˆˆ ""ÂgÛèèh¦L™BïÞ½xöÙgùûßÿÎùóçéÔ©S£ƒˆHcP"nfê’„ýéÂ… Ì›7 64Jý·ß~{•yW®\áã?æË/¿¤¨¨ˆÌÌLÊÊÊèØ±#)))<úè£ <˜ñãÇW¹ÑÌb±x¦°Z­mõ‹ˆÔ‡î¤‘€(--åÙgŸ%??Ÿû·cÁ‚té҇ÀÑhäÍ7ßäõ×_'((ˆÙ³gó‹_ü¢Ö: CS„."âWJÄÍ̯ýkâãã›|¿ñññÌ;·ÉöwìØ1®\¹ÂsÏ=G§N áäÉ“äååù¬×¯_?~ûÛß²dÉÒÓÓÒ[ "Ò˜Ô5ÝÌ 8Ч{¸ò5ÝÏ?ÿ¼Aõû»¾úЉ‰áêÕ«üío#55•׺˜ Ù¾}; ""‚ƒbµZ‰ŠŠ H¼""E‰Xâ¶Ûnã¹çžã/ù o¾ù&dذažåEEE|úé§,Z´ˆââb:tèÀüùó}® ‹ˆ|4ÖEµÕ[Ýrƒ×|÷ØXMÙ{£×vî²{0]›¯O»Ç‘»ëp,µr¹\8Nv»¢¢"Úµkç—ú¿«-b¹õdffŽÙlÆd2yØãÏû3,Ë@à[ÀؽÆÎëÓN¯Áå5¸Ëx•ñšW¹ìòšöVݼº,¯3]#¾ÅM›6éÓ§ßô|i^Ô5}‹ûç?ÿY§ù""Ò¼¨E,""@JÄ"""¤D,""@JÄ"""¤D,""@JÄ"""¤D,""@JÄÒìýéO t""Fôùàƒ(..ÆårQ\\LXXqqq<øàƒŽND¤ùS"¾ÅÝqÇÕ>給ùþöøãP^^ÎÛo¿Í÷¿ÿýFß§ˆÈw‰ñ-îwÞ©Ó|i^”ˆ›¹JJJ<å¼¼<âããëUW^^žO944´A±ÝŒ%K–еkWŽ=Ê AƒHLLdÓ¦Màp8èÙ³'ƒ Âår±cÇNŸ>Éd¢}ûö >¸öf«Í›7“ÓédĈ$&&6zì""MA‰¸™ëر#GŽñ”çÍ›Çܹs뜌óòò˜7ožÏ¼N:ù%ÆÚÅ´iÓ0™LžDIYY+V¬ ))‰òòrrrr˜:u*ƒ»ÝîSOß¾}IMMåĉlݺ•©S§6zì""MA‰¸™3fŒO"Þ½{7cÇŽõ[ÝM¡gÏžži£ÑˆÝngß¾}Q\\Lqq1­ZµâêÕ«lÛ¶¤¤$Ÿ¯Ñh$66€.]º°iÓ¦&‰[D¤)èçKÍÜÃ?L—.]ü^o×®]™0a‚ßë­Ìýâp·¼¼<6nÜHbb"C† !>>žòòr"##™:u*íÛ·çÈ‘#¬X±—«êû· C“Ü„&"ÒT”ˆ›9‹ÅÂo¼á×dܵkW^ýu‚‚‚üVçͺtéÁÁÁÄÇÇãp8ÈÎÎÆd2Q^^NYY;w&55•+W®P^^Þäñ‰ˆ45uMßâââxï½÷X½z57näôéÓ>7pÝŒ’““¹ï¾û˜0aB@’0@rr2gÏžå½÷ÞÃjµÒ¶m[Ìf3ìÙ³‡«W¯âp80`ÁÁÁ‰QD¤)5Vßê­n¹Ák¾{l¬¦ì½ŽÑk;wÙ=˜®Í×§ÝãÈŠŠŠÝu8–Z¹»ON'N§›ÍFQQmÛ¶Uªˆ|g¸\.²²²'((£Ñè¹ôäÏÏ:‹Å2øpv¯±óú´Ókpy î2^e¼æU.»¼¦½U7¯.ËëL]ÓÀh4ât:o¼¢ˆÈ-ÂétV¹çCüCgÕOÜß ƒçš§ˆÈwEyy9&“Éç³NüC‰ØÏ܉øêÕ«EDÄo®^½ê“ˆÅ”ˆýÈýÓ³ÙŒÍfÃf³:$‘sž™Ífý„°(7÷?¤Á`ðÜÀ`±X(((`d""þQPP€Åbñ|¾UþÜ“†Q"ö÷·DïV±Ó餰°0С‰ˆÔ[aa!N§Ó§5¬V±)û™û±ÉdÂb±PXXXçßüŠˆ4%%%b±X<ŸkJÀþ§DìÞwº£ÑˆÙl&88˜K—.©e,"·”ÂÂBÏ“ðÌf³§KÚ»5¬¤ìz²V#ðþ­Ëå"$$„¢¢"JJJˆ‰‰Áb±0:‘šUTTx^Sâi ›L&ýޏ‘(û‰Á`ÀåryÆ€§U ŒÍf#77‹ÅBDD-Z´¨rトHSr¹\8NÊËË),,¤¢¢‹ÅBpp0&“ÉÓvSkØÿ”ˆ»kÚétb2™|æ™Ífìv;žÇbŠˆ’ûnh³ÙìiFOKØ½Ž’oãP"ö#ïÖ°;ñº\.Ï îGĹoär:¸\®j_÷'"Ò¼ïkñþ ¦÷O•*ß%­„ì_JÄ~V9{Ý­dwòuO‹ˆ’w"vOÕ¾ÔAIØÿ”ˆAådìr¹ªÜäPyž²ˆ4µÊ Ö»ÑPÓ:âJĤº–±wòu/S‘@«î¬ÊIWI¸ñ(7"ï\¹¬;E¤9RnzJÄM ¦„,"Ò\ésªé(7!ïluI‹Hs£äJÄ¢x=kZDD$ ”ˆEDDH‰XDD$€”ˆEDDH‰XDD$€”ˆEDDH‰XDD$€”ˆEDDH‰XDD$€”ˆEDDH‰XDD$€”ˆEDDH‰XDD$€”ˆEDDH‰XDD$€”ˆEDDH‰ø; //¼¼¼@‡!""õ`tßeS¦LáäÉ“ âââHLLdܸqŒ5Ê/û())áÑG`ãÆX­V¿Ô+""MC‰¸ Œ9ƒÁÀùóçùâ‹/8pà[·neþüù ®;88˜¤¤$Z´hÑàúDD¤i)7^x°°0Ž=ÊóÏ?Ozz:6l`ìØ± ªÛh4²Ãˤ IDATdÉ„)"" kÄM¬GüøÇ?`Íš5žù¥¥¥üçþ'&L 55•Ù³g“““ÀôéÓ0`‡ò¬ÿÿñ 0€}ûö1|øpî»ï>Ï2§ÓÉ{ï½Çã?ÎðáÃùáȾ}ûn¸€;w2mÚ4†θqãøÉO~BQQQ£ž‘eJÄ0dȾùæϼ¹sçò·¿ý.]º0xð`öìÙÜ9sp:žëÉ{öìñ¬¿k×.¢££ùÞ÷¾W¥þ ðÖ[oa·Û}º:::´dÉÍš5KëׯWmm­¦M›&éh .\¸P?øÁôî»ïêÎ;ï”$=øàƒÚ³gž{î9½øâ‹jnnÖŠ+"ûÞ¿¿þüç?kñâÅZ»v­N;í4ÍŸ?__|ñ…jjjôüóÏ«©©IúÓŸÒ>gÈ&ZÄuðàÁ¨åX!¼sçNÕÕÕióæÍjii‘$UTThÒ¤IºöÚkUUUÕç=ö}uuu¥]Ï`0¨•+WjÍš5zñÅõØciúôéºùæ›5nÜ8]tÑE:tè>þøcsÎ9joo׎;4uêTµ¶¶ª»»[S¦LÑСCìêêêR]]^ýu1B’tÛm·éÎ;ïŒõ°aô`ÁuÖY’¤Ÿüä'úûßÿ®… FÞ3kÖ,­]»6ís€l"ˆ T(ÒC=¤W^y¥ÏHÆÆÆF566jÕªUºîºë´xñâœÞ¦TZZªùóçëÆoTCCƒ^zé%ÝrË-zæ™g4nÜ8M:Uÿþ÷¿uÎ9稡¡A“'OÖàÁƒUYY©êêjÍ;W_|±fÏž­‰'Æ<ÎîÝ»e†.\¹m¢§§§ÏÖ{¸ÇŒ#éè6fWVVöùCÜB P(¤E‹iË–-qˆ¡Õ«Wë³Ï>Ó#<’ó{†€¦M›¦K.¹DsçÎU}}½Æ§éÓ§ëÉ'ŸÔÏþsmܸQ3fÌt40{ì1mݺU¯½öš–,Y¢êêjÕÖÖ:îßü^xA'tRÚõäÞG^Â5âT[[›0„­Þ{ï=-_¾<‡5ŠVTT¤²²²Èï—^tÑEjnnÖ§Ÿ~ª>ø@S§N*?aÂÝsÏ=Z¹r¥6nÜuÙzkUEE…¬7æçD â³sçÎ>×7ËËËUSS£††544hÙ²e=ztT™5kÖô– Û¶mÓ²eËôî»ïjÏž=úüóÏõä“Oê“O>ÑôéÓ%½Ž|ñÅëÁÔy繿¸££CëÖ­S[[›>¬­[·ª¤¤DÆ ‹œ×û￯––:tH@@óçÏ׊+ôî»ïª··W{÷îÕöíÛ³~^/tM˜W_}5êšpyy¹^~ùå¨N—^z©Î;ï<Í›7O{÷î•t´›º®®N‹/Îj}Ì.âeË–©µµUÅÅÅ:óÌ3õøãGý10cÆ ÝqǺï¾û"ë:;;õÆoèÑGUWW—F­x òƒ3gÎÔ¦M›tà 7h„ Z¾|¹n½õVp ‘ã1B7ß|sdpš\],K´_§í>ËzsêwX¶–ñ[Þg.›¯¢cÓÀ±yszR(z;…s‰ËúŒéÞÞ^õôô¨³³ÓñžÚTTWWG-›]ÑsçÎjÙÖÔÔDnù±«¯¯ºU©ªªJ«V­Š»ÿ\ùüóÏ#×3}âw455iÈ! ***Š ŒÌ渋`0x¡¤¯%õJê±Lcó†å¶¼ÌeY–eYg_[æ­=6,ë£kºÀ˜-\ÓäÉ“c–½à‚ ¢–[[[sR§d¼ù曚8q"! 6qIåž_ûó`Ý|`û?þñ]vÙe®¼Š .p›7o޹íwÞ‰Z9rd®«ãè‹/¾Ðÿû_]rÉ%®¼Œ .p+V¬PGGGŸõ_ýuŸ[–ÆŸ¯jE5j”6oÞy à8‚¸ÀíÞ½[óæÍS}}½ºººÔÕÕ¥ 6hÞ¼y}žû|å•WºTK@,ܾÔìÝ»·Ï9ØMš4)îã#î E<TTTDÝ¿ ð‚¸Ÿ;ÿüóõÌ3Ïèä“Ov»*tM÷3%%%9r¤Æ¯+®¸"î}Æ÷Ä&×OÀJ×îÝ»õá‡êšk®q»*PPèšFV477güXOˆâa¿É+û2555Ä‚¸@ÜÿýY ж¶6-]º4 5:®§§Gû÷ïWYYYV÷ ׈=ª´´TŒ,oÚ´IW_}uNŽ5xðàŒÞßÚÚªŠŠŠ¬þ ´ˆ=j̘1y;VUUUFï§[ÒG{T®Z¿NfΜ™Ñû¨é#ˆ=jΜ9:ãŒ3r~œ³Ï>[³gÏNûý†a諯¾âú0¤‰ ö¨Aƒéá‡Ö™gž™³cœuÖYª­­U þPÖÖV•——s}ÒÄ`-+++ÓÓO?­ºº:­_¿^;wîŒÀ•ŽÒÒR;V3gÎÔìÙ³3 a‰niÈAìq@@×_½®¿þz·«â¨¬¬ŒniÈAŒŒTVVº](h\#ÀE1."ˆpA €‹b\Dà"‚ĸˆ ÀEú裌!I¡PH‡ÎʾÀ+bêîîÖ/ùKíØ±#gÇøÏþ£Ûo¿]===9;F6=õÔSúÝï~çv5 «bZ»vmNCØôÉ'Ÿ¨®®.çÇ8ã±G­_¿>jyÊ”)ºûî»õo|#£ýîÛ·OK—.UCCCÔ±®¿þúŒö›,Ã0ôì³Ïêõ×_W{{»¾÷½ïé׿þµ¾ùÍoJ’Þ~ûm=þøãjllTYY™¦OŸ®_üâzî¹çôÔSOI’êëëõ£ýHwÞyg^ê ¹D{TcccÔr6BX’FŽ©»îº+*ˆwíÚ•ñ~»»»“º†ýÄOè­·ÞÒ< ’’ýñÔm·Ý¦¿þõ¯:tè–,Y¢Ûo¿]W\q…öíÛ§#GŽH’æÏŸ¯®®.555éþûïϸ¾à±GÙ€;VïܹSuuuÚ¼y³ZZZ$Iš4i’®½öZUUUõy}_]]]×wõêÕZ½zuÜ2¡PH/¼ð‚žxâ ;V’tï½÷ê‡?ü¡ôío[ÝÝÝš2eІª¡C‡f\/ð:‚¸@…B!=ôÐCzå•WdFÔ¶ÆÆF566jÕªUºîºë´xñâœß¦tã7jñâÅ}ÖWWWGæ›››ÕÓÓ£3Î8#²®¤¤Dçž{®>ûì3]vÙeª®®ÖܹsuñÅköìÙš8qbNë nc°V …BZ´h‘Ö¬YÓ'„­ ÃÐêÕ«µhÑ"uww籆ÎB¡ ÃPoooÔú`0¨`0(¿ß¯Ç{LË—/× Aƒ´dÉÝqÇ.Õòƒ .@µµµÚ²eKÒåß{ï=-_¾<‡5JΨQ£äóùôÁDÖ†¡íÛ·Gºª%i„ ºçž{´råJmܸQmmm‘m…r«$‹ .0;wîÔÚµk£Ö•——«¦¦F jhhвeË4zôè¨2kÖ¬é3,ßJKK5kÖ,ýáÐöíÛÕÞÞ®eË–iĈšËzsêwX¶–ñ[Þg.›¯¢cÓÀ±yszR(z;…s‰+KRäÚgOO:;;õ­o}+£ýZ9IŠtEÏ;7ªe[SS£iÓ¦9¾^wÝuWd¹ªªJ«V­Š»ˆ¥©©IC† Q PQQ‘üþ£_Ñ>_ö¢$ ^(ékI½’z,SãØ¼ay…-/sY–eYÖٗÖy+§u©lO-â³wïÞ¨åÉ“'Ç,{ÁD-·¶¶æ¤N€ôÄ&•{~ÍÖº)Þk€;â·yóæ˜ÛÞyç¨å‘#Gæº:€ÄnÅŠêèèè³þ믿îsËÒøñãóU-@’â·{÷nÍ›7OõõõêêêRWW—6lØ yóæEÝ+IW^y¥KµÄÂ#.û½{÷FŽv2iÒ$ D‹x¨¨¨Ð}÷Ýçv5â~îüóÏ×3Ï<£“O>ÙíªÐ5ÝÏ”””häÈ‘?~¼®¸âЏ÷ÜG¯>k÷îÝúðÃuÍ5×ätß<òˆ-Z”õc€[èšFV477güXO7ö n#ˆ „ýV$¯ìËÔÔÔ”³°Ìå¾ÔÞÞž“}@2âqÿý÷g%@ÛÚÚ´téÒ,Ô踞žíß¿_eeeYÝo®÷-}þv(Êɾ \#ö¨ÒÒRÊ˱ªªª2z¿½ë¸££CÆ Ó­·Þª¢¢¢¨?(œôôôhâĉ>|¸vìØ¡þóŸZ°`ã¾%éûßÿ¾f̘U6 iݺuš3gŽÊÊÊÔØØ¨×^{M·Ür‹šššÔÒÒ¢ Èçó©§§GÒÑŸ‚lllÔÔ©SuÊ)§dô@ºèšö¨\µ~Ìœ93£÷; ¦:ûì³UTT”Ôûý~¿†.I7nœ:;;cîÛï÷Gº©­e÷ìÙ£áÇG¶3F'žx¢¾üòK >\пþõ/íÞ½;éz@>Ä5gÎqÆ9?ÎÙgŸ­Ù³g§ý~Ã0ôÕW_E]Ã5,Ü^.>Ÿ/ªÚ¾ïxeƒÁ`ÔöP($¿ß¯“N:I ,ШQ£ôñÇ륗^êó‘à‚Ø£ ¤‡~XgžyfÎŽqÖYg©¶¶V@úW(Z[[U^^ D'%%%êîîÖž={$m½:ýbT:û6UTT¨¥¥E­­­’޶¤Ãá°N>ùd9rD‡Öé§Ÿ®3fhÿþý:räH¤nÉÔr…kÄVVV¦§Ÿ~ZuuuZ¿~½vîÜ™ðzk"¥¥¥;v¬fΜ©Ù³ggÂRr÷øú|>MŸ>]ûÛßTZZª#F$5@,•û‡KJJtÕUW©¾¾^½½½:ñÄuÍ5×Èï÷«½½]ï¼óŽ8 ÞÞ^MžE®A@päÈE}×!;â,3ƒøÀnW²æÀQAŒì!ˆ³È¼&¨»»[ÝÝÝnW 2f~Ÿ¨Û‘q†¬ÿ!}>_dC0äÇô ííí ƒ‘ï7û÷2Cg‰ùW¢µUl÷¨(h2 #ª5L«8»â,3¯) ª££#ã{À TGG‡‚Á`ä{Î>‚8 ¬£Í—ßïW Pqq±¾üòKZÆ JGG‡¾üòK+Dº¤­­aB9;¸8¬÷Ú…Ãa•––ª³³SÔˆ#ú<¼" ©½½]½½½*--´„‹ŠŠ¸8Gâ,ñù| ‡Ã‘©¤H«X’Š‹‹ÕÝÝ­ÖÖVƒA :T'œpBŸOápX†aèÈ‘#êèèP(R0Tqq±ŠŠŠ"­a­áì#ˆsÀìš¶>]ËÚ]ÝÓÓ£öööÈc1ÀMæhè@ iûýþHKØ,CøæAœEÖÖ°¼áp82ÀÁ|Dœ9Ë0 …Ãa~’€k¬ãZ¬·`ZoU²’&³‹ Î2{[§f+Ù _sÜd bs^’ã:ÂÙGç€=ŒÃápŸAöu2€|³¬µÑ« ² Χ–±5|Ím0·9 À²‡.!œ;qYؾÌÈC^DçAœ±¼Šï©ü!ˆóÈú›.i^Cøºƒ v ÿáÏšÀU1."ˆpA €‹b\Dà"‚ĸˆ ÀE1."ˆpA €‹rÄü´ ¿ÉI¶z‹˜Àt¸Äý¡ú%W²©ÐZÄ8 ‘‚Ê ¯q:fAýRÒïrÁëA @¿Fà"‚y-ˆ³Õw?>Ÿ¯-KÇdIßÍyɈ|óZ'Ãþ†l Ûæ{»»»wä¨n€4ûnî•ów·U¢ïý‚âÕ Ç™Úçc½ ËÔ|IRw[[Û–\VºcßÍÝÇ­ßÝÖïôx/9Ì;M=ÅÍ NöI÷ƒsú‹ÉÔ³råÊMiî#Ǿ›{t¼á¯å›¬\gMÆ|.ïÛç0oŸJGÿ`°®·¿üË~IE–WÀ2=ñ«¯¾úÍàÁƒg¥vJ€\èêêZ7|øðû%ÐÑ0îµLÍ—µ…lïýŒÕ:6Ž%n 9™ î÷ÏšNt‚ñ¶[ÿ!cÞú:R[[û|úUdÓ±ïä#J¾ËÙ²v™dJ^å²EœÌþíÛcµŠýË>[9¿Ã4^«¸dëÖ­?<÷Üsÿ_ çȲmÛ¶Ý;a„×%Rr­aûTêÎÖK’RßVpªƒ¼rÜ…ÄÖngsÙoÛ¯{Ú¯£ák±ùüé§ŸþøÔSOýeJgÈŠæææ‡+++ÿ"©KÑÁÛ£ã׋“é––œƒ9Ù[®±Û]Ó™œX¬[•âý#Xÿ1 I‡*++WmÛ¶íÞ êHömÛî­¬¬\¥£-aëw³y “”ø»]–õérµ›º(ÇûÏæ€­D­d§u֮찜»´{žxâ‰Ï ÃØpþùçŸ ¿“DiêêêZ·téÒÿ»é¦›¶H:¨¾­^k;ÝW,ÅcOß®dçv×´½L¼àµ_'vêžvImíž¶vS[¯û% ’tÂÝwß=jáÂ…žrÊ)‚Áà¸p8ü$΃Ïçk …BÿmmmÝúÔSO½ýûßÿþ ˜Õ­ã¡k±½;Úœ&!mï–6×Éa}¼ùX öq2ÇÈä:q¬kÇÖ¶_+¶OíóË˾¯||^ÐØÒ Yëµ_{KؾÖmö}ź3ƾNq¦öº&:—œärçI +¹€3ËY§²­³–5t¼KÚHâÖ‹ÿ=Šô€Äb¥Tl˜<™IDATÓSc…¯5p­ó±Ža_—J=]ã… Ž%Õ€6çÍ6—Mæ_T>ý‡6·)ú?‰OǃÛÚ–bH–5º”ã]v!m²rÁn,^ b§Ö­¹lmåÚ[Âöy“YÎüÇ•¢»­eÛn¿ªWtM@ªœ®ÓÚCÙÞ*¶·„ã”vš·Ó^ÎiÙuùâd[¶™ì_êÂf›ámZß¶•³·ˆ­ÝàÖãœ9µ`uQ;Í[ß/9‡q®ƒ5çÁ¯PÉæèi)ñjë:û¨k¿¢aÅqmßw²çY¬®äx#Ÿía,E·¦cŠN¦5ìÉÑÒ&/vMKéµ¢cuWKÇ[¹f‹8¬è.nkHÓ"€ÌÄk›Ûí-^{ØÚ»´íûN' =×--y«El/—N«8ÞíMN÷#ǺÙZÎ>o¯à8{Øóö`vzT¥½\¼kƹj §R.mùjçò:±µ%lr´e2lÛ­­b³¼_ÇGVÇÚõ80ÐÅúž´ÿB’=˜l¬ë™´Œ“•—´W»¦%çÑÖuÖÛ”ìï±ؒòS¶Yƈ³œá  ¿K% â °rê~NÊ±ŽŸJkØSòÙêž¶Ï;ÝZäÔmo}¢Y„+¤Ç)YÅêfN¦û9ÖíJžï––rÿ£v©Žž¶/Ç Fûú°Ã:{‹:V]œþSħŸ.”â·ZS áXa™ik8o-è|L:­bër¢Vq¬õÉ öŠ5ïA éŠÕ¥/€­ëµzµ†íê©Ö°äͱS¹DaioµÆk›óö9Ù2 ±X­ìe¤Äan7Zz¹ c)qË8Ö{Z½±Ž¯Ž0P¥ÓEœLk9“ö|kXòv;•Mv WªAkðV¬z¤ŠÀP¨2 ¥x­`ûödƒ6Y©Žôη"Û­b§méµÓv§2€äÄk;•I´É†mA´†%ï±SÙtÃ8ÕíñÊ’ã¼rX—LÈæ:„S-›nL>ÂØ\Nç–¨TÖŽK&x­ëR àXe’Ù–ˆ+ƒ¼ò=jÚ.Waœìºda¥Û•ýUº]½©^ÓM4º CXr?82 bûºT¶Ç+K+²'Ù–°SùLB8ÖºXlK™‡±}}¦lÝæ…ÏúƒX׊­ÛÍ'»=ÞúLËf‚&Õ:¤ƉÊ$ ådÊ’¸F*×zÓ-“ŒÄRöÂØº-ÁšÖ:ôWÙju¦Ü©„{"®?…ËKá‘‹0Nv¿‰ÂÛKŸ’D­Öd‚0Ýv*ûu×&úä¢K™ëÃ}‰®;•wšW.}»Êka“n}²ÑêÍÕ{` J¥Å›É{Ó T‚8Ž\†±µl6ÏÝ‹Ÿ#äC6-“sªÇñ ¯H&õJç½þÄE9`$.ÒG&Aê©–¼ÄRþÃØþ^/6Pˆ2é®¶ï#ßïͯ‡M¦õËÆùyý3¯ËFfºO†°T!ãÕ0-„Ïò)aç…Ï©B “lյ΢l§§ØTh¡ÄHg迲=» bÑÍ ý‡W»³ó¦(—u/äϼ,—!YPlê“ÏsèŸäC>C± ØÔ_‚Å‹çáÅ:@&¼x^¬SJú[Xô·ó8+ø6õçàêÏçQ¿ _«Vá ?ë—lh!5ÐÎ U¿_«LýüÀ+LðÚDÑø< ?lðÚ<Éás€ô¸ 0ÙÇg  ¿#\Ùõÿõ:[6«ºIEND®B`‚Portfolio-1.0.2/screenshots/en/2.png000066400000000000000000000714431476103320100173060ustar00rootroot00000000000000‰PNG  IHDRâ6*Ù€³sBIT|dˆtEXtSoftwaregnome-screenshotï¿>-tEXtCreation TimeFri 09 Feb 2024 04:04:48 PM -03j? IDATxœìÝ{tTÕÁþñïÌdÉÕr€r‰B­ ×E¬ZÁ…¢¢FDªëE-D-±*úB[é+*¢‚Ø Øe /EˆH_/@ëÅ‚¼„\&—¹ýþ€Œ“0ÉL’™œIò|Öš5snûì™$ódï³Ï9 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""˜Œ®@+¤ÏTDZ;·ÑhMÑç$"Ò8 m?05éói è3Úzð´õ÷/".Úl0·µ jkïWD¤¥j3ÁÜ‚©-¼G‘Ö¬U‡rk©ÖüÞDDÚ¢VÈ­-¬ZÛûßZM(·–à Ç÷ŽuiŠp ¿p¬Sƒ´†°hÎ÷Ð>/‘æÐœ٢ø%K(ën qù""m‘›Ð†f‹ ä–6¡¨³9eŠˆˆ®”Ù¢¹¥q°ê«¯ˆHø f‹¹Å„qK £¦ÖÕTë¹9÷-"ÒV5&ݵž›sßÍ®%LSêØØð tý–ðù‰ˆ4‡@C¯¡áŒPë@÷ ilýÀþÖÓ1d‘Æñw ¸¡ÞØP Û0ç nJûÛ¶¾å¦:^‹ˆHã¹ëx]ßzu-oUa®AӔ㸆líùÙNDDêç/tëZHX7åøsØÇ€il74HC¾áøyŠˆ„B°C°®Pnl7¶Í.Ü‚£±ƒªêkéúšW×|ûñ·žˆˆ4­º¡ÜØÖqØ„q¸…JCêÓÐöÀþ¶mÊ)O""rvPÚí+ƒÆ bÂÁ`_¡NŸˆHKæ«•HË·¡ÜâÂ8\‚&T!\û´£º¹1]Üþ„Ëg+",Á<믥\{ºöiP­&ŒÃ!,Bµ׫/€ÒZ®o ¬õZ_«·¾ík‡l«ãp”†Ô¡® kÂu°¿ Õ=DDÇ_K¶® ­+p ã†ÜL¢Mq°CØ\Çü@¸!-j L !ÚÐ@vÕ1Ÿ:Öñǰ06:`r J]Æõ…p \ßñdµˆED§¾Àtù˜ï+Æ é¢n“AÜÔÖpcBØ_ׯÁÄ%"Ò5ôÔ"—×u¬nJ‡}«Ø¨ f×´¾–Õ`_­åúFS+|EDêWß©J¾Bµzºv Æ3ë0÷ öw\¸®6ûXÏûÙìcÛºŽ#›ë©‡ˆˆÔÏÅÙ­V_!Zýº¾0ß-àúF]{×#m"ˆƒÕ$„ÀÞÛ™ñ]¦ˆˆ4Líàtá»H 7%ŒÃ¶UÎAh{ÏoH›}Ì÷~í½½‚XD¤q¼ƒ×»‹ÙÅÙ¡[;˜ ã@F[{×%ÍÄ͹36J:Ðe¾BØ×2s­õªC·¾ öž/"" ç¸Þ“×ÞÛ˜j½®^^ýÚìc>¦ pYcÖ Šæ⦨ë¼àºž aK­g§?ë™yf~ŒXƒ¹D¤µkHH¹¼NÀ88ý½ê>3Ïtæ¹:\½Ÿ«Ë¨þnõ¡™Ã´)Â1ˆkw;×^V×úu/®+„#¨ÂV jóæÍi©©©·GEE]l2™âL&“ZÃ""Màv»]n·»¸²²ò‹;v¼qõÕWTr:”«CØÄÏ]WCÍÐõì¢Ö.ë aï0ÍÙzkH·´¯î熜†TW7³™Ÿ[º¯y1›7o9f̘L‹ÅÒ-Ð7$"" çt:ó·nÝ:ïꫯÎlüÜ}íäç–³¯.m_Ç›ÁÿiOÞÂî"áľZµÕÓuu?ûíë˜ouëØBÍ0Žùé§Ÿþ;!!aB߈ˆ4AQQÑúÄÄÄÇ9ÆÞ!\ýºö€®ÚÁL=ó î ÆÇ|_Úl74„k¯ã=«zºÎ.++ûŸ˜˜˜Ô½ ›Í¶ãœsΙAýaì=ªºöhêº.úŒVq³qsÿ eà×>&\{P—wkØ{Vô?þ8O!,"bœ˜˜˜ÔüqMÍC†ÞßÙµ¿×뺬q°5Kc5œ"ù UWkÓ¾º¤½[7mÚ4211q|pª.""•˜˜8~Ó¦M#©yÆJíïo_ì+êOT[ØœáÒ\il·4ø¾\¥¯Ü»5ìýCŒ æ ­à»Ýþ~DDÄyuU¦¼¼œÊÊJGUoºˆˆ¢¢¢ˆŽŽn–ý‰Hèèû£áÇQ«Õz-PÆéÑÓÞƒ¶ª§kwK×u±ú.‹I­õý y÷t¸´ˆë j7|¨ž®ë¿¢Ú#ª-@Äû￟ZW;NŠŠŠ(++k¶?"‡ÃAYYEEE8ÎfÛ¯ˆ¾?/""â¼÷ß?•Ó%ïk;€ï«âcÓÕÛû­âæâ`¼Q] ¾º#jÿધ#ÓÒÒn¯kG%%%ÍúT›Ãá ¤¤Ä°ý‹Hãéû£iÎ|7GâûÔºzG½Ú-Ý!ëpiªöBþæùúaZbbb~é«ðòòrCÿˆª9ÊËË®†ˆ4€¾?šîÌw³wk¸öaÇ@¾÷k¿{áÄWö5¯öe)k Ö2™Lq¾ «¬¬lhC&œê""þ…Óßl8Õ¥!Î|7׬UÍ{àÖY›R|“+"áp‰Ë†ŽhkÈñŸWת법áðßlµpª‹ˆøN³Á¬‹Ó餲²»ÝŽÃáÀí>=vÉd2Õj%** ‹ÅÒä}ùn®ëªˆÕ¿ëúÎ÷wé†Îo6¡â†üâoÝúZï¾â×.××ðwñÁétb³Ùêl]»Ýnìv;v»›ÍFTT111Ád_÷¯Í{^]!Z}­êº4$€CÖáÐ"öÅ__}­e_§6ùš/"">TTTPZZÚ m*++©¬¬¤}ûö´k×®)»¯ë;»ö¼@[·Þó oýúÒ’[‡´w+¸¾ã ""Øl¶‡°·ÒÒRl6[Sªàë;;S”Zìw»Ñ-bÇ}=×õßQ]§+ùú¯*h¾üòKÖ¯_Ï_|Aqq1ñññ\|ñÅL˜0_þÒçàl‘°TQQÑÔN‡¹Ùlnl˸¾ïxïÛúêzö^îï~ŵ÷iXKÙè TØý§ãt:yñÅÙ¸qcùßÿ=|ð|ð&Là·¿ýmP1ˆˆ„’ÓélRK¸¶ÒÒR¬V«ÑßaÙ][(ƒ¸±áìaè! ñꎊŠâ¶ÛnãÊ+¯¤C‡²uëVÞzë-Ö¯_Ûíæ¿þë¿BQia***p8ÄÄÄ`6vThäÈ‘DGG“““Ó¨}VVVòÚk¯±iÓ& éÔ©×_=S¦LñY‡#GŽpÛm·áp8صkW£ö)þ]uÕUÚnË–-A®ÉÏ‚ÑöUflllS‹iè÷½¿àml8‡,Ô[ò1â@„$„¿üòKOgeeqÇwÐ¥K"""èÚµ+wÜqYYYDEE±aÃöïߊjH óÌ3ÏpÅWpèСfÛçÂ… yùå—‰ŠŠâª«®¢¤¤„¥K—²|ùò³Öu»Ý<óÌ3-öTi¼êS”‚­²²2X—Ü »^Ñ`j)]Óþ4ëiÆ ÜvÛmôíÛ×ç:}ûöåÖ[oeåÊ•lذAÇ‹¥Ù9N6mÚDRR¯¿þ:V«•žžÎÎ;¹÷Þ{k¬ÿÎ;ïpøða)..6¨ÖmC([¶á/„¯ºê*ú÷ïÏܹs‰ ¸¸˜¹sçòÙgŸÕû~*++‰‰‰ j}½´ˆ®gŒlž¡ìân”Ï?ÿ€+¯¼²ÞõÆŒSc}i»233=ÝË“'OfÈ!že.—‹åË—3iÒ$FÅÔ©Sùøã=Ëív;Ï=÷×]wãÆcÙ²e5Ê4h«V­   €#Fpíµ×zŽ÷UUUQVVüü…{þùçר߉'X²d 3gÎô|ÑJÛa·Ûë]~þùçóå—_2{ölN:Å©S§xä‘Gøì³ÏèÞ½{“Ên€Pga­îÖÞ5Õ­…:Ô»^ÇŽ8uêTÈë$áÍûŸ¶+®¸‚ûî»Ï3½páB–.]ŠÃá --òòò-ˆââb¶mÛÆyçÇO?ýIJeËØ±c³gÏ&>>žW_}•~øE‹QUUÅ“O>I||<ãǧ´´”É“'3{ölxàÈýôSRSSILLdÖ¬Y<ñÄÜtÓM”””0iÒ$O‹ûÁä³Ï>ã?ÿùç÷155µÆïä¦M›øøãyýõ×1™Zõ¡¸°nƒµª/[Y—:••ÅìÙ³9|ø0pº¡±hÑ":wîܤ²ƒ v÷t‹ë®·q°¾Búm2`À¶nÝZïzÿüç?¸øâ‹CYiÁ:„Óédذau†ð¹çžKTTðóï’÷1Ük¯½–xn7sæLàt—÷<ÀÑ£G¹ÿþûy衇èÙ³'Ï?ÿ<¿ûÝï(**báÂ…ÜtÓMôîÝ·ÛÝ_œÒY­Ö¿£‘‘‘uþΆP‹Èˆ†j‰-bÃM˜0>ø€·Þz‹ú°uàÀÖ®]‹Édb„ ÔRZ‚ê㸆_]çdz_8¡ú´¤?þ˜}ûöqçw2uêT&MšÄm·ÝÆ¿þõ/Ž;Æk¯½Fqq1k×®eíÚµ5Ê4hýúõó–à ·ÁZ&“©ÞßÁü‘ŒŒ òòò<Ç„9BFFYYY$%%Õ[¶Ô/ÜZÄ-Â/ùK&L˜@ee%³gÏæõ×_çØ±c8 XµjTVVGrr²ÑU–0qúÿ^ï·÷èÑ€;vPUUÕ¨rwíÚÅîÝ»=Ó¯¾ú*pú¼e€¼¼<Ï2ï ,X,zõêÅĉk<ªMœ8‘Ñ£G7ªNÒ²TÿnÖeΜ9žÎÊÊ"++‹îÝ»säÈæÌ™Ó¤²E-âFûío‹ÛífÆ ¬\¹’•+WÖXn2™HHH ¨¨ˆY³f‘Mbb¢Aµ•pЧOž{î9ÒÒÒ˜:u*^x!—]vŸ~ú)wÞy'C‡åСCLŸ>Ýs¤>eee<óÌ3$&&òÖ[o1uêTV®\ÉØ±c4h;v$77—o¼‘_ýêWüûßÿæ›o¾aøðátêÔ‰[n¹å¬2÷ìÙC~~>¿ÿýïƒþHx²Z­õŽn>tè½{÷fþüùžÖoVVsæÌñ{^¼Õj j][£pâÆôi4K?ˆÅbá¿þë¿3f 6là‹/¾à§Ÿ~">>ž0aÂ’““ÉÈÈàðáà caÒ¤I|õÕWäææ²~ýzFEß¾}Y¸p!/¼ðü1ï¼ó=zôð´fýùóŸÿÌñãÇyòÉ'ùÅ/~Á¬Y³˜5kÏ<ó +V¬àå—_fÙ²e|òÉ'äääйsg¦NÊ”)SBûf¥^á6X+**ªÞ+kùÚoRRÿó?ÿPÙAÖ˜ÁXa=€+”¡å¯ìºî”ä½ÌìcÚ×ý…}ÝeÉræ9âÌëêçx·Û½ÓW…¾ÿþ{?Un¸S§NyFvëÖ¬¬,Î=÷Ü€¶ýÅ/~ôúˆHh4äû£9‚¸¡ß%%%A¿ºVTTTƒ/qi2™†§'àðzvyíòz¸Ï<¼_ã5¯yµ§½×§Ö²º„$Ì âºnaÕê‚NL­n÷éÓ‡¿üå/m§ i9BõýÑX ýþp:üôÓOA­Cbbbƒoú`pSǼ@–5ZK¬Õ"†ã%$$°hÑ".¼ðB¿ç܉ˆÁb±Ð¾}û •×¾}ûæ¼óR‹È‚º„êq‹þPB!!!_|ÑèjˆˆÔ©]»v¸\®&߉)&&¦±÷"w!9ÖÜÒ[Ä""D111Mj·oß>”7yh•Â}Ô´ˆˆ4³víÚaµZ±ÙlàŠŠŠ"&&¦9»£[ ±ˆˆœÅb±KLL •••Øív‡ç \&“‰ˆˆ¬V+QQQ à&P{‰ˆˆ›;…èj4"-Kkýþ°X,êj1#ö‚Ï-œê""þ…Óßl8ÕEüS{‰ŽŽ‹–hDDÑÑÑFWCD@ßÒX âZbcc ýcŠˆˆhð•hD$<èûCÃøßÂŒÅb!!!òòr*++›í˜ODDQQQúOV¤Ó÷‡4†‚¸ÑÑÑú¥‘FÑ÷‡4„º¦EDD ¤ 1‚XDDÄ@ b)ˆEDD Ô&GMçççãr¹ü¯(""ÍÆln›mÃ6Äݺu3º """€º¦EDD ¥ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@mòZÓ q¼ØIöæ"¾8Z…£Ö}",fèŸÉc×$Ð!ÖbLED¤ES‹ØìͧØ{äìpºà³ü*l*jþЉˆH«  öã‹‚*¿ëì˯bÍîR*ìîf¨‘ˆˆ´&êšöÃá ,\_ÙQÂ+;JTvBŒ™±}£™š‹ÕbjLõDD¤…S‹Ø@E6ëö”±â£†¸ˆˆ´jQ\´™á½-tŠ3c6ûoáº\nŽ»ÙùuÓFÆ5C ED$Ü(ˆƒÀl‚ó’,¤¦Dx³Ùl¢k‚ õJ‹ˆ´] b?b£LŒH±Ò)ÞLÜFé×´‚333ÉÉÉÀb±Ð©S'ÆŒÃý÷ßOddd“ë—žžÎĉ?~|“Ë‘šÄ~¤]x:„CÉdjzÂß|óÍÌœ9“ªª*8Àܹsq¹\üîw¿ B ED$T4XËŽq-ã#²Z­DGGϰaøå–[øôÓO®–ˆˆøÑ2RÆ@Ah¬¢°°^½z`·Ûyî¹ç¸ñÆ1b'Näý÷ß?k›7ß|“›o¾™1cÆðÐCqìØ±³Ö)--eÒ¤I¼üòËŒ7ŽÝ»w{–îlqÆå—_N\\k×®mrý, íÚµkr9""­‚¸KLL¤}ûö—\r 6là»ï¾£  €yóæa·Û=Û´k׎ýû÷óÉ'ŸpêÔ)–-[†Ífc̘1ÍôɈˆ„7#n%Þ~ûmÞ~ûmàt°¦¤¤••åiyΜ9“—^z‰¥K—2|øpF]cû?üádggsï½÷b·ÛIII¡OŸ>ÄÄÄÔXïî»ïæË/¿ä‘GaÅŠ<øàƒÌ;—[n¹…äädÆç i€öíÛsõÕWóì³Ïòý÷ß3`À^zé%¬Vkˆ?‘–!TgÉú+××r“×üêg³iïuÌ^ÛUOW?,gž#μ®~Žw»Ý;}#o|òS «6Éí—'ú_©Òå1E¤!L&Ópàà^Ï®3¯]^·×£z¯i¼æÕžv{½öæïÞ·A¿ñ¼º¦EDD ¤ 1‚XDDÄ@¬%!U}™MñM-b?\A?,6·»v"""aIA쇭*ô·_ªt„|""¦Äþ¸ÌX,VBÑhu»ÝXLn,ú1ˆˆ´Y:FìÇô‘qìγ³ëk8Yb÷¿AtŠ53¸g—ŸÔrED¤åP`p+ƒ{èJP""|ê1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@FW Ü/v’½¹ˆ/ŽVápÕ\f1CÿäH»&±c*(""-šZÄ~do>ÅÞ#g‡0€ÓŸåW±`SQóWLDDZ±_Tù]g_~kv—Raw7CDD¤5Q×´g`áúÊŽ^ÙQÒ ²bÌŒíÍÔÔX¬Scª'""-œZÄ*²¹X·§Œ5,ÀED¤õP‹8ˆâ¢Í ïm¡Sœ³Ù ×års¼ØÍί+˜62®j(""áFAfœ—d!5%‚¨ˆÀ»˜Íf]L¨WZD¤íRûebDŠ•Nñfhä6JǸ¦œ™™INNf³™äädúôéÃ}÷ÝGŸ>}‚QEÒÓÓ™8q"ãÇ?kYUU.—‹víÚe_""m‰Žû‘v¡•. ¡ a“©é…ßtÓMlÛ¶œœž|òIHOOgóæÍA¨aý^}õUžyæ™ïGD¤5R‹ØŽq-ã•ÈÈHÚ·o@bb"—^z);wfÑ¢E 6ŒØØXƒk(""¾´Œ”1P«†¹ãŽ;p»Ýäææ`·Ûyþùç¹þúë;v,óæÍÃf³yÖß¹s'éé餦¦2a–,Yâ³ÜÒÒR&MšÄË/¿ÌªU«xõÕWÉÉÉaÈ!,X°—ËÅŠ+˜8q"£G桇âØ±cž2ÒÓÓyûí·™6m£Fâ®»îâÀ!ü4DD“ZÄ­Xdd$½{÷&//€E‹qêÔ)V­Z…Ãáà©§žbñâÅ<ú裓‘‘ÁÃ?ÌÕW_ÍÉ“'©¬¬<«L§ÓÉc=FJJ ÷Ýweee=z”yóæyÖ[¶l;vì`Á‚DGGóüóÏ3cÆ þú׿bµZX¸p!¯¼ò ]»våõ×_çþûïç½÷ÞSë]DÚµˆ[¹:PTTDYYëׯgÖ¬Y$%%ѱcGf̘Á¶mÛ8yò$v»ÔÔTâââèÕ«ýúõ«Q–ÛífáÂ…Øl6žxâ‰:÷YUUÅêÕ«™3g½{÷&99™¹sçRVVæi<þøã\zé¥tìØ‘‡~˜ÄÄÄf9¦-"NÔ"‚ÿ;\Å—Gí¸\n"#L\ùËvtI›@œ>þ¬uÍf3K—.eÏž=¼÷Þ{ddd0hÐ ²³³=ë\~ùå|ûí·¬]»–ûï¿¿Þýº\.œN'fóÏ.‘‘‘õ¸ÅbÑ)P"Òæ¨k:Ün7ƒ.ˆäW="I»0ŠôÔs˜:ª=· ‹á‹£vÃêõæ›o’œœÌ!CèÒ¥ çœsÛ·o¯w›òÔSO±|ùr¶oßNaa¡gY×®]™?>+W®¬ÑÅ §[×Õºuë†ÉdbïÞ½žy.—‹лwo¾ÿþ{þö·¿Õ(cÿþýôêÕ«ÑïWD¤%R‡PT„ —«yîÈd·Û)//§´´”ýû÷“••Åš5kÈÌÌÄd2Azz:‹/f×®]8NNœ8á©\\\ÌÆ),,¤¢¢‚={öMBBBý :”éÓ§óÄOŸŸ@§NøôÓOùî»ï(//'&&†n¸gŸ}–ðã?’••ERRC‡ ]»vìß¿ŸO>ù„S§N±lÙ2l6cÆŒi–ÏKD$\¨kº•X·nëÖ­ÃjµÒ³gO.¹äÖ®][£zêÔ©DEE‘••ÅñãÇIJJâ®»î¢_¿~”””°uëV–,YBYY=zô`Á‚>»’ï¾ûn¾üòKyäV¬XÁu×]ÇG}T’L IDATÄ-·ÜÂÀùÓŸþDFF/¼ð³gÏÆáp0dÈ/^ì9.ܾ}{®¾újž}öY¾ÿþ{ ÀK/½äQ-"ÒV„ê,YåúZnòš_ýlö1í½ŽÙk»êéê‡åÌsÄ™×ÕÏñn·{g oäO~ò»Îk–qÛ°˜³®3]épóÖ¿lLI;Ço·_žh•Z…ú.™)"m—Édœœ€ÃëÙuæµËëáözTOã5×¼ÚÓn¯×Þüuc½›S-â ¸ø<+oýËvV7´ÙlbÀyjቈHÝÄA0ð‚HÃFF‹ˆH˦ÁZ"""R‹X ±jÕ*£« "Ô"ö£9Î>r»›ç' ? b?lU¡¿ýR¥Ãÿ:""Ò:)ˆýq™±X¬„¢Ñêv»±˜ÜXôci³tŒØé#ãØgg××p²$¸—«ìkfpÏ(.??:¨åŠˆHË¡ ÀàV÷ÐùÀ""|ê1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@FW Ü/v’½¹ˆ/ŽVápÕ\f1CÿäH»&±c*(""-šZÄ~do>ÅÞ#g‡0€ÓŸåW±`SQóWLDDZ±_Tù]g_~kv—Raw7CDD¤5Q×´g`áúÊŽ^ÙQÒ ²bÌŒíÍÔÔX¬Scª'""-œZÄ*²¹X·§Œ5,ÀED¤õP‹8ˆâ¢Í ïm¡Sœ³Ù ×års¼ØÍί+˜62®j(""áFAfœ—d!5%‚¨ˆÀ»˜Íf]L¨WZD¤íRûebDŠ•Nñfhä6JǸ¦œ™™INNf³™äädúôéÃ}÷ÝGŸ>}‚QE¿ÒÓÓ™8q"ãÇo–ý‰ˆ´:FìGÚ…Vº$„.„L¦¦~ÓM7±mÛ6rrrxòÉ'IHH ==Í›7¡†""*jûÑ1®eü¯IûöíHLLäÒK/¥sçÎ,Z´ˆaÆkp EDÄ—–‘2 BcÕ0wÜqn·›ÜÜ\ìv;Ï?ÿ<×_=cÇŽeÞ¼yØl6æÌ™ÃÓO?]cû+V‘‘áw[i<q+IïÞ½ÉËË`Ñ¢E;vŒU«VñÆoPPPÀâÅ‹7n¹¹¹¸\?_BlÛ¶m\}õÕ~·‘ÆS·r:t ¨¨ˆ²²2Ö¯_ϬY³HJJ¢cǎ̘1ƒmÛ¶0|øpœN'ûöí °°Ã‡“––æw[i<#nåNž×¿æšk˜7o?ü0|ð·Ýv@@ÛŠˆHã¨kº{óÍ7INNfÈ!téÒ…sÎ9‡íÛ·×¹þ Aƒ(//gÏž=|õÕW :  mED¤qÔ"n%ìv;ååå8NòòòøÇ?þÁ–-[xá…0™LDDDžžÎâÅ‹éÔ©ƒ âûï¿ç‡~ _¿~À鋌;–§Ÿ~šQ£F ж àv»ƒr^´ˆH[¡ n%Ö­[Ǻuë°Z­ôìÙ“K.¹„µk×ÖèJž:u*QQQdeeqüøq’’’¸ë®»IBBééélÞ¼95 O¯¾ú*Ï<óŒÑÕiµˆýè×2þW‰ŒŒ¤}ûö$&&r饗ҹsg-ZİaÈ5¸†""âKËH¡±j˜;ÛMnn..—‹+V0qâDFÍC=ıcÇjlóæ›oróÍ73f̘ËÇÇîÝ»=ëJqq1Üpà ¼ÿþûdgg3f̘ ~¢""Á§ ’=yV~TÉŠ+xíà ¾,p]%:tè@QQUUU¬^½š9sæÐ»wo’““™;w.eeeäææât:Y¾|9¿ÿýïéÛ·/;vdܸqDGG´Ÿ„„233éׯIIIÜyçÜsÏ=tïÞîÝ»sà 7xZ´eee¬_¿žY³f‘””DÇŽ™1cÛ¶m;«Ìþýû“””Ä”)Søá‡ÈÏÏ÷Y‡“'Ob·ÛIMM%..Ž^½zѯ_¿¦}€""!¦cÄAòåQ'·‰$*ÂÄgG|òÍéVfÿdcoqòäIÒÒÒ(((ÀáppÑEy–EGGsñÅóí·ßRPP€ÍfkRp™Í?ÿ_׳gOl6III\pÁž®ç¼¼<\.÷Üsg°šÃá ¬¬¬Î2»víJtttîko\pƒ bÒ¤I¤¥¥1~üxÜè÷#"ÒÄAâr¹=ç÷êhÁé‚ÿûÆnhWUUqèÐ!¦L™â9ÕÇétÖ·ÈÈH"##)--NG¶XBSgïÑáÕƒÇV¯^M|||£Ê¨Íl6³téRöìÙÃ{ï½GFFƒ ";;»ñ• 1uM‡@ûv&.;?£oñæ›o’œœÌ!CèÖ­&“‰½{÷z–»\.8àéªv¹\|¸Æ<—ËÇ-©Ô¥KÎ9ç¶oßÞè2àt+º¶òÔSO±|ùr¶oßNaaa“ö!"J âVÂn·S^^Nii)û÷ï'++‹5kÖ™™‰Éd"&&†n¸gŸ}–ðã?’••ERRC‡%>>žÑ£G³`Áòòò°Ùläææzº/¹ä6lØÀwß}GAAóæÍó òjŒˆˆÒÓÓY¼x1»víÂétrâÄ 8p:uâÓO?å»ï¾£¼¼œââb6nÜHaa!ìÙ³‡èèh]O‘PS×t+±nÝ:Ö­[‡Õj¥gÏž\rÉ%¬]»¶F·oFF/¼ð³gÏÆáp0dÈ/^ìéîýÃþ@vv6÷Þ{/v»””úôéCLL >ø sçÎå–[n!99™qãÆÕ9h*PS§N%**Ь¬,Ž?NRRwÝuWÀÇ©¯»î:>úè#n¹åÈìٳٺu+K–,¡¬¬Œ=z°`Á"##›TO‘P ÕY²þÊõµÜä5¿úÙìcÚ{³×vÕÓÕË™çˆ3¯«ŸãÝn÷Î@ßÈŸüä%`åG•žÁZ•7kvUq׈¨€¶¿ýòÀNiÍL&Ópàà^Ï®3¯]^·×£z¯i¼æÕžv{½öæï¨bÐ:ªE$'[X³« —ëôÏÈl6q±Á#¦ED$ü)ˆƒä²ó#¸ì|}œ""Ò0¬%""b ±ˆˆˆÄ~¸šád`·Ûè3ŽEDÄ( b?lU¡¿ýRåÙפ‘6BAìËŒÅb%V·ÛÅ䯢ƒˆH›¥a¾~LÇî<;»¾†“%¿’”/bÍ îÅåçv‡#i}ÄÜÃÊàV£«!""­úDEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1P„ÑwÇ‹do.â‹£U8\5—YÌÐ?9’Ç®I C¬Å˜ ŠˆH‹¦±Ù›O±÷ÈÙ! àtÁgùU,ØTÔü‘VAAìÇU~×Ù—_ŚݥTØÝÍP#iMÔ5í‡ÃX¸¾²£„Wv”4¨ì„3cûF355«ÅԘꉈH §±Šl.Öí)cÅG pi=Ô"¢¸h3Ã{[ègÆlößÂu¹Ü/v³óë ¦Œk†ŠˆH¸QÙç%YHM‰ *"ð.f³ÙD×ê•i»Ä~ÄF™‘b¥S¼™¹Ò1®igff’““€Ùl&99™>}úpß}÷ѧOŸ`T±ÁÒÓÓ™8q"ãÇIùUUU >œõë×sÞyç…d""ÍALjýH»ÐJ—„Ð…0€ÉÔôÂoºé&¶mÛFNNO>ù$ ¤§§³yóæ ÔPDDBE-b?:ƵŒÿU"##iß¾=‰‰‰\zé¥tîÜ™E‹1lØ0bcc ®¡ˆˆøÒ2RÆ@Ah¬æŽ;îÀív“›› €ËåbÅŠLœ8‘Ñ£GóÐCqìØ1üqæÌ™ãÙ¶ªªŠAƒñÜsÏyæÙl6†JAAééé,_¾œiÓ¦1jÔ(&OžÌ¾}ûê¬K}û¶Ûí<÷ÜsÜxãŒ1‚‰'òþûïרÞétòâ‹/ò›ßü†k¯½–¬¬¬ËwîÜIzz:©©©L˜0%K–4íÃi& âV,22’Þ½{“——À²eËøç?ÿÉ‚ X½z5‘‘‘̘1»ÝÎÈ‘#ùä“Op»OŸ7ýÿ÷lÛ¶ÍSÞ§Ÿ~ÊyçGrr2EEE,]º”™3gòî»ïÒ¿}ôQìv»ÏºÔ·o«ÕJ·nÝÈÊÊâí·ßæúë¯gîܹžz,Y²„;w’ÍK/½Dyy¹gYqq1Üpà ¼ÿþûdgg3f̘`œ""!¡ ²ƒÇ<æ0º:t ¨¨ˆªª*V¯^Íœ9sèÝ»7ÉÉÉÌ;—²²2rss>|8%%%:t8ÝÂ|à(**â›o¾`×®]¤¦¦@ff&ýû÷'))‰)S¦ðÃ?ŸŸVüíàæ›o¦W¯^$''s÷ÝwÓ¥KöïßïÙþí·ß&33“””ºuëFff¦§ü“'Ob·ÛIMM%..Ž^½zѯ_¿~®""Á¢ ²JÇéG¸8yò$=zô   ‡ÃÁE]äYÍÅ_Ì·ß~Kûöí¹ì²Ëøä“Oøè£¸òÊ+>|8~ø!p:ˆGŒáÙÞlþù×§k×®DGGc³ÙΪƒ¿}ñÖ[oñè£rÿý÷sôèQ***8zô(N§³ÆöÞû¾à‚ 4h“&M"33“Ý»w7å#iV âV¬ªªŠC‡Ñ«W/ªªªp¹\8ÎëDFF À¨Q£Ø½{7GŽÁb±Ð½{w®¸â rss9yò$'NœàW¿úUû«kô·¿}———3eÊNž<ÉôéÓÉÊÊ"%%ų¾ÝnÇårárù¸ó§CyéÒ¥üéOÂjµ’‘‘Á¬Y³þœDDŒ¤ ’ŸÊÜœ,ùùºÔ'KÜ•{ˆ7ß|“ääd† B·nÝ0™Lìݻ׳ÜårqàÀz÷î ÀÈ‘#Ù»w/¹¹¹¤¥¥ššÊ¿ÿýorrr2d hïoߤ¨¨ˆ™3gÒ³gObbbøê«¯8qâDÛû2pà@žzê)–/_ÎöíÛ),,lp]EDš›‚8H.ز¿ŠÅ.N»Ø²¿ »ï\HØívÊËË)--eÿþýdee±fÍ2331™LÄÄÄpà 7ðì³ÏràÀ~üñG²²²HJJbèСtéÒ…®]»òòË/{‚¸}ûöüêW¿âÏþ³çøp (((ÀívûÝwRR¥¥¥¼óÎ;œ:uŠ-[¶§»»bbb¸îºëøãÿÈþýûùþûïyþùç=û*..fãÆRQQÁž={ˆŽŽ&!!!X¯ˆHÈ(ˆƒ¤C¬‰1}­œ,vq┋+úZéÛ|ç>­[·Ž´´4ÆŽËüùóq¹\¬]»–””Ï: >œÙ³gsë­·RRRÂâÅ‹kt)9’²²2.½ôRϼѣG0lذ€ë3yòd6mÚDFF†ß}ŸþùÌœ9“—^z‰o¼‘mÛ¶yöYí‘GaРA<üðÃ̘1ƒŽ;z‚º¤¤„­[·rÇwpå•W²~ýz,Xàér g¡J åúZnòš_ýlö1í½ŽÙk»êéê‡åÌsÄ™×ÕÏñn·{g oäO~ tUàt—´ øECøöË´¾ˆHkd2™†§'àðzvyíòz¸½ÕÓxMã5¯ö´Ûëµ7ǃ~ÌQWÖ ’VøœwZ»f®‰ˆˆ´$ â QàŠˆHc豈ˆˆÄ"""Rûáj†S«¯ï,""m‚Ø[UèOA §KbŠˆHóRûã2c±X E£Õívc1¹±èÇ "ÒfiÔ´ÓGƱ;Ïή¯ád‰ï[ü5V§X3ƒ{FqùùÑA-WDDZq÷°2¸‡ÕèjˆˆH+¤>Q)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD atÂÝñb'Ù›‹øâhWÍe3ôOŽä±kèk1¦‚""Ò¢©EìGöæSì=rv8]ðY~ 65ÅDD¤UPûñEA•ßuöåW±fw)vw3ÔHDDZuMûáp®¯ì(á•% *;!ÆÌؾÑLMÅj15¦z""Ò©El "›‹u{ÊXñQÃ\DDZµˆƒ(.ÚÌðÞ:Å™1›ý·p].7Ç‹Ýìüº‚i#ãš¡†""nÄA`6ÁyIRS"ˆŠ¼‹Ùl6Ñ5Á„z¥EDÚ.±±Q&F¤Xéo&€Fn£tŒ NÁ[¶laãÆ:tˆ’’:wḭ̂aØ1c111AÙG°¥§§3qâDÆotUDD ¡ ö#íÂÓ!J&SÓ‚Øív3gξúê+¦OŸNJJ ‘‘‘=z”½{÷Ò®]» ÕTDD‚MAìGǸðÏö׿þ•C‡ñÖ[oé™ßµkWl`ÍDDıMl¬6‹åË—3kÖ¬!\›ÝngñâÅ|øá‡Ò¹sgî¹ç®½öZÏ:éééŒ5Šýë_ü¿ÿ÷ÿèÞ½;¿ÿýïùüóÏY¿~=ß}÷ƒ âñÇ'11ÑSîÒ¥Kùßÿý_*++¹âŠ+xøá‡=]áþ–{+--eêÔ©Œ;–o¾ù†èèhžxâ Ïò•+Wòù矓¬NDÄpáßÜ“zSXXHß¾}ë]ÏjµÒ­[7²²²xûí·¹þúë™;w.yyyžuŠŠŠøË_þÂï~÷;Þ}÷]ºwïNzz:ùùù,\¸×_£Gòâ‹/z¶Y´hÇŽcÕªU¼ñưxñ‗Ws:<öØc¤¤¤pß}÷1vìXvìØÛýóyÜÛ·o窫®jÊÇ%"vÄ-\aa!ÑÑÑžyË–-cРAžÇÿ÷póÍ7Ó«W/’““¹ûî»éÒ¥ û÷ï÷l—@ff&ýúõ#))‰;ï¼€{îݻӽ{wn¸áöíÛ@YYëׯgÖ¬Y$%%ѱcGf̘Á¶mÛZ^Íív³páBl6›§üðCú÷ïOnn.C‡åœsÎiêG&"VÄ-\LL ]»våÃ?dÀ€DFFzÍjµP^^Δ)S3f Ó§O§sçÎÜ{ï½8ÎíÏ{„wll,«W¯&>>þ¬uý-¯vùå—óí·ß²víZî¿ÿ~Ïü«®ºŠ—_~™ßþö·lß¾±cÇ6¨®""-º¦[»ï¾›·Þz‹o¿ý¶Îu2ŽÝyvv} 'KìA-»S¬™Á=£¸üühÿ+‹ˆH«¤ ÀàV÷°] i…Ô'*""b ±ˆˆˆÄ"""R‹ˆˆHA,""b ±ˆˆˆÄ"""R‹ˆˆHA,""b ±ˆˆˆÄ"""R‹ˆˆHA,""b ±ˆˆˆÄ"""R‹ˆˆHA,""b ±ˆˆˆÄ"""R‹ˆˆHA,""b ±ˆˆˆ"Œ®@¸;^ì${s_­Â᪹Ìb†þɑ˯bÁ¦¢æ¯˜ˆˆ´ b?¾(¨ò»Î¾ü*Öì.¥Âîn†‰ˆHk¢®i?ÎÀÂõ•%¼²£¤Ae'ĘÛ7š©©±X-¦ÆTODDZ8µˆ Tds±nO+>jX€‹ˆHë¡qÅE›ÞÛB§83f³ÿ®Ëåæx±›_W0md\3ÔPDD‚8Ì&8/ÉBjJQw1›Í&º&˜P¯´ˆHÛ¥ ö#6ÊĈ+â?Ùi IDATÍÐÈm”ŽqM+833“¯¿þš·Þz “éì²þóŸÿpûí·³lÙ2.»ì²&í à–[ná׿þ5S¦LirY""mŽû‘v¡•. ¡ aÀgx6Ô¡C‡øðÃ}.[±bE“Ë÷6jÔ(.ºè¢ –)"ÒV)ˆýè×2>¢ØØX^{íµ³æ9r„O?ý”sÎ9'hûºÿþû:thÐÊiËZFÊ(Õfqã7zB×ÛÊ•+¹õÖ[i×®]ùãÆc÷îÝžéƒ2bÄÏôÎ;IOO'55• &°dÉϲôôt6lØP£¼7ß|“›o¾™1cÆðÐCqìØ±`¾=‘VKLj[‰víÚ1yòdV¬Xá9\XXȶmÛx÷ÝwY»vmÀe“‘‘ÁÃ?ÌÕW_ÍÉ“'©¬¬¬sýU«V±iÓ&ž~úiÎ=÷\öîÝKbbb“ß“ˆH[  nE&MšÄªU«ø÷¿ÿÍE]ÄêÕ«¹á†ˆ‹kØ©Q'OžÄn·“ššJ\\\½Û;N–/_ÎâÅ‹éÛ·/pºµ-""Q×tíÉs°ò£JV|XÁÊ*ùô[G³í»}ûöÜ|óͼöÚkó÷¿ÿÉ“'7¸œ .¸€Aƒ1iÒ$233kta×VPP€Íf£_¿~M©ºˆH›¥ ²/:¹uH$w§µãÖ!‘|QàlÖýßvÛmìܹ“ 0fÌ:tèÐà2Ìf3K—.åOúV«•ŒŒ fÍšåsÝÒÒR\.wÅ¿ÄAær¹=õˆŠ0ár5ï ’’’øÍo~CNNéééu®ÍáÇkÌ«¦ä©§žbùòål߾³ÊINNÆårqðàÁ༑6FA$?¹8xÌQçtsš6mË–-£{÷îu®sÉ%—°aþûî; ˜7ov»8=XkãÆRQQÁž={ˆŽŽ&!!á¬râãã=z4 , //›ÍFnn.6›-dïOD¤5Ñ`­ ((rñÑÿ³Ó1Öäsº¹ÅÇÇû½‚Öƒ>Èܹs¹å–[HNNfܸqäççPRRÂÖ­[Y²d eeeôèу 鳬?üádggsï½÷b·ÛIII¡OŸ>ÄÄÄý½‰ˆ´6¡J åúZnòš_ýlö1í½ŽÙk»êéê‡åÌsÄ™×ÕÏñn·{g oäO~ò»Î'Š]\Ò=‚|^ůºGÔ˜¾}X;¿eÜ~¹N÷1™LÃS€px=»Î¼vy=Ü^êi¼¦ñšW{ÚíõÚ›¿ã‰A?Þ¨®é èÕÉ‚­ 6}þÿÛ»÷è(ëÿ㟹$!\W¡‚x¡J½þ@pаuUh4{Ö<[ŽËAQ"‹ÚßÁžÒIZmªGp7¥ØVÛñ¬( «¬èÑZpë® ’Ts!·¹<óû&N&3™Édf¾3ÉûuΜçö}žù΃Î'ßïó}žqëÚ 3:/Ë0]=@ £k:²œ6ÝrUçnÛàeB¡E €A1Ä$ã6`Ÿ/¹÷RAA‹+ñ· µ›¹ÝâH,»Ž %¢Ñêóùä°ùäàŸú-FMGðýoçéÝcníÿTªmrÇõØ#ríš|a–®½ ;®Ç¤‚8 “ÇfhòXîÄ}¢D`A €A1ÄD`A €A1ÄD`A €A1ÄD`A €A1ÄD`A €A1ÄD`ÓtRÝ©F¯*ªëõÑq—üÜ¥²íõɯ O ˆ#øè„+b™>wé×ïžQ›Û—„úº¦#ðx£ ×ÞjÒ o5õèØ9v͹,[‹¦å*Ãa‹¥z€4G‹Ø úK/hÖ‹o÷,À}-â8Ê˶kêÅȳËnܵ,ŸN5ú´ïÓ6ýÓ·ó’PC@ª!ˆãÀn“¾QèдñNe9£ïb¶ÛmU`½ÒÐÄäfÙtýø È·+ŠFnL†çõîÀ>ú¨vìØ!Ir:3fŒ¾ûÝ視¸XvûÙ«ÅÅźùæ›u÷ÝwG<žËå’eY0`@¯êˆŒ Ž`ú7φp"Ùl½Oø héÒ¥jmmÕ;ï¼£òòrµ¶¶jÑ¢E’¤3fèÒK/êX6lÐñãǵfÍš^× Ð=‚8‚áyé1ž-33Sƒ Ò AƒtÛm·©©©I7nìâûî»Ïp ¡¤GʇƪW_}µêëëÕÒÒ"I*))ÑÖ­[;•Ù¼y³n¿ývÍš5K<ð€¾øâ UUUiÆ Ú±c‡¦L™¢²²2IÒ7Þ¨wß}·cß?þX×_}ÇrII‰ª««µlÙ2͘1Cû÷ï—$¹Ýn=ýôÓºõÖ[5gέY³¦£N’´oß>•””hÚ´iúÎw¾£uëÖ%ìœ@*¢EÜGÕÕÕièСÊÉÉ ¹½ªªJÛ·o×øC 2DÔàÁƒURR¢æææ˜º¦yä­ZµJK—.Õ˜1c$Ik×®UCCƒªªªäñx´zõjUVVjåÊ•jllTii©–/_®¢¢"ÕÖÖª½½½×ŸÒ AÜÇx½^ýïÿþ¯žyæÝu×]aËlܸQ•••ºì²Ë$mñöÖ¼yó4þüŽåææfmÙ²E¯½öš %IK–,ÑÊ•+µråJÕÖÖÊívkÚ´iÊËËS^·pèâ8hlµ´óG­H—mלoe(/;ñýÛ›7oÖæÍ›e³Ù4jÔ(ÝyçZ°`AȲ'NœPKK‹&L˜×:\yå•–;&˲´xñâŽiGÍÍÍ’¤qãÆiÒ¤IºãŽ;4}útÍ›7O“'OŽk ÕÄqðY¥o ¶kʤÌNëßùÔ£Ï꼺òüÄŸæ¹sçêÁTff¦233»-{æÌI’eYr8÷«Q¹¹¹’¤M›6)??¿Ëv»Ý®õë×ëÀÚ¶m›JKK5iÒ$UTT$¬Nj¬–OÊÊ>­ñêH­·cý€Œ³Û’Á?b:RKÒèÑ£eY–>þøã°e<O§åììl=z´Ó:Ë ñ“TÎ;ï< 8P{öìé¶ÜĉµzõjmܸQ{öìQMMM„O}AG wèÂaÕ6úäí>£ŒÊÏÏ×Ì™3UVV¦cÇŽ©¥¥E{÷îíÍŸ6Ÿü3@¿Å¨é¾ÿí<½{Ì­ýŸJµMÝ?Iª§FäÚ5ùÂ,]{Av\ Hq&ÍÐä±Ü ˆ?úD0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À §é ¤ºS^UT×ë£ã.y¬ÎÛvéòÑ™ú—› 4,×a¦‚€´F‹8‚ŠêüK×–$¯%}ø¹KeÛë“_1@Ÿ@GðÑ WÄ2|îÒ¯ß=£6·/ 5ô%tMGàñF®/¼Õ¤Þjêѱ rìšsY¶MËU†ÃKõiޱAõ-–^>ЬßîY€úZÄq”—m×Ô‹‘g—ݹ…kY>jôiß§mú§oç%¡†€TCÇÝ&}£Ð¡iãÊrFßÅl·Û4ªÀ&z¥ ÿ"ˆ#ÈͲéúñ‘oWܘ ÏëÝ}ôQíØ±C’ät:5fÌ}÷»ßUqq±ìöÞ]}¸ãŽ;tÛm·é®»îêÕq$Éårɲ, 0 ×Ç€¾‚ Ž`ú7φp"Ùl½Oø héÒ¥jmmÕ;ï¼£òòrµ¶¶jÑ¢E½:îŒ3ôÍo~³×õ“¤ 6èøñãZ³fM\Ž}AÁð¼ôÏ–™™©AƒiРAºí¶ÛÔÔÔ¤7ö:ˆÿùŸÿ9N5„’)cP«F\}õÕª¯¯WKK‹$©¤¤DÕÕÕZ¶l™f̘¡ýû÷K’,ËÒ‹/¾¨ùóçkæÌ™zàôÅ_t§¤¤D[·níXv»Ýzúé§uë­·jΜ9Z³fMÇ{ømÞ¼Y·ß~»fÍšÕq¼ªª*mذA;vìД)STVV&IÚ·oŸJJJ4mÚ4}ç;ßѺuë}j ¥Ð"î£êêê4tèPåäät¬{ä‘G´jÕ*-]ºTcÆŒ‘$=ÿüózë­·TVV¦ììl=ýôÓZ²d‰~÷»ß)##£Ëq×®]«††UUUÉãñhõêÕª¬¬ÔÊ•+%IUUUÚ¾}»~øÃjÈ!:xð ¬’’577wêšnllTii©–/_®¢¢"ÕÖÖª½½= gR-â>ÆëõêÏþ³žyæ™.¬æÍ›§ùóçküøñ0`€\.—6mÚ¤GyD_|±F­Ç\ÍÍÍÚ»wo—c777kË–-z衇TXX¨áÇkÉ’%zóÍ7;Þ{ãÆZµj•.»ì2 >\7Þx£²³³CÖµ¶¶Vn·[Ó¦MS^^ž.ºè"M˜0!îçR-â>bóæÍÚ¼y³l6›F¥;ï¼S ,èTæÊ+¯ì´|âÄ y<]zé¥ë²³³uÅWè³Ï>ëòÇŽ“eYZ¼xqÇ3Ç£æææŽãµ´´D¦ãÆÓ¤I“tÇwhúôéš7ož&OžÜ“ i î#æÎ«|P™™™ÊÌÌŒjÿíD^¯·ÓmN᎑››+IÚ´i“òóó»l?s挤³×ŽÈ¿Fe·Ûµ~ýz8p@Û¶mSii©&Mš¤ŠŠŠ¨ê}]Ó}„Ät´!,I矾l6›<رβ,>|X_|q—òçwž¨={ö„<ÞèÑ£eY–>þøã°ïéñxº¬›8q¢V¯^­7jÏž=ª©©‰ú3@º#ˆû±œœÍ;WO<ñ„>¬/¿üRååå*,,Ôu×]×¥¼ÓéTII‰*++µÿ~y½^>}Z‡–$åççkæÌ™*++Ó±cÇÔÒÒ¢½{÷vŒª1b„Þÿ}:SÿrS†å:ÌTÖhGPQÝ ƒé’䵤?w©l{}ò+èâ>:áŠXæƒÏ]úõ»gÔæö%¡F€¾„®é<ÞèÂõ…·šôÂ[M=:vAŽ]s.ËÖ¢i¹ÊpØb© ÍÑ"6¨¾ÅÒËšõâÛ= p@ßA‹8Žò²íšz±C#òì²Û#·p-˧S>íû´Mÿôí¼$Ôjâ8°Û¤o:4m¼SYÎ軘ív›FØD¯4ô_q¹Y6]?>C#ò튢‘“áy±xéÒ¥ÊÏÏך5kºl[µj•FŽ©ûï¿_ÅÅźùæ›u÷Ýw‡<ŽËåÒÔ©SµeË}ã߈¹>€žáqÓ¿™¡ó Â’d³Å~ð¢¢"½õÖ[r¹:îv¹\zûí·5gÎIÒŒ3t饗öªž€ø#ˆ#ž—Ú§è†nP{{»Þ{ï½Nëßyçh„ ’¤ûî»O×]w‰*ºA×t½h¬&E^^ž¦L™¢7ÞxC×_}ÇúÝ»wköìÙË%%%š?¾æÍ›'Iòz½zöÙgµ}ûvy½^M›6­ÓqÝn·Ö¯_¯;wª½½]7Üpƒ–/_®œœI’eYú·û7½öÚkúòË/uÕUWéá‡Ö¨Q£$IûöíÓ³Ï>«#GŽhèСš={¶–.]šèÓi'µ›{ˆJQQ‘öîÝ+Ë:ûø/¯×«½{÷vtK‡²nÝ:íÛ·Ozî¹çÔÚÚÚiûÚµkõÅ_¨ªªJ¿úÕ¯tâÄ UVVvlþùçõ‡?üAeeeÚ´i“233µdɹÝn566ª´´TsçÎÕüǨ¢¢B³fÍJ̇€4G÷3fÌЙ3gôá‡J’8 ÜÜ\]vÙe!Ë»\.ýö·¿Õ£>ªñãÇëüóÏ×£>Ú±½¹¹Y[¶lÑC=¤ÂÂB >\K–,Ñ›o¾Ù±ÿ¦M›ôÈ#èâ‹/ÖèÑ£õøã«¹¹Y{÷îUmm­Ün·¦M›¦¼¼<]tÑE]ä€Îèšî ¤¿ù›¿Ño¼¡k®¹FøÃTTT¶üñãÇåõz; Þ²Û¿þ›ìرc²,K‹/îHæñxÔÜÜ,I:qâ„<O§ý³³³uÅWè³Ï>ÓÌ™35iÒ$ÝqÇš>}ºæÍ›§É“'Çûc@Ÿ@ÇÑcýé¸W–uö±˜v»MWŒvèÿ\øÓ<{öl­_¿^<ð€Þ|óÍNÝÈÁÜn·,Ë’eYØ/77W’´iÓ&åççwÙîr¹dY–¼^o§ý333•™™)»Ý®õë×ëÀÚ¶m›JKK5iÒ$UTTÄá“@ßB×t¼zÐ¥3m>ýé¸Wÿ0%S÷L {¦Ð?LÉÔG'¼I©ÃŒ3ôå—_êå—_VNNŽÆ¶ìùçŸ/›Í¦ƒ†Ü~ÞyçiàÀÚ³gOÔû[–¥Ã‡ëâ‹/îX7qâD­^½Z7nÔž={TSSã§€¾‹ Žƒ¿ž±ôêA—,Ë×éÉZYN[Gë8Ñrrr4uêT­]»¶ÛAZþ²·Ür‹~ò“ŸèСCª««ÓÓO?ݱÝétª¤¤D•••Ú¿¿¼^¯NŸ>­Ã‡wì?wî\=ñÄ:|ø°¾üòK•——«°°P×]wõꫯª¦¦Fmmm:pà€²³³UPPÐs鈮é8I…ÛœæÌ™£7Þx#bKÒÃ?¬Ÿþô§Z¾|¹òòòtÛm·uÜz$I‹-RVV–ÊËËuêÔ)jáÂ…ƒ®JKKõóŸÿ\+V¬ÇãÑ”)STYY)›Í¦¦¦&íÞ½[ëÖ­Sss³ÆŽ«²²2eff&ì³@ºJT|D:n¨í¶€õþ©=Är`{À~þeÿËqnê<7ïŸæû|¾}Ñ~_½÷UÄ2¯tiæ„ ýþÿ¹ôS2;ZÅíŸ~½ß¥…×gE<Æ×޶JÐgÙl¶©’$y%y¦Ö¹y+àå xù—°¬€uÁ˾€ù@‘º1ãÞÍI‹8æ^s¶¥wÅh‡~½ßÕe°áÄqô.p&e„4 ï`°ÄDGŒ»|¾äÜâH=q-®Äß—ÔîIø[RA‰e—Ñ¡D4Z}>Ÿ6Ÿü3@¿Åß¾ÿí<½{Ì­ýŸJµMî¸{D®]“/ÌÒµdÇõ¸€ôAGaòØ M›aº€>ˆ>Q "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb rš®@ºðz½r»Úåóùz´_k[«rå)#33A5¤3ZÄQŠ%„%ÉëñèäÉãr»Ý ¨ ÝÄQŠ%„ý¼^¯N~ñ¹\®ö8ÖÐÄIâõzuêä ÂÐ AœD„1 ƒµbPSsJ–eu[Æn·køð‘’­ózûöíÓ³Ï>«#GŽhèСš={¶–.]*·Û­ÊÊJýçþ§jjj4räH-^¼X÷w×±oII‰f̘¡ÿú¯ÿÒÿüÏÿh̘1Zµj•þøÇ?jË–-:yò¤&Mš¤Ç{LƒîØçÖ[oÕ®]»ôç?ÿY\pV®\© &„¬ŸÛíÖúõëµsçNµ··ë†nÐòåË•““Ó¥lUU•6lØ IÚµk—þþïÿ^ƒÖo¼¡M›6Éáp¨¥¥E ,Ð’%KT__ߥüÊ•+ãz~ ‘âÄ;H{£±±Q¥¥¥Z¾|¹ŠŠŠT[[«öö³÷)gddèüóÏWyy¹  êêj=þøãúÖ·¾¥±cÇJ’êëëõ‹_üBUUU9r¤***TRR¢ââb=ùä“’¤ÒÒR=óÌ3zì±Ç:Þ÷É'ŸÔ /¼ Q£Fé—¿ü¥î»ï>mÛ¶M¹¹¹]ê¸víZ544¨ªªJG«W¯VeeeÈÀ,))Qsss§®f·Û­]»v饗^Ò÷¾÷==ÿüóºä’K4wî\IêRÒ AƒTj×ÖÖÊívkÚ´iÊËËS^^^§í·ß~{Çü=÷Ü£­[·êСCA\PP {î¹§£5{×]wiÇŽZ¼x± %IsçÎÕ¿ÿû¿w:îc=¦«¯¾Z’´|ùr½õÖ[ª®®Öüùó;•knnÖ–-[ôÚk¯uoÉ’%Z¹reÔ-׌Œ =úè£Z¶l™.¸à½öÚkÚ¼ys´§RAƒTj7N“&MÒwÜ¡éÓ§kÞ¼yš}Z‡]+¥XIDATî8ÞÃ?¬ªªªŽå#Fèý÷ß×É“'ÕÚÚ*˲´zõj-\¸P%%%ºôÒKõÔSO…-é„q R©EÜÔԤݻwkݺujnnÖØ±cUVV¦ÌÌL]pÁZ¶l™ž{î9­_¿^S§NÕÌ™3{ýžƒ RQQ‘žxâ ÕÕÕéÊ+¯ÔsÏ=§ŒŒ IÒ?þã?êÇ?þ±Ž9¢òòr-Z´HYYY*//שS§TXX¨… j„ ²,K'NœètÍ÷–[nÑÛo¿­ââbMœ8Q×\sZ[[uçwJ’V®\©ââbÝtÓMºöÚk»”ÿÙÏ~ÖëÏÉùâ_bŽj»-`½j±XưŸÙÿrœ›:ÏÍû§ù>Ÿo_>‹$©µåëÁEñ¼Fœ30¦ã˜ÔÛGX@86›mª¤I^Iž€©unÞ xù^þe,+`]ð²/`>P¨u=ÙÞc´ˆcJ-b@z#ˆcJ£¦é ŽA ˆ‚8ý½E8ÂÐ;q Ò=H©ƒûˆ0ˆ ŽR4yL…cÒ A¥ŒÌ¬¸§ÍfSFfVÜŽHO\#Ž’Ãá#»ëÙдˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À ‚ƒb "ˆ0ˆ À §é ˜ðùçŸË²,ÓÕ°ÛûgÛ°_ñùçŸoº H¢k£b "ˆ0ˆ À ‚ƒúå¨éHÜn·ÚÚÚär¹äóùLWG’ÔÚÚª!C†˜®F›Í&‡Ã!§ÓÙoo9Bñx<òx‡kÃb "ˆ0ˆûˆÀ°íÛ·«¦¦F’4|øpÝtÓM†k„d"ˆ¬¾¾^<òˆ233õÔSO­ËöíÛåõzõ·û·0`€Ñºý•eYúàƒtäÈ‘cSjjjTUU%IÊÉÉÑE]¤«®º*iO°óxùD.—Ëtuäõzõ׿þU;wîT[[›éêýÒ| ?ýéOQ miiÑG}¤?ü0 5;ë_ÿõ_õ›ßü¦KKÒ'Ÿ|¢ÒÒRÕ××'­>ý-âihhPii©Ž=ª1cÆèG?ú‘é*iöìÙª®®ÖW_}¥;v¨¨¨ˆg¿IväÈIÒÍ7߬aÆu´~ƒ•””¨¶¶V¯¿þº>ýôS]sÍ5I©ßÎ;%IO?ý´¾õ­ou¬÷7,Ž=ª tÚgÈ!***ÒÂ… {ôÌkœÅK€††­X±¢#„ËËËUXXhºZÊÊÊRQQQGWWWÆ@’ù[ÂÆ “töš°ÿú°ßðáÃ;•Iæí•þ–ðý÷ßߣ}^zé%IÒâÅ‹R¯¾Œ ޳T a?ÂH-©:0k×®]Q—=tèî¿ÿ~UWWÄ1àq¥zûùÃxðàÁjhhPuu5Ï~3v¨ëʈŒ Ž£U«VéèÑ£7nœ~úÓŸ¦dûeeeiΜ9a¼{÷nÓUÆ.¿ür]qŦ«‘–èšÄdöìÙ’Îvc›¾=3Ñ"Ž£Ÿüä'7nœŽ=ª|P_~ù¥é*…ÕÞÞ®;wꫯ¾R~~¾fÍšeºJÐ/Äq”ŸŸ¯òòr7NùË_´bÅŠ” ãöööŽÁZùùù փ蚎3ûm­X±"¥mÂzÃßn]OF[ã¬Dµˆûõ¯Ä§j˘É!C$½% ]$$ÛÒ½Eì“d3]‰PòóóµvíÚŽ'Ñ<öØcZ¿~½Ñ:íÚµK_}õ•¬9sæð¼iÀ€œœµ´´¨¶¶¶ãáÔÖÖvì“,EEEz饗º<ÐãòË/×SO=Õ©Å8X˰´nü™ â” Ðx*((ÐÚµk;~ôÁ4‡Ã¡!C†ð£€A]t‘>úè#½þúë=Ú'Y.\(Iª®®ît_°ÍÖ翲%CžÈ3éØÁÛmëüS{в-h9pÞÿrœ›:ÏÍû§ù>Ÿo_peêêê"~ “Z[[;ºŒRU2ÿjRIOAiY–>üðC}úé§÷‹õ×—Bý¿˜ˆï¸Þ´ˆ‡r½Íf›*©A’W’'`j›·^¾s¯Ày,+h‚Öo¤ î—]Ó±´œÓº‹@ßf·ÛuÍ5×$íGÉP—t,ßñ) ܾ€A1Äçp}`Bªq¼úñ»=ŽÏ糂×ñcÖbÕ“T‰®.©ô®.¡¾›ƒ‹Ä© )uÍ8uþë‰^w£ÜBm !çõù|ÁÍÌÌì/Ãó‚s‡þ,B.+++É5 /\]Î}7{ú»»SÑ0ó¡–S^ªqð°óPÿ ¾/+`êI’»¥¥%ä#c¿OÐÏddd˜®`ŒÓéL‰V±ÝnÄÙÙÙ)ñƒÓé ûD¿sßÍîs‹Á·)Þ¢î¥ó¡¦)Åä9ÑžXO\¨¿˜,Iž;wþ6Ô àZq 222RâpÀ¤¬¬,£al·Û#¶zsssþ¿êt:•››vû¹ïfÿ=ÃR÷-ßh%:kzÍä=‚Ë?Ì#p›=h}ðËbÙÿPÿ+ðÁƒÜn÷ïœNç7BUÊív«­­M.—K>_jü•jô°Ùlr8)ÓR…Çã‘Çã‘eEºÜþVpO¶µµUíííòx< ¬ÙלN§²²²º}¶½Çã9ž‘‘1_Òu~‡7àü îZÊ Ø®€åPÓàùpúü="=¼£»í–:?q+RvûÖ­[ËçÏŸò—¬322èj“ž†¢ ÙÙÙ)÷ƒ/[·n-—Ԯ軜ƒC6˜‘§dÅ"Ñ#lbyÌe¨ið£.[ÀþõöÓîZÅÙµµµÿwèС·õàó⬮®nÛ°aÃ~(©Uѵ†ƒ§R×pîîÑ–S…Y–°àN—>Åp',Ô‰énàVàrÛ°aÃÖ455uyî4 9šššö 6l¤6õ¬Û9P¬ášLqoNR¸[•BuIû—ƒÞš——÷P]]ݶ^ÔƒºººmyyyélK8ð»Ù “ù»]ëce4°Mq´¢éR·Îÿ•7`Ùÿí•ÔìLj¢LL’ñ8¤x Ø Ùàåpƒ¶¤®¿O< žw¼‚Å㣠:ÁéYÿ+°w2x>xj…8V¸;c‚ש›ip]#}–„H…1ö‘n[ .8UкÀ²þ[šüó‘Þ#°+Û£ðAˆ,\P†zêa¸ð ÜÀùpï¼®'õ4&‚8œž´>ðžâÀìÿ‹Ê¦³ÿÐþíuþĦ¯ƒ;°…-Ä­À@ Õ¥jpVðXžP³‚9m7œT âP­[ÿr`+7¸%<ïø€Ûƒ¦ÛƒŸÔå]ÓÐS¡®Ó‡rp«8¸%ÜÝHéPóÁï\.Ô²qÉâh[¶½9¾Ô5„ýìïÀi྾ rÁ-âÀnðÀ÷„ª©‹:Ô|àþRè0Nt°&<¸“*ñ=-EA¸.xÔµ]au7â:øØÑ~èÏÂu%w7ò98Œ¥Î­ép£¢£i §ähi¿Tìš–bkE‡ë®–¾nåú[Ä>uîâ iZÄÐ;ݵˆýÛƒ[¼ÁaÜ¥|ìX2庥¥Ôj—‹¥UÜÝíM¡îGw/r`¹àùàz¾vVˆùà`¾&ª\w׌ÕîI¹˜%«EœÈëÄ-a¿Pƒ¶ü¬ í­by»¾Yî8ïý]¸ïÉà_H æHîºpoZÆÑJJ :U»¦¥Ð#¢×Þ¦¼Oð€-…XÕ…í/cu³ á  ¯ëI@u7À*T÷s¤P÷þ=i §”d†F¼º§ƒçCÝZªÛº»õ‘f®›P¡jU¸næhºŸÃÝ®”òÝÒÒÙ‡Y$SOGO/‡ Æàõ¾ë‚[Ôáêê? ‚bê§ ¥î[­= ápaÙÛÖpÒZÐɘXZÅË‘ZÅáÖG3Ø+ܼM1Ä*\—rw¸.R«7Rk88PSª5,¥f‹8T¹HaÜjí®eìŸ>ÉѶ‘…h\FŠÖ±†pZ0ÑÒKTK‘[Æáö Õê ÷ÝÕú«Xºˆ£i-÷&„S¾5,¥v‡*í@®žu¸Á[áêÑS6€tÕÛPꮼ=Ú e@VOGz'©€ˆw«8Ô¶X‚:ÔöPeÑ鮪L¤ 6lÓ¢5,¥~‡*k÷t{wåÑ ¼ ±.šMt÷´l\˜ ˜d„±9–[¢z²ðµh‚7p]O8\™h¶EbdW²GMKTG».ÚAX±v¥@_kWoO¯éF Ö!,™ŽÞqðºžlï®,­`ˆŸh[¡Ê÷&„í §ß±Ôû0^ßÛ@Ü– çú‚p׊·Gšv{wë{[6îR!hzZ‡XÃ8R™H¡M9@ô×èɵÞXËD£ß±¿0Ü`Gkúªxµ:{Ü= ÷HŒ?…+•Â#aíq#…w*'H'‘Z­Ña¬-ìžטT ˜X꓈.e®@üEºNª|¨ùîÊÅrl£R-lb­O7ŽzÓ]|Œdï›0©6½­_<>_ªŸ#HuñÀÞ#%CXJIÕ0M‡sÉ”ˆ°K…O¨t “xÕ5>3ôGñ Δ`¿t %F:@ßïØi!Ènfè;Rµ;;iÒ9€Y÷t>/Ê’iÀ~}!p’ùúÂù€dHf(¦eûõ•`IÅÏ‘Šu€ÞHÅÀKÅ:õH_ ‹¾öy¡¥}ûõåàêËŸ ú£>¾úCXõ‡Ï}YŸ `¿þRýíó@ºêÓᨿSÿü*úMð#ˆ:ã|@rôÛà FðD‡ó±!p# `âs  ¯#\ñõÿ„œ¶ÍÈC²nIEND®B`‚Portfolio-1.0.2/screenshots/en/3.png000066400000000000000000000737021476103320100173070ustar00rootroot00000000000000‰PNG  IHDRâ6*Ù€³sBIT|dˆtEXtSoftwaregnome-screenshotï¿>-tEXtCreation TimeFri 09 Feb 2024 04:06:43 PM -03 lµ¨ IDATxœìÝ{tTÕÁþñïÌdÉÕr€r‰B­ ×E¬ZÁ…¢¢FDªëE-D-±*úB[é+*¢‚Ø Øe /EˆH_/@ëÅ‚¼„\&—¹ýþ€Œ“0ÉL’™œIò|Öš5snûì™$ódï³Ï9 """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""˜Œ®@+¤ÏTDZ;·ÑhMÑç$"Ò8 m?05éói è3Úzð´õ÷/".Úl0·µ jkïWD¤¥j3ÁÜ‚©-¼G‘Ö¬U‡rk©ÖüÞDDÚ¢VÈ­-¬ZÛûßZM(·–à Ç÷ŽuiŠp ¿p¬Sƒ´†°hÎ÷Ð>/‘æÐœ٢ø%K(ën qù""m‘›Ð†f‹ ä–6¡¨³9eŠˆˆ®”Ù¢¹¥q°ê«¯ˆHø f‹¹Å„qK £¦ÖÕTë¹9÷-"ÒV5&ݵž›sßÍ®%LSêØØð tý–ðù‰ˆ4‡@C¯¡áŒPë@÷ ilýÀþÖÓ1d‘Æñw ¸¡ÞØP Û0ç nJûÛ¶¾å¦:^‹ˆHã¹ëx]ßzu-oUa®AӔ㸆líùÙNDDêç/tëZHX7åøsØÇ€il74HC¾áøyŠˆ„B°C°®Pnl7¶Í.Ü‚£±ƒªêkéúšW×|ûñ·žˆˆ4­º¡ÜØÖqØ„q¸…JCêÓÐöÀþ¶mÊ)O""rvPÚí+ƒÆ bÂÁ`_¡NŸˆHKæ«•HË·¡ÜâÂ8\‚&T!\û´£º¹1]Üþ„Ëg+",Á<믥\{ºöiP­&ŒÃ!,Bµ׫/€ÒZ®o ¬õZ_«·¾ík‡l«ãp”†Ô¡® kÂu°¿ Õ=DDÇ_K¶® ­+p ã†ÜL¢Mq°CØ\Çü@¸!-j L !ÚÐ@vÕ1Ÿ:Öñǰ06:`r J]Æõ…p \ßñdµˆED§¾Àtù˜ï+Æ é¢n“AÜÔÖpcBØ_ׯÁÄ%"Ò5ôÔ"—×u¬nJ‡}«Ø¨ f×´¾–Õ`_­åúFS+|EDêWß©J¾Bµzºv Æ3ë0÷ öw\¸®6ûXÏûÙìcÛºŽ#›ë©‡ˆˆÔÏÅÙ­V_!Zýº¾0ß-àúF]{×#m"ˆƒÕ$„ÀÞÛ™ñ]¦ˆˆ4Líàtá»H 7%ŒÃ¶UÎAh{ÏoH›}Ì÷~í½½‚XD¤q¼ƒ×»‹ÙÅÙ¡[;˜ ã@F[{×%ÍÄ͹36J:Ðe¾BØ×2s­õªC·¾ öž/"" ç¸Þ“×ÞÛ˜j½®^^ýÚìc>¦ pYcÖ Šæ⦨ë¼àºž aK­g§?ë™yf~ŒXƒ¹D¤µkHH¹¼NÀ88ý½ê>3Ïtæ¹:\½Ÿ«Ë¨þnõ¡™Ã´)Â1ˆkw;×^V×úu/®+„#¨ÂV jóæÍi©©©·GEE]l2™âL&“ZÃ""Màv»]n·»¸²²ò‹;v¼qõÕWTr:”«CØÄÏ]WCÍÐõì¢Ö.ë aï0ÍÙzkH·´¯î熜†TW7³™Ÿ[º¯y1›7o9f̘L‹ÅÒ-Ð7$"" çt:ó·nÝ:ïꫯÎlüÜ}íäç–³¯.m_Ç›ÁÿiOÞÂî"áľZµÕÓuu?ûíë˜ouëØBÍ0Žùé§Ÿþ;!!aB߈ˆ4AQQÑúÄÄÄÇ9ÆÞ!\ýºö€®ÚÁL=ó î ÆÇ|_Úl74„k¯ã=«zºÎ.++ûŸ˜˜˜Ô½ ›Í¶ãœsΙAýaì=ªºöhêº.úŒVq³qsÿ eà×>&\{P—wkØ{Vô?þ8O!,"bœ˜˜˜ÔüqMÍC†ÞßÙµ¿×뺬q°5Kc5œ"ù UWkÓ¾º¤½[7mÚ4211q|pª.""•˜˜8~Ó¦M#©yÆJíïo_ì+êOT[ØœáÒ\il·4ø¾\¥¯Ü»5ìýCŒ æ ­à»Ýþ~DDÄyuU¦¼¼œÊÊJGUoºˆˆ¢¢¢ˆŽŽn–ý‰Hèèû£áÇQ«Õz-PÆéÑÓÞƒ¶ª§kwK×u±ú.‹I­õý y÷t¸´ˆë j7|¨ž®ë¿¢Ú#ª-@Äû￟ZW;NŠŠŠ(++k¶?"‡ÃAYYEEE8ÎfÛ¯ˆ¾?/""â¼÷ß?•Ó%ïk;€ï«âcÓÕÛû­âæâ`¼Q] ¾º#jÿધ#ÓÒÒn¯kG%%%ÍúT›Ãá ¤¤Ä°ý‹Hãéû£iÎ|7GâûÔºzG½Ú-Ý!ëpiªöBþæùúaZbbb~é«ðòòrCÿˆª9ÊËË®†ˆ4€¾?šîÌw³wk¸öaÇ@¾÷k¿{áÄWö5¯öe)k Ö2™Lq¾ «¬¬lhC&œê""þ…Óßl8Õ¥!Î|7׬UÍ{àÖY›R|“+"áp‰Ë†ŽhkÈñŸWת법áðßlµpª‹ˆøN³Á¬‹Ó餲²»ÝŽÃáÀí>=vÉd2Õj%** ‹ÅÒä}ùn®ëªˆÕ¿ëúÎ÷wé†Îo6¡â†üâoÝúZï¾â×.××ðwñÁétb³Ùêl]»Ýnìv;v»›ÍFTT111Ád_÷¯Í{^]!Z}­êº4$€CÖáÐ"öÅ__}­e_§6ùš/"">TTTPZZÚ m*++©¬¬¤}ûö´k×®)»¯ë;»ö¼@[·Þó oýúÒ’[‡´w+¸¾ã ""Øl¶‡°·ÒÒRl6[Sªàë;;S”Zìw»Ñ-bÇ}=×õßQ]§+ùú¯*h¾üòKÖ¯_Ï_|Aqq1ñññ\|ñÅL˜0_þÒçàl‘°TQQÑÔN‡¹Ùlnl˸¾ïxïÛúêzö^îï~ŵ÷iXKÙè TØý§ãt:yñÅÙ¸qcùßÿ=|ð|ð&Là·¿ýmP1ˆˆ„’ÓélRK¸¶ÒÒR¬V«ÑßaÙ][(ƒ¸±áìaè! ñꎊŠâ¶ÛnãÊ+¯¤C‡²uëVÞzë-Ö¯_Ûíæ¿þë¿BQia***p8ÄÄÄ`6vThäÈ‘DGG“““Ó¨}VVVòÚk¯±iÓ& éÔ©×_=S¦LñY‡#GŽpÛm·áp8صkW£ö)þ]uÕUÚnË–-A®ÉÏ‚ÑöUflllS‹iè÷½¿àml8‡,Ô[ò1â@„$„¿üòKOgeeqÇwÐ¥K"""èÚµ+wÜqYYYDEE±aÃöïߊjH óÌ3ÏpÅWpèСfÛçÂ… yùå—‰ŠŠâª«®¢¤¤„¥K—²|ùò³Öu»Ý<óÌ3-öTi¼êS”‚­²²2X—Ü »^Ñ`j)]Óþ4ëiÆ ÜvÛmôíÛ×ç:}ûöåÖ[oeåÊ•lذAÇ‹¥Ù9N6mÚDRR¯¿þ:V«•žžÎÎ;¹÷Þ{k¬ÿÎ;ïpøða)..6¨ÖmC([¶á/„¯ºê*ú÷ïÏܹs‰ ¸¸˜¹sçòÙgŸÕû~*++‰‰‰ j}½´ˆ®gŒlž¡ìân”Ï?ÿ€+¯¼²ÞõÆŒSc}i»233=ÝË“'OfÈ!že.—‹åË—3iÒ$FÅÔ©Sùøã=Ëív;Ï=÷×]wãÆcÙ²e5Ê4h«V­   €#Fpíµ×zŽ÷UUUQVVüü…{þùçר߉'X²d 3gÎô|ÑJÛa·Ûë]~þùçóå—_2{ölN:Å©S§xä‘Gøì³ÏèÞ½{“Ên€Pga­îÖÞ5Õ­…:Ô»^ÇŽ8uêTÈë$áÍûŸ¶+®¸‚ûî»Ï3½páB–.]ŠÃá --òòò-ˆââb¶mÛÆyçÇO?ýIJeËØ±c³gÏ&>>žW_}•~øE‹QUUÅ“O>I||<ãǧ´´”É“'3{ölxàÈýôSRSSILLdÖ¬Y<ñÄÜtÓM”””0iÒ$O‹ûÁä³Ï>ã?ÿùç÷155µÆïä¦M›øøãyýõ×1™Zõ¡¸°nƒµª/[Y—:••ÅìÙ³9|ø0pº¡±hÑ":wîܤ²ƒ v÷t‹ë®·q°¾Búm2`À¶nÝZïzÿüç?¸øâ‹CYiÁ:„Óédذau†ð¹çžKTTðóï’÷1Ük¯½–xn7sæLàt—÷<ÀÑ£G¹ÿþûy衇èÙ³'Ï?ÿ<¿ûÝï(**báÂ…ÜtÓMôîÝ·ÛÝ_œÒY­Ö¿£‘‘‘uþΆP‹Èˆ†j‰-bÃM˜0>ø€·Þz‹ú°uàÀÖ®]‹Édb„ ÔRZ‚ê㸆_]çdz_8¡ú´¤?þ˜}ûöqçw2uêT&MšÄm·ÝÆ¿þõ/Ž;Æk¯½Fqq1k×®eíÚµ5Ê4hýúõó–à ·ÁZ&“©ÞßÁü‘ŒŒ òòò<Ç„9BFFYYY$%%Õ[¶Ô/ÜZÄ-Â/ùK&L˜@ee%³gÏæõ×_çØ±c8 XµjTVVGrr²ÑU–0qúÿ^ï·÷èÑ€;vPUUÕ¨rwíÚÅîÝ»=Ó¯¾ú*pú¼e€¼¼<Ï2ï ,X,zõêÅĉk<ªMœ8‘Ñ£G7ªNÒ²TÿnÖeΜ9žÎÊÊ"++‹îÝ»säÈæÌ™Ó¤²E-âFûío‹ÛífÆ ¬\¹’•+WÖXn2™HHH ¨¨ˆY³f‘Mbb¢Aµ•pЧOž{î9ÒÒÒ˜:u*^x!—]vŸ~ú)wÞy'C‡åСCLŸ>Ýs¤>eee<óÌ3$&&òÖ[o1uêTV®\ÉØ±c4h;v$77—o¼‘_ýêWüûßÿæ›o¾aøðátêÔ‰[n¹å¬2÷ìÙC~~>¿ÿýïƒþHx²Z­õŽn>tè½{÷fþüùžÖoVVsæÌñ{^¼Õj j][£pâÆôi4K?ˆÅbá¿þë¿3f 6là‹/¾à§Ÿ~">>ž0aÂ’““ÉÈÈàðáà caÒ¤I|õÕWäææ²~ýzFEß¾}Y¸p!/¼ðü1ï¼ó=zôð´fýùóŸÿÌñãÇyòÉ'ùÅ/~Á¬Y³˜5kÏ<ó +V¬àå—_fÙ²e|òÉ'äääйsg¦NÊ”)SBûf¥^á6X+**ªÞ+kùÚoRRÿó?ÿPÙAÖ˜ÁXa=€+”¡å¯ìºî”ä½ÌìcÚ×ý…}ÝeÉræ9âÌëêçx·Û½ÓW…¾ÿþ{?Un¸S§NyFvëÖ¬¬,Î=÷Ü€¶ýÅ/~ôúˆHh4äû£9‚¸¡ß%%%A¿ºVTTTƒ/qi2™†§'àðzvyíòz¸Ï<¼_ã5¯yµ§½×§Ö²º„$Ì âºnaÕê‚NL­n÷éÓ‡¿üå/m§ i9BõýÑX ýþp:üôÓOA­Cbbbƒoú`pSǼ@–5ZK¬Õ"†ã%$$°hÑ".¼ðB¿ç܉ˆÁb±Ð¾}û •×¾}ûæ¼óR‹È‚º„êq‹þPB!!!_|ÑèjˆˆÔ©]»v¸\®&߉)&&¦±÷"w!9ÖÜÒ[Ä""D111Mj·oß>”7yh•Â}Ô´ˆˆ4³víÚaµZ±ÙlàŠŠŠ"&&¦9»£[ ±ˆˆœÅb±KLL •••Øív‡ç \&“‰ˆˆ¬V+QQQ à&P{‰ˆˆ›;…èj4"-Kkýþ°X,êj1#ö‚Ï-œê""þ…Óßl8ÕEüS{‰ŽŽ‹–hDDÑÑÑFWCD@ßÒX âZbcc ýcŠˆˆhð•hD$<èûCÃøßÂŒÅb!!!òòr*++›í˜ODDQQQúOV¤Ó÷‡4†‚¸ÑÑÑú¥‘FÑ÷‡4„º¦EDD ¤ 1‚XDDÄ@ b)ˆEDD Ô&GMçççãr¹ü¯(""ÍÆln›mÃ6Äݺu3º """€º¦EDD ¥ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@mòZÓ q¼ØIöæ"¾8Z…£Ö}",fèŸÉc×$Ð!ÖbLED¤ES‹ØìͧØ{äìpºà³ü*l*jþЉˆH«  öã‹‚*¿ëì˯bÍîR*ìîf¨‘ˆˆ´&êšöÃá ,\_ÙQÂ+;JTvBŒ™±}£™š‹ÕbjLõDD¤…S‹Ø@E6ëö”±â£†¸ˆˆ´jQ\´™á½-tŠ3c6ûoáº\nŽ»ÙùuÓFÆ5C ED$Ü(ˆƒÀl‚ó’,¤¦Dx³Ùl¢k‚ õJ‹ˆ´] b?b£LŒH±Ò)ÞLÜFé×´‚333ÉÉÉÀb±Ð©S'ÆŒÃý÷ßOddd“ë—žžÎĉ?~|“Ë‘šÄ~¤]x:„CÉdjzÂß|óÍÌœ9“ªª*8Àܹsq¹\üîw¿ B ED$T4XËŽq-ã#²Z­DGGϰaøå–[øôÓO®–ˆˆøÑ2RÆ@Ah¬¢°°^½z`·Ûyî¹ç¸ñÆ1b'Näý÷ß?k›7ß|“›o¾™1cÆðÐCqìØ±³Ö)--eÒ¤I¼üòËŒ7ŽÝ»w{–îlqÆå—_N\\k×®mrý, íÚµkr9""­‚¸KLL¤}ûö—\r 6là»ï¾£  €yóæa·Û=Û´k׎ýû÷óÉ'ŸpêÔ)–-[†Ífc̘1ÍôɈˆ„7#n%Þ~ûmÞ~ûmàt°¦¤¤••åiyΜ9“—^z‰¥K—2|øpF]cû?üádggsï½÷b·ÛIII¡OŸ>ÄÄÄÔXïî»ïæË/¿ä‘GaÅŠ<øàƒÌ;—[n¹…äädÆç i€öíÛsõÕWóì³Ïòý÷ß3`À^zé%¬Vkˆ?‘–!TgÉú+××r“×üêg³iïuÌ^ÛUOW?,gž#μ®~Žw»Ý;}#o|òS «6Éí—'ú_©Òå1E¤!L&Ópàà^Ï®3¯]^·×£z¯i¼æÕžv{½öæïÞ·A¿ñ¼º¦EDD ¤ 1‚XDDÄ@¬%!U}™MñM-b?\A?,6·»v"""aIA쇭*ô·_ªt„|""¦Äþ¸ÌX,VBÑhu»ÝXLn,ú1ˆˆ´Y:FìÇô‘qìγ³ëk8Yb÷¿AtŠ53¸g—ŸÔrED¤åP`p+ƒ{èJP""|ê1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@FW Ü/v’½¹ˆ/ŽVápÕ\f1CÿäH»&±c*(""-šZÄ~do>ÅÞ#g‡0€ÓŸåW±`SQóWLDDZ±_Tù]g_~kv—Raw7CDD¤5Q×´g`áúÊŽ^ÙQÒ ²bÌŒíÍÔÔX¬Scª'""-œZÄ*²¹X·§Œ5,ÀED¤õP‹8ˆâ¢Í ïm¡Sœ³Ù ×års¼ØÍί+˜62®j(""áFAfœ—d!5%‚¨ˆÀ»˜Íf]L¨WZD¤íRûebDŠ•Nñfhä6JǸ¦œ™™INNf³™äädúôéÃ}÷ÝGŸ>}‚QEÒÓÓ™8q"ãÇ?kYUU.—‹víÚe_""m‰Žû‘v¡•. ¡ a“©é…ßtÓMlÛ¶œœž|òIHOOgóæÍA¨aý^}õUžyæ™ïGD¤5R‹ØŽq-ã•ÈÈHÚ·o@bb"—^z);wfÑ¢E 6ŒØØXƒk(""¾´Œ”1P«†¹ãŽ;p»Ýäææ`·Ûyþùç¹þúë;v,óæÍÃf³yÖß¹s'éé餦¦2a–,Yâ³ÜÒÒR&MšÄË/¿ÌªU«xõÕWÉÉÉaÈ!,X°—ËÅŠ+˜8q"£G桇âØ±cž2ÒÓÓyûí·™6m£Fâ®»îâÀ!ü4DD“ZÄ­Xdd$½{÷&//€E‹qêÔ)V­Z…Ãáà©§žbñâÅ<ú裓‘‘ÁÃ?ÌÕW_ÍÉ“'©¬¬<«L§ÓÉc=FJJ ÷Ýweee=z”yóæyÖ[¶l;vì`Á‚DGGóüóÏ3cÆ þú׿bµZX¸p!¯¼ò ]»våõ×_çþûïç½÷ÞSë]DÚµˆ[¹:PTTDYYëׯgÖ¬Y$%%ѱcGf̘Á¶mÛ8yò$v»ÔÔTâââèÕ«ýúõ«Q–ÛífáÂ…Øl6žxâ‰:÷YUUÅêÕ«™3g½{÷&99™¹sçRVVæi<þøã\zé¥tìØ‘‡~˜ÄÄÄf9¦-"NÔ"‚ÿ;\Å—Gí¸\n"#L\ùËvtI›@œ>þ¬uÍf3K—.eÏž=¼÷Þ{ddd0hÐ ²³³=ë\~ùå|ûí·¬]»–ûï¿¿Þýº\.œN'fóÏ.‘‘‘õ¸ÅbÑ)P"Òæ¨k:Ün7ƒ.ˆäW="I»0ŠôÔs˜:ª=· ‹á‹£vÃêõæ›o’œœÌ!CèÒ¥ çœsÛ·o¯w›òÔSO±|ùr¶oßNaa¡gY×®]™?>+W®¬ÑÅ §[×Õºuë†ÉdbïÞ½žy.—‹лwo¾ÿþ{þö·¿Õ(cÿþýôêÕ«ÑïWD¤%R‡PT„ —«yîÈd·Û)//§´´”ýû÷“••Åš5kÈÌÌÄd2Azz:‹/f×®]8NNœ8á©\\\ÌÆ),,¤¢¢‚={öMBBBý :”éÓ§óÄOŸŸ@§NøôÓOùî»ï(//'&&†n¸gŸ}–ðã?’••ERRC‡ ]»vìß¿ŸO>ù„S§N±lÙ2l6cÆŒi–ÏKD$\¨kº•X·nëÖ­ÃjµÒ³gO.¹äÖ®][£zêÔ©DEE‘••ÅñãÇIJJâ®»î¢_¿~”””°uëV–,YBYY=zô`Á‚>»’ï¾ûn¾üòKyäV¬XÁu×]ÇG}T’L IDATÄ-·ÜÂÀùÓŸþDFF/¼ð³gÏÆáp0dÈ/^ì9.ܾ}{®¾újž}öY¾ÿþ{ ÀK/½äQ-"ÒV„ê,YåúZnòš_ýlö1í½ŽÙk»êéê‡åÌsÄ™×ÕÏñn·{g oäO~ò»Îk–qÛ°˜³®3]épóÖ¿lLI;Ço·_žh•Z…ú.™)"m—Édœœ€ÃëÙuæµËëáözTOã5×¼ÚÓn¯×Þüuc½›S-â ¸ø<+oýËvV7´ÙlbÀyjቈHÝÄA0ð‚HÃFF‹ˆH˦ÁZ"""R‹X ±jÕ*£« "Ô"ö£9Î>r»›ç' ? b?lU¡¿ýR¥Ãÿ:""Ò:)ˆýq™±X¬„¢Ñêv»±˜ÜXôci³tŒØé#ãØgg××p²$¸—«ìkfpÏ(.??:¨åŠˆHË¡ ÀàV÷ÐùÀ""|ê1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@FW Ü/v’½¹ˆ/ŽVápÕ\f1CÿäH»&±c*(""-šZÄ~do>ÅÞ#g‡0€ÓŸåW±`SQóWLDDZ±_Tù]g_~kv—Raw7CDD¤5Q×´g`áúÊŽ^ÙQÒ ²bÌŒíÍÔÔX¬Scª'""-œZÄ*²¹X·§Œ5,ÀED¤õP‹8ˆâ¢Í ïm¡Sœ³Ù ×års¼ØÍί+˜62®j(""áFAfœ—d!5%‚¨ˆÀ»˜Íf]L¨WZD¤íRûebDŠ•Nñfhä6JǸ¦œ™™INNf³™äädúôéÃ}÷ÝGŸ>}‚QE¿ÒÓÓ™8q"ãÇo–ý‰ˆ´:FìGÚ…Vº$„.„L¦¦~ÓM7±mÛ6rrrxòÉ'IHH ==Í›7¡†""*jûÑ1®eü¯IûöíHLLäÒK/¥sçÎ,Z´ˆaÆkp EDÄ—–‘2 BcÕ0wÜqn·›ÜÜ\ìv;Ï?ÿ<×_=cÇŽeÞ¼yØl6æÌ™ÃÓO?]cû+V‘‘áw[i<q+IïÞ½ÉËË`Ñ¢E;vŒU«VñÆoPPPÀâÅ‹7n¹¹¹¸\?_BlÛ¶m\}õÕ~·‘ÆS·r:t ¨¨ˆ²²2Ö¯_ϬY³HJJ¢cǎ̘1ƒmÛ¶0|øpœN'ûöí °°Ã‡“––æw[i<#nåNž×¿æšk˜7o?ü0|ð·Ýv@@ÛŠˆHã¨kº{óÍ7INNfÈ!téÒ…sÎ9‡íÛ·×¹þ Aƒ(//gÏž=|õÕW :  mED¤qÔ"n%ìv;ååå8NòòòøÇ?þÁ–-[xá…0™LDDDžžÎâÅ‹éÔ©ƒ âûï¿ç‡~ _¿~À鋌;–§Ÿ~šQ£F ж àv»ƒr^´ˆH[¡ n%Ö­[Ǻuë°Z­ôìÙ“K.¹„µk×ÖèJž:u*QQQdeeqüøq’’’¸ë®»IBBééélÞ¼95 O¯¾ú*Ï<óŒÑÕiµˆýè×2þW‰ŒŒ¤}ûö$&&r饗ҹsg-ZİaÈ5¸†""âKËH¡±j˜;ÛMnn..—‹+V0qâDFÍC=ıcÇjlóæ›oróÍ73f̘ËÇÇîÝ»=ëJqq1Üpà ¼ÿþûdgg3f̘ ~¢""Á§ ’=yV~TÉŠ+xíà ¾,p]%:tè@QQUUU¬^½š9sæÐ»wo’““™;w.eeeäææât:Y¾|9¿ÿýïéÛ·/;vdܸqDGG´Ÿ„„233éׯIIIÜyçÜsÏ=tïÞîÝ»sà 7xZ´eee¬_¿žY³f‘””DÇŽ™1cÛ¶m;«Ìþýû“””Ä”)Søá‡ÈÏÏ÷Y‡“'Ob·ÛIMM%..Ž^½zѯ_¿¦}€""!¦cÄAòåQ'·‰$*ÂÄgG|òÍéVfÿdcoqòäIÒÒÒ(((ÀáppÑEy–EGGsñÅóí·ßRPP€ÍfkRp™Í?ÿ_׳gOl6III\pÁž®ç¼¼<\.÷Üsg°šÃá ¬¬¬Î2»víJtttîko\pƒ bÒ¤I¤¥¥1~üxÜè÷#"ÒÄAâr¹=ç÷êhÁé‚ÿûÆnhWUUqèÐ!¦L™â9ÕÇétÖ·ÈÈH"##)--NG¶XBSgïÑáÕƒÇV¯^M|||£Ê¨Íl6³téRöìÙÃ{ï½GFFƒ ";;»ñ• 1uM‡@ûv&.;?£oñæ›o’œœÌ!CèÖ­&“‰½{÷z–»\.8àéªv¹\|¸Æ<—ËÇ-©Ô¥KÎ9ç¶oßÞè2àt+º¶òÔSO±|ùr¶oßNaaa“ö!"J âVÂn·S^^Nii)û÷ï'++‹5kÖ™™‰Éd"&&†n¸gŸ}–ðã?’••ERRC‡%>>žÑ£G³`Áòòò°Ùläææzº/¹ä6lØÀwß}GAAóæÍó òjŒˆˆÒÓÓY¼x1»víÂétrâÄ 8p:uâÓO?å»ï¾£¼¼œââb6nÜHaa!ìÙ³‡èèh]O‘PS×t+±nÝ:Ö­[‡Õj¥gÏž\rÉ%¬]»¶F·oFF/¼ð³gÏÆáp0dÈ/^ìéîýÃþ@vv6÷Þ{/v»””úôéCLL >ø sçÎå–[n!99™qãÆÕ9h*PS§N%**Ь¬,Ž?NRRwÝuWÀÇ©¯»î:>úè#n¹åÈìٳٺu+K–,¡¬¬Œ=z°`Á"##›TO‘P ÕY²þÊõµÜä5¿úÙìcÚ{³×vÕÓÕË™çˆ3¯«ŸãÝn÷Î@ßÈŸüä%`åG•žÁZ•7kvUq׈¨€¶¿ýòÀNiÍL&Ópàà^Ï®3¯]^·×£z¯i¼æÕžv{½öæï¨bÐ:ªE$'[X³« —ëôÏÈl6q±Á#¦ED$ü)ˆƒä²ó#¸ì|}œ""Ò0¬%""b ±ˆˆˆÄ~¸šád`·Ûè3ŽEDÄ( b?lU¡¿ýRåÙפ‘6BAìËŒÅb%V·ÛÅ䯢ƒˆH›¥a¾~LÇî<;»¾†“%¿’”/bÍ îÅåçv‡#i}ÄÜÃÊàV£«!""­úDEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1P„ÑwÇ‹do.â‹£U8\5—YÌÐ?9’Ç®I C¬Å˜ ŠˆH‹¦±Ù›O±÷ÈÙ! àtÁgùU,ØTÔü‘VAAìÇU~×Ù—_ŚݥTØÝÍP#iMÔ5í‡ÃX¸¾²£„Wv”4¨ì„3cûF355«ÅԘꉈH §±Šl.Öí)cÅG pi=Ô"¢¸h3Ã{[ègÆlößÂu¹Ü/v³óë ¦Œk†ŠˆH¸QÙç%YHM‰ *"ð.f³ÙD×ê•i»Ä~ÄF™‘b¥S¼™¹Ò1®igff’““€Ùl&99™>}úpß}÷ѧOŸ`T±ÁÒÓÓ™8q"ãÇIùUUU >œõë×sÞyç…d""ÍALjýH»ÐJ—„Ð…0€ÉÔôÂoºé&¶mÛFNNO>ù$ ¤§§³yóæ ÔPDDBE-b?:ƵŒÿU"##iß¾=‰‰‰\zé¥tîÜ™E‹1lØ0bcc ®¡ˆˆøÒ2RÆ@Ah¬æŽ;îÀív“›› €ËåbÅŠLœ8‘Ñ£GóÐCqìØ1üqæÌ™ãÙ¶ªªŠAƒñÜsÏyæÙl6†JAAééé,_¾œiÓ¦1jÔ(&OžÌ¾}ûê¬K}û¶Ûí<÷ÜsÜxãŒ1‚‰'òþûïרÞétòâ‹/ò›ßü†k¯½–¬¬¬ËwîÜIzz:©©©L˜0%K–4íÃi& âV,22’Þ½{“——À²eËøç?ÿÉ‚ X½z5‘‘‘̘1»ÝÎÈ‘#ùä“Op»OŸ7ýÿ÷lÛ¶ÍSÞ§Ÿ~ÊyçGrr2EEE,]º”™3gòî»ïÒ¿}ôQìv»ÏºÔ·o«ÕJ·nÝÈÊÊâí·ßæúë¯gîܹžz,Y²„;w’ÍK/½Dyy¹gYqq1Üpà ¼ÿþûdgg3f̘`œ""!¡ ²ƒÇ<æ0º:t ¨¨ˆªª*V¯^Íœ9sèÝ»7ÉÉÉÌ;—²²2rss>|8%%%:t8ÝÂ|à(**â›o¾`×®]¤¦¦@ff&ýû÷'))‰)S¦ðÃ?ŸŸVüíàæ›o¦W¯^$''s÷ÝwÓ¥KöïßïÙþí·ß&33“””ºuëFff¦§ü“'Ob·ÛIMM%..Ž^½zѯ_¿~®""Á¢ ²JÇéG¸8yò$=zô   ‡ÃÁE]äYÍÅ_Ì·ß~Kûöí¹ì²Ëøä“Oøè£¸òÊ+>|8~ø!p:ˆGŒáÙÞlþù×§k×®DGGc³ÙΪƒ¿}ñÖ[oñè£rÿý÷sôèQ***8zô(N§³ÆöÞû¾à‚ 4h“&M"33“Ý»w7å#iV âV¬ªªŠC‡Ñ«W/ªªªp¹\8ÎëDFF À¨Q£Ø½{7GŽÁb±Ð½{w®¸â rss9yò$'NœàW¿úUû«kô·¿}———3eÊNž<ÉôéÓÉÊÊ"%%ų¾ÝnÇårárù¸ó§CyéÒ¥üéOÂjµ’‘‘Á¬Y³þœDDŒ¤ ’ŸÊÜœ,ùùºÔ'KÜ•{ˆ7ß|“ääd† B·nÝ0™Lìݻ׳ÜårqàÀz÷î ÀÈ‘#Ù»w/¹¹¹¤¥¥ššÊ¿ÿýorrr2d hïoߤ¨¨ˆ™3gÒ³gObbbøê«¯8qâDÛû2pà@žzê)–/_ÎöíÛ),,lp]EDš›‚8H.ز¿ŠÅ.N»Ø²¿ »ï\HØívÊËË)--eÿþýdee±fÍ2331™LÄÄÄpà 7ðì³ÏràÀ~üñG²²²HJJbèСtéÒ…®]»òòË/{‚¸}ûöüêW¿âÏþ³çøp (((ÀívûÝwRR¥¥¥¼óÎ;œ:uŠ-[¶§»»bbb¸îºëøãÿÈþýûùþûïyþùç=û*..fãÆRQQÁž={ˆŽŽ&!!!X¯ˆHÈ(ˆƒ¤C¬‰1}­œ,vq┋+úZéÛ|ç>­[·Ž´´4ÆŽËüùóq¹\¬]»–””Ï: >œÙ³gsë­·RRRÂâÅ‹kt)9’²²2.½ôRϼѣG0lذ€ë3yòd6mÚDFF†ß}ŸþùÌœ9“—^z‰o¼‘mÛ¶yöYí‘GaРA<üðÃ̘1ƒŽ;z‚º¤¤„­[·rÇwpå•W²~ýz,Xàér g¡J åúZnòš_ýlö1í½ŽÙk»êéê‡åÌsÄ™×ÕÏñn·{g oäO~ tUàt—´ øECøöË´¾ˆHkd2™†§'àðzvyíòz¸½ÕÓxMã5¯ö´Ûëµ7ǃ~ÌQWÖ ’VøœwZ»f®‰ˆˆ´$ â QàŠˆHc豈ˆˆÄ"""Rûáj†S«¯ï,""m‚Ø[UèOA §KbŠˆHóRûã2c±X E£Õívc1¹±èÇ "ÒfiÔ´ÓGƱ;Ïή¯ád‰ï[ü5V§X3ƒ{FqùùÑA-WDDZq÷°2¸‡ÕèjˆˆH+¤>Q)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD ¤ 1‚XDDÄ@ b)ˆEDD atÂÝñb'Ù›‹øâhWÍe3ôOŽä±kèk1¦‚""Ò¢©EìGöæSì=rv8]ðY~ 65ÅDD¤UPûñEA•ßuöåW±fw)vw3ÔHDDZuMûáp®¯ì(á•% *;!ÆÌؾÑLMÅj15¦z""Ò©El "›‹u{ÊXñQÃ\DDZµˆƒ(.ÚÌðÞ:Å™1›ý·p].7Ç‹Ýìüº‚i#ãš¡†""nÄA`6ÁyIRS"ˆŠ¼‹Ùl6Ñ5Á„z¥EDÚ.±±Q&F¤Xéo&€Fn£tŒ NÁ[¶laãÆ:tˆ’’:wḭ̂aØ1c111AÙG°¥§§3qâDÆotUDD ¡ ö#íÂÓ!J&SÓ‚Øív3gξúê+¦OŸNJJ ‘‘‘=z”½{÷Ò®]» ÕTDD‚MAìGǸðÏö׿þ•C‡ñÖ[oé™ßµkWl`ÍDDıMl¬6‹åË—3kÖ¬!\›ÝngñâÅ|øá‡Ò¹sgî¹ç®½öZÏ:éééŒ5Šýë_ü¿ÿ÷ÿèÞ½;¿ÿýïùüóÏY¿~=ß}÷ƒ âñÇ'11ÑSîÒ¥Kùßÿý_*++¹âŠ+xøá‡=]áþ–{+--eêÔ©Œ;–o¾ù†èèhžxâ Ïò•+Wòù矓¬NDÄpáßÜ“zSXXHß¾}ë]ÏjµÒ­[7²²²xûí·¹þúë™;w.yyyžuŠŠŠøË_þÂï~÷;Þ}÷]ºwïNzz:ùùù,\¸×_£Gòâ‹/z¶Y´hÇŽcÕªU¼ñưxñ‗Ws:<öØc¤¤¤pß}÷1vìXvìØÛýóyÜÛ·o窫®jÊÇ%"vÄ-\aa!ÑÑÑžyË–-cРAžÇÿ÷póÍ7Ó«W/’““¹ûî»éÒ¥ û÷ï÷l—@ff&ýúõ#))‰;ï¼€{îݻӽ{wn¸áöíÛ@YYëׯgÖ¬Y$%%ѱcGf̘Á¶mÛZ^Íív³páBl6›§üðCú÷ïOnn.C‡åœsÎiêG&"VÄ-\LL ]»våÃ?dÀ€DFFzÍjµP^^Δ)S3f Ó§O§sçÎÜ{ï½8ÎíÏ{„wll,«W¯&>>þ¬uý-¯vùå—óí·ß²víZî¿ÿ~Ïü«®ºŠ—_~™ßþö·lß¾±cÇ6¨®""-º¦[»ï¾›·Þz‹o¿ý¶Îu2ŽÝyvv} 'KìA-»S¬™Á=£¸üühÿ+‹ˆH«¤ ÀàV÷°] i…Ô'*""b ±ˆˆˆÄ"""R‹ˆˆHA,""b ±ˆˆˆÄ"""R‹ˆˆHA,""b ±ˆˆˆÄ"""R‹ˆˆHA,""b ±ˆˆˆÄ"""R‹ˆˆHA,""b ±ˆˆˆÄ"""R‹ˆˆHA,""b ±ˆˆˆ"Œ®@¸;^ì${s_­Â᪹Ìb†þɑ˯bÁ¦¢æ¯˜ˆˆ´ b?¾(¨ò»Î¾ü*Öì.¥Âîn†‰ˆHk¢®i?ÎÀÂõ•%¼²£¤Ae'ĘÛ7š©©±X-¦ÆTODDZ8µˆ Tds±nO+>jX€‹ˆHë¡qÅE›ÞÛB§83f³ÿ®Ëåæx±›_W0md\3ÔPDD‚8Ì&8/ÉBjJQw1›Í&º&˜P¯´ˆHÛ¥ ö#6ÊĈ+â?Ùi IDATÍÐÈm”ŽqM+833“¯¿þš·Þz “éì²þóŸÿpûí·³lÙ2.»ì²&í à–[ná׿þ5S¦LirY""mŽû‘v¡•. ¡ aÀgx6Ô¡C‡øðÃ}.[±bE“Ë÷6jÔ(.ºè¢ –)"ÒV)ˆýè×2>¢ØØX^{íµ³æ9r„O?ý”sÎ9'hûºÿþû:thÐÊiËZFÊ(Õfqã7zB×ÛÊ•+¹õÖ[i×®]ùãÆc÷îÝžéƒ2bÄÏôÎ;IOO'55• &°dÉϲôôt6lØP£¼7ß|“›o¾™1cÆðÐCqìØ±`¾=‘VKLj[‰víÚ1yòdV¬Xá9\XXȶmÛx÷ÝwY»vmÀe“‘‘ÁÃ?ÌÕW_ÍÉ“'©¬¬¬sýU«V±iÓ&ž~úiÎ=÷\öîÝKbbb“ß“ˆH[  nE&MšÄªU«ø÷¿ÿÍE]ÄêÕ«¹á†ˆ‹kØ©Q'OžÄn·“ššJ\\\½Û;N–/_ÎâÅ‹éÛ·/pºµ-""Q×tíÉs°ò£JV|XÁÊ*ùô[G³í»}ûöÜ|óͼöÚkó÷¿ÿÉ“'7¸œ .¸€Aƒ1iÒ$233kta×VPP€Íf£_¿~M©ºˆH›¥ ²/:¹uH$w§µãÖ!‘|QàlÖýßvÛmìܹ“ 0fÌ:tèÐà2Ìf3K—.åOúV«•ŒŒ fÍšåsÝÒÒR\.wÅ¿ÄAær¹=õˆŠ0ár5ï ’’’øÍo~CNNéééu®ÍáÇkÌ«¦ä©§žbùòål߾³ÊINNÆårqðàÁ༑6FA$?¹8xÌQçtsš6mË–-£{÷îu®sÉ%—°aþûî; ˜7ov»8=XkãÆRQQÁž={ˆŽŽ&!!á¬râãã=z4 , //›ÍFnn.6›-dïOD¤5Ñ`­ ((rñÑÿ³Ó1Öäsº¹ÅÇÇû½‚Öƒ>Èܹs¹å–[HNNfܸqäççPRRÂÖ­[Y²d eeeôèу 鳬?üádggsï½÷b·ÛIII¡OŸ>ÄÄÄý½‰ˆ´6¡J åúZnòš_ýlö1í½ŽÙk»êéê‡åÌsÄ™×ÕÏñn·{g oäO~ò»Î'Š]\Ò=‚|^ůºGÔ˜¾}X;¿eÜ~¹N÷1™LÃS€px=»Î¼vy=Ü^êi¼¦ñšW{ÚíõÚ›¿ã‰A?Þ¨®é èÕÉ‚­ 6}þÿÛ»÷8§Ê{ßãß\f†ææÀ@QD@¼´ X.‚c­uƒà.t`ƒÇRËPÁÚ*tLA¬žŠ‘ª`7‚§r)la‹¨U õ¨ˆ€Ãu†¹åzþ€ÄL&™d2IV2óy¿^y%k­'+¿Íwžg=kÅ®¯H©¹Ü1Åèò Œ¡é(H³štÇõ5‡mý—„1"ˆ0AB9b“ÃUs›Å,uo›ªG~”«ü,‹1’=âŠ7žÓÞ¯k‡°$9]ÒLJm*Z6þ…‚8„OŽÚB¶ùûa›^ÿð¼ªìî8ThLšÁá /\ÿð~™þð~Y½ö›aÖ®éšÐ/K)S$å’=b­piÕîr½²£~h<èGQvºY}:YÔ:Û,³9t×årëX©[;¿¨Ò}7gÇ¡B@¢!ˆ£Àl’.˳¨_g«Ò¬á1›Í&µÉ5‰Qihºâ²ÒLêÛ9E­sÌ £“‘VÙ Ûñ£>ª 6H’¬V«Ú·o¯»îºK£G–Ù|áèÃèÑ£uûí·küøñ!÷g³Ùär¹Ô¬Y³Õ ¡ÿÕB8–L¦†'üÈ‘#5yòdUVVêƒ>Ђ TYY© &H’  .]º„µ¯—_~YGŽÑܹs\ nq­²“c>[jjª233•™™©¡C‡ª¬¬LK—.õñý÷ßop…€@’#e …Ϊ!n¸á={V’¤ÂÂB½ýöÛ5Ú¬\¹Rwß}· ¤©S§ê›o¾ÑòåËõòË/kÆ êÝ»·ŠŠŠ$I·Ýv›>üðCïs8 ¾}ûz— µqãF=ðÀ0`€víÚ%I²ÛíZ¸p¡î¼óN 2DsçÎõÖ$I;wîTaa¡úõë§Ÿüä'Z¼xqÌ>HDôˆ©“'OªeË–ÊÈȸ}ùòåZ¿~½æÌ™£-ZhïÞ½ºä’KTXX¨òòòˆ†¦gÏž­Y³fiòäÉjß¾½$iþüù:w/_.‡Ã¡Ç\‹-ÒÌ™3UZZª3fhÚ´i*((PII‰ª««üÞ ™ÄŒÓéÔ矮çŸ^?ûÙÏ‚¶Yºt©-Z¤®]»JºÐãm¨áÇkĈÞåòòr­Y³Fï¼óŽòòò$I“&MÒÌ™35sæL•””Èn·«_¿~ÊÎÎVv6§phzâ((­tiÓ>‡J+k^:;ݬ!פ(;=öãÛ+W®ÔÊ•+e2™Ô¦M3F#GŽ ØöèÑ£ª¨¨P·nÝ¢ZÃu×]WcùСCr¹\š8q¢wBšÃáPyy¹$©cÇŽêÙ³§F¥þýûkøðáêÕ«WTk€DGGÁW']ºì³z÷L­±þƒ/úê¤S×µ‹ýÇÆD0A €b D` ‚Ĉ À@1"ˆ0A €b D` ‚Ĉ À@1"ˆ0A €b D` ‚ÄÈjt‰îX©SÅÏê“#69\5·YÌR÷¶©zäG¹ÊϲS ©Ñ#¡xã9íýºvK’Ó%}|ئ¢õgã_ Q ˆCøä¨-d›¿¶éõÏ«ÊîŽCE€Æ„¡éÎðÂõï—éï—Õkß¹f éš® ý²”b1ER ÉÑ#6ÐÙ —Ví.×+;êà€Æƒqe§›Õ§“E­³Í2›C÷p].·Ž•ºµó‹*Ýwsv*$‚8 Ì&é²<‹úu¶*Íþ³ÙlR›\“•€¦‹ !+ͤ¾SÔ:Ǭ0:¹i•ݰ?úè£Ú°aƒ$Éjµª}ûöºë®»4zôh™Í ;ú0jÔ( :T?ûÙÏ´I²Ùlr¹\jÖ¬Yƒ÷ABÿ«/„p,™L Oø‘#Gjòäɪ¬¬Ô|  ¨²²R&LhÐ~  «¯¾ºÁõIÒË/¿¬#GŽhîܹQÙ4q­²“c>[jjª233•™™©¡C‡ª¬¬LK—.mpÿò—¿ŒR…€@’#e …Ϊ!n¸á={V’¤ÂÂBmܸQ<ð€  ]»vI’\.—^yå1B·Ür‹¦Nªo¾ùÆ»ŸÂÂB½ýöÛÞe»Ý®… êÎ;ïÔ!C4wî\ïkx¬\¹Rwß}· äÝßòåËõòË/kÆ êÝ»·ŠŠŠ$I;wîTaa¡úõë§Ÿüä'Z¼xq¬?H(ôˆ©“'OªeË–ÊÈÈð®›={¶fÍš¥É“'«}ûö’¤—^zIï¿ÿ¾ŠŠŠ”žž®… jÒ¤Izë­·”’’Rk¿óçÏ×¹sç´|ùr9=þøãZ´h‘fΜ)IZ¾|¹Ö¯_¯9sæ¨E‹Ú»w¯.¹äª¼¼¼ÆÐtii©f̘¡iÓ¦©  @%%%ª®®ŽÃ§‰ƒq#ãt:õ¯ýKÏ?ÿ|­ VÇ׈#Ô¹sg5kÖL6›M+V¬ÐìÙ³Õ©S'µmÛVO<ñ„ÊË˵}ûöZû.//ך5kôÐC)//O­ZµÒ¤I“´uëVïk/]ºT³fÍR×®]ÕªU+ÝvÛmJOOXkII‰ìv»úõë§ììl]yå•êÖ­[Ô?Hdôˆ‰•+WjåÊ•2™LjӦƌ£‘#GÖhsÝu×ÕX>zô¨‡ºtéâ]—žž®k¯½V_}õU­×8tè\.—&Nœè`æp8T^^îÝ_EEEØaÚ±cGõìÙS£FRÿþý5|øpõêÕ«>o’AÜH 6L>ø RSS•ššÖs<§9Χ9ÛGVV–$iÅŠÊÉÉ©µýüùó’.w¶XBÿ•ÙlÖ’%K´{÷n­[·N3fÌPÏž=U\\VýÐ04ÝHxfL‡Â’Ô®];™L&íÝ»×»ÎåriÿþýêÔ©S­ö—^z©š7o®mÛ¶Ü_Û¶mår¹tàÀ ¯ép8j­ëÑ£‡üq-]ºTÛ¶mÓ‰'Â~ìâ&,##CÆ ÓÓO?­ýû÷ëôéÓZ°`òòòtÓM7ÕjoµZUXX¨E‹i×®]r::~ü¸öïß/IÊÉÉÑ-·Ü¢¢¢":tHÚ¾}»wVuëÖ­µgÏ}ûí·ª¬¬Tii©Ö®]«'N¨ªªJ»wïVzzºrssãú9€‘šnâf̘¡çž{NÓ§O—ÃáPïÞ½µhÑ¢ ™0a‚ÒÒÒ´`Á;vLyyy7nœ÷¸ðc=¦ââbÝ{ï½²Ûíêܹ³®ºê*eddèŽ;îÐŽ;4zôhõèÑCÓ§O×–-[´xñb•——«C‡***ªW¯’]¬Î’ µß@ÛM>ë=÷æË¾mÌ>Ïó,{n–‹÷Ö‹=÷9n·{g¸o䵿 ·iƒŒ¹ñ’¸¼N};V£GÖСC.@`2™úH:'É)ÉásïºøØåssûÜ<ËòY–Ï:ÿe·Ïc_¡~û6ê?<ÏÐ4‚²Ùl:xð Ú¶mkt)ÐhÄêÕW_UÇŽuà 7] 4Z1r:úç?ÿ©^x¡Á¿àŽÉZÈb±hþüùF—]\Q?,_›Û‡$$‚8„ [ì~©ºö5.MAŠË,‹%E±è´ºÝnYLnYøg€&‹cÄ!üüæl}xÈ®]_H%eö¨î»u–Y½®HÓ—þu"@ãG‡¡W‡õêPû·yh(ÆD0A €b D` ‚Ĉ À@1"ˆ0A €b D` ‚Ĉ À@1"ˆ0A €b D` ‚ÄÈjt‰îX©SÅÏê“#69\5·YÌR÷¶©zäG¹ÊϲS ©Ñ#¡xã9íýºvK’Ó%}|ئ¢õgã_ Q ˆCøä¨-d›¿¶éõÏ«ÊîŽCE€Æ„¡éÎðÂõï—éï—Õkß¹f éš® ý²”b1ER ÉÑ#6ÐÙ —Ví.×+;êà€Æƒqe§›Õ§“E­³Í2›C÷p].·Ž•ºµó‹*Ýwsv*$‚8 Ì&é²<‹úu¶*Íþ³ÙlR›\“•€¦‹ !+ͤ¾SÔ:Ǭ0:¹i•ùŽ'Ož¬œœÍ;·Ö¶Y³fé{ßûž¦L™¢Ñ£GëöÛo×øñãîÇf³©OŸ>Z³f.»ì²ˆëÔLjCèuŠ.Í]K’ÉùÎ ôþûïËf«9»Ûf³iÇŽ2dˆ$iÀ€êÒ¥KƒêDAB«ìÄþˆ¨êêjýío«±þƒ>Pnn®ºuë&IºÿþûuÓM7Q"  M‡Ð€Îj\dgg«wïÞzï½÷Ô·o_ïú-[¶hðàÁÞåÂÂB1BÇ—$9Nýþ÷¿×úõëåt:Õ¯_¿ûµÛíZ²d‰6mÚ¤êêj 8PÓ¦MSFF†$Éåréü£Þyç>}Z×_½~øaµiÓF’´sçNýþ÷¿×—_~©–-[jðàÁšú¨:wî¬víÚéÑGõn///ך5kôÐC)//O­ZµÒ¤I“´uëVïóW¬X¡Ù³g«S§NjÛ¶­žxâ •——kûöí*))‘ÝnW¿~ý”­+¯¼Ò;D¨‰¡éF 33S?üáõÞ{ïéûßÿ¾6oÞ¬‚‚‚ í9"§ÓYcò–ÙüÝßd‡’ËåÒĉ½É‡ÊËË%IG•Ãá¨ñüôôt]{íµúꫯtË-·¨gÏž5j”ú÷ï¯áÇ«W¯^Ñ~ÛÐ(ÄQ´ûCŸqÊåºpYL³Ù¤kÛZôƒËcÿ19êŒK  ÐéÓ§µjÕ*edd¨sçÎAÛ¶k×N&“I{÷î ¸ýÒK/UóæÍµmÛ¶°Ÿïr¹´ÿ~uêÔÉ»®GzüñǵtéRmÛ¶M'NœˆðÝ@ãEGÁ©ó.­Ýk“Ëå®qe­4«ÉÛ;޵ŒŒ õéÓGóçϯs’–§íwÜ¡§žzJûöíÓÉ“'µpáBïv«ÕªÂÂB-Z´H»ví’ÓéÔñãǵÿ~ïó‡ ¦§Ÿ~Zû÷ï×éÓ§µ`Áååå馛nRii©Ö®]«'N¨ªªJ»wïVzzºrsscú@2bh:Já4§!C†è½÷Þ Ä’ôðÃëw¿û¦M›¦ììl :Ô{ê‘$M˜0AiiiZ°`Ž;¦¼¼<7Î;éjÆŒzî¹ç4}út9õîÝ[‹-’ÉdRYY™¶l٢ŋ«¼¼\:tPQQ‘RSScöÞ YÅ*>Bí7Ðv“ÏzϽ9À²o³Ïó<Ëž›åâ½õâcÏ}ŽÛíÞîyíogB¶Y»×¦[º¥èÏÙtOïTo¯¸ÚáÖë»l×7-ä>ÆÜxI¸%@£e2™úH:'É)ÉásïºøØåssûÜ<ËòY–Ï:ÿe·Ïc_¡†1£>ÌI8 †}ÿBOïÚ¶½¾ËVk²ÁÄQôƒË­q™! h<˜¬€b D‡³ÜîøœâH<q¶ØŸ—TíˆùKAŠË,‹%E±è´ºÝnYLnYøg€&‹)¾!üüæl}xÈ®]_H%eö¨î»u–Y½®HÓ—§Gu¿€äA‡¡W‡õêbt€Fˆ1Q D` ‚Ĉ À@1"ˆ0A €b D` &y‰Ë¶Éß7„ÍjŽý¯Ý%"zĈ À@1"ˆ0A €b D` ‚Ĉ À@Mò—‘ÈLuëÒ,§¬õüÓ¥¬¢B%•ªvñQj£G¦ïEÂ’dr9•é<®4‹3úE’A¦”|R.—S™ÎcJ3Û£W Q ˆãÄåt*Óu‚0Ô@Ça ðÇ ¢œ8qL.—«Î6f³Y­Z}OòûyM—Ó©LÌ­TíJ‰a•€d@G U«ï…ݶYZ3•Ÿ?/·Ûí]G<âÔ§Glµ¦¨E‹|UWWÕj“aªÒár‚š2‚8õéK’Õj•Õšp›©\rÜ’¼~=m‚Ý~— èùv›Mn·K©iÍbÒ Az#F½½ýæ2ÿöˆ~9cNLÚ@"!ˆ#@À¢… ŽÀÉ“ÇåpDv¥,«Õ¢–-[Gµžìþ@«W¼¤£‡¿Rî%-Ô«ß ÷ 9v½±ìíùð}9U¢ù­4|ô¨ß-?ò>÷×Ó&è7ݬOö| C?×¥mÛiÂý3õÙ?ÿ¡­ש丷ºæºº÷?g)+'×ûœþƒïЇïoÑW_~¦6—uиI銫º¬Ïá°kÕòõÁû›e·U«ÇM7k̽SÔ¬Yz­¶ï¼µBkÞX&IúpÇ{ºå¶áÊνDíܦß>ûŠ,‹ªª*õð/îÑÈ1÷©ôÜ™ZíÇÿbzT?_ˆ%‚8ÑÒ†(?_¦gæ=¢±ÐM7Ñ™S%²Ùª%IVkŠZ·¹LÓ}Z©iÍôÁÿU/=û¤®ìÜM—¶m/I*+=§Õ+^Ҝ߽¬ù­µâÿ,ÔcNPÁ#5eÖ<ÉíÖ³OÍÖË_нÿ9Ëûºü}±+zA­Z·Ñ;~MOÿê=»ô¿”Ѽö±ðå/>£²Ò³šó»—åt8ôâ³OêõWž˜wŽ«ªÊŠC͇]»þ{³6¬}S?¾ëßõç•PûË;éæÁwHR­öLâ$RøÌ©9ìvÝpc_5ÏÌRó̬Ûÿøß¼‡Ý]¨m×é‹Ïö{ƒ8+;GÃFz{³wüÛOõ?Û7iøèñÊÉÍ“$ |‡ÞÛ°¶Æ~ïýÏGtu·ë%Icï}@ÿÛNýÏöMºõö»j´«¬(×{ÞÖs¯¬ñîoäØûôÜS³Ãî¹Z­)šøŸèÿæAµ¹¬ƒþ{ó_4ï¹åá~DÐâ$R¸M»ËÕíºšyÿOõý^}5°`˜®¹¾§w{YéYíܺQÿÚÿ±ÊÏ—éø±£²ùJe2}wµ¶í:J’ª++¥ÜïÖUUUø½òwW*1™ÌêÜõZ;z¸V}ÇŽ–ÛíÖÿ\¦‹Ïq8ª¬(¯×ûìÜõ:õ0D æLפié’ùõz>$*‚8‰Ô#6›Íšõäs:ðÉmßü=;o–º]ûMûU‘ª«*õøCÿK7ö¨ûé½j™ßZsfNªsÆ·Éd ´2d&³Y©iiµÖgd^ª~òÙW”™•þ ûb:-À±eHVq©GìÑõÚ¨ëµ?ÐwýTL«3§JtüÛ#*+=«{ÆÿÒÛî냟ëTÉñ½Ö™3§´eýšç ùÿhøÝãjµm™ÿ=5KÏО]ï{é†ÃápÔXþöè×úëÿý³þý?~©?þ¾X×\ß³Æñhÿö,â$Rø|Y©öìúo]ûý^jž™¥ŸîUZ³fÊÌÊQee…**ʵù/Vïþ·jßÇ»%Iù­/mÐk¦¥¥éËÿw@ûÿ±[í;vÒP¾Ç(÷IDATÆwV«ª²B7ö(IÊÌÎUÉñoäv»e±Zu爱z}ÙóÊk™¯®×öÐÙ3'UzæŒ:^ÕE’´pÞ,]yõ5ºsÄXIR^ËÖÚ²~Nž8¦¬ì¥¤¦éÅg~«;GŒÑÿ6F>Ý«•//Ò½Ì Øž3€dBG ‘zÄåçõáÎ÷ôú²%ª¬,×¥mÛkÊ#ó”’šª6—uÐ=ãï×[+ÿ U+^Òu?¸I=8 Á¯™‘‘©›úߪW–Ì×Ù3§tU—îzô©çeµ^¸\çí?¹GKŸ/Ò‘¯jÚ£Okø¨qJMMÓò—žÕ©’cÊÉÍÓ#ƪãU]äv»Trü[åµlåÝ¿A?ÒÇíÔ#¿£®×þ@W_s½ª««tûOþ]’4~Òt=òË1ê3°@Ý®ëQ«ýC¿žßà÷ñúà_löh»Ég½çÞ`Ù·ÙçyžeÏÍrñÞzñ±ç>ç㯫w:\õ»°d×üï†>£Ù#þg‰5é.qÙÐKX@ V³I×·Oë#éœ$§$‡Ï½ëâc—ÏÍísó,ËgY>ëü—Ý |…áP_ÉQÿʦGDê’AD:F HnqR@´Ähê=â9Ï,5ºh4â${‡9t+q˜ìÁ¯ 1‡+óàI… Ó±2KTÃØî”¾)³Do‡€¤Ä1â0·™ôù)>.@tÑ#À@1"ˆ0A €b D` ‚Ĉ À@1j’×l¼®]ªÑ% ‰1†"ˆ0A €b D` ‚Ĉ À@1"ˆ0A €b D` ‚Ĉ À@1"ˆ0A €b D` ‚Ĉ À@1"ˆ0A €b D` ‚Ĉ À@1"ˆ0A €b D` ‚Ĉ À@1"ˆ0A €b D` ‚Ĉ À@1"ˆ0A €b D` ‚ÄÈjtF8|ø°\.—Ñe|˜ÍM³oØ$ƒ¸]»vF—€$†¦0A €b D` ‚5ÉYÓ¡ØívUUUÉf³Éív]Ž$éÔ©SjÑ¢…Ñex™L&¥¦¦ªY³fJII1ºœ„WYY©êêj9ޏ¼žÕjUZZšÒÓÓÃ~5ÖFÑIM Aì§¢¢BF—‘ðÜn·ª««U]]­ŒŒ edd]RBr:*++‹Ûž‡ÃáÃáPuuµ²²²d±X‚¶¥Æà¨1:êScSÄдªª*B8ªªª2ºŒ„dÄ—ž/‡Ã¡²²²:ÛPchÔáÔØÄ>ÊËË.!iñÙÕVYYiè—ž‡ÃáPeeeÀmÔ>jŒŽºjlªâ‹éxp2Ⳬ­ººÚ輂ÕBõCÑ‘Hµ$‚ø¢DøKK"ý7¬j¬jŒŽDª%Äql`‚ĈóˆÀápè•W^Ñ_ÿúW:u*¬ç´hÑB7nœ¬ÖØ~ü‡C¯¾úª¶lÙ¢Ó§O‡]ß Aƒ4vìØ˜×‡ÀŽ;¦ßýîwúä“Od·Û%I)))ºþúëõÐC)??ßà ëf·ÛuúôiµjÕJ&“ÉèrJ¨ïŒx~?Ô%œï¶Î;kÞ¼yÊÍÍsu=âX¶l™Þxã°CXºp…¬?ýéOúãÿÃÊ.xõÕWµzõê°CXºPߪU«ôÚk¯Å°2Ô¥¸¸X{öìñ†°t!Ü>úè#=óÌ3VÚŽ;t÷Ýwk̘1ºï¾ûtôèQ£KªÅf³iòäÉš6mZÜ_;ÔwF<¿êÎwÛgŸ}¦3fèìÙ³q¬¬q¢ËÓ›6m’$=÷ÜsêÖ­[XÏÙ¶m›~ûÛßjãÆš8qb,ËÓ{ï½'IZ°`ºvíÖs8 éÓ§kóæÍ7n\,ËCŸ~ú©$é¿þë¿”••%I:}ú´F¥üãF–V§­[·ê©§ž’Óé”$M™2%.ßu UçÙ³g5cÆ ø ,X ¼¼¼x–Ùh04'‡CsæÌÑÁƒ. ¦!_ºFaK5C¸OŸ>5¶effê’K.‰K›6mÒÀ5{ölÍž=[ƒ ÒÆ½Û«««5þ|¹\.=Z]ºt‰K]õõì³Ï&ü!IÊÉÉÑüùóÕ±cG}ýõ×úÕ¯~etII‹q¸Ýnk÷îÝjÑ¢½MÔàéÑzzºž¡Õ`Âm¾!ü‹_üB#FŒÐo~óíØ±C‹EÓ§OWjjª!µY,–=ô´´4-[¶ÌZê’,¸ÉÍÍÕüùó5räH}öÙgF—“´ë_µ‘Z¶l™6mÚ¤ôôtÍ;W“&M2º$$-[¶hРAµ'ŠmÛ¶yCøÞ{ïÕˆ#ôÖ[oiÇŽ2›Ízøá‡Õ¯_¿¸ÕSPP ×_]Ò…?~·nÝ*·Û]c(?#‡ø=£‚ñLà’dèqc… êܹsºûî»ÕµkWŽ GAcï¾û®^{í5™ÍfÍž=[:u2º$$‰yóæyÃ×÷q"ضm›æÍ›ç á{î¹Go½õ–^xá™ÍfÍœ9S·Þzk\kòL.ܸqc½GŒâO† \v»Ýû»ãƒÖÔ©Sµk×.=ÿüóºüòË ©©1!ˆ£dêÔ©²Ùl5Ϋû裼eO:U?üá,ˆŠD aéÂÏOœ8±VX=÷ÜsZ»v­:uê¤Å‹:¼ë94µoß>]sÍ5’›ÀåÛ.Þ>ÿüs­ZµJ³fÍòÖÓ¥K8p@[·nÕøñã ©«1a²V”Øl¶çÕ}ñÅš3gŽœN§ÆŒ£ÿøÇF—4X¢†p0{÷îÕºuë”’’¢‡~Øðc¬’.Ì”Ÿ:uª¡µ„k×®]Ú²e‹öïß/—Ë¥5kÖè_ÿú—$©Y³fW×8Ð#Ž’yóæy§òOŸ>]eeeª¨¨ÐàÁƒù‹ÂŽ;¼!왘å a‹Å¢Y³fyOJ*..–Ûí–ÙlÖæÍ›Õ¡CY,Ãjò>O„ïá°Z­r»Ýš={¶RRRtæÌI®öÖ¿ƒ«kâ(ÉÍÍUqq±7Œ%y/I˜,ÿÃuY¼x±œN§úöí›ð!,]˜uüøqï±õ7ß|Sn·[÷ÝwŸa5>OdƒÖš5kTRRRc½Ùl–ÙÌ j4Ä*ˆÝ’š\úxΫ›={¶L&“žxâ ï ¾fÏžð±Q<_Ä;vìЯýkíܹ3aCXº0 jРAÞc›&“I›6m24ˆ“Q‹-ôüóÏëÝwßÕ‰'´iÓ&Ùl6UWWë™gžQQQQB\„$NܱØi²÷ˆ ü@/rssµdÉ’ Ïñ\A©eË–q«ïÀa_]kÿþý’âSjòü{}úé§êÞ½{YҞǟ|ò‰·­‘vîÜé=E)CX’RSSåv÷½év» –&Ð÷ˆ¯x~g«)//OcÆŒ‘$5oÞ\o¾ù¦$iÏž=Ú°aC"\Ý-&/Fq£è1èOú“¦L™ÑscmРAZµj•¦OŸ^ïç&Ò„›¦bðàÁzã7šÄÿ~ê’ŸŸ¯É“'«oß¾†ÖQßóŠ¥ ×^¿çž{ ¬(°p¿Gâùo¬¦îÝ»ëÉ'ŸÔúõëUZZ*IzñÅÕ«W¯ÆryKC=ÙzÄ à‘œ·Ø²eK¨°°0–¥I’ÆŽ+éÂÅ êSß­·ÞªŸþô§±, x&õÕõÓs-Z´Ð!C ùAŽD¸’W}øOŒºçž{ò‡LB}Äó;#TM&“I™™™3fŒ^xáIRYY™-Z¤ßüæ7q«/ IÕCŽe¨…Ú·ÿv“Ï:“ß:ße³ß6s€Ç–‹7³.ü±añ¹Ïq»Ý;ý‹9yòd8ïÉ0§N2|82†³kJ´ÿ¦ýûPcýQctû¾0™L}$“ä”äð¹w]|ìÔ… u]¼÷,Ÿeù¬÷]–ß:ùm &&Ï”7 D` ‚ø¢ŒŒ £K4A‰Äѯs?n·Ûå¿ÎèKß¡ñI¤ÿ¦‚ÕBõCѬ–@ßÍþM¢TBBMæJ´ G]×mó?0ït»Ý¥þ;MMMm*'¤ÇŸ]miiiF—à¬j¬jŒŽ`µ\ünöLÆ wRU}'\%œD â@³Û<÷fº¹|î=7I²WTTì ô¢Í›7Þ;hbøìjKOOOˆ^ˆÕjUzzzÀmÔ>jŒŽºj¼øÝl¿¸èûÝí?3:ØMºO(Fq¸H¤\ ¿˜\’›6mz3К5kƱâdddð+,AdeeúågµZ•••Ugj £#T¿›=§*Iu÷|Ãë¬i0#Ï#öoèb³Ÿ[è\bϲYßOì{±UR¦ÝnËjµ^¨(»Ý®ªª*Ùl¶—È3R¢Gl2™”ššªfÍšq=í0TVVªººZ‡#.¯gµZ•––´ç5ÖFÑN‡ãHJJÊIçUóüa§ÏÍ·‡èâ@½cßãÎuõÃù²I $R{–ƒ±ïvÿ›§ÉïÞªïÙ7ˆSW¯^=pĈ†_  ½õÖ[SG޹U’MµƒØ¥ïzÊþ‡¥º‡ªƒq$Ç–“.ˆÃÙ  to°èŠ[þ÷uõŠÓKJJ~ݲeË¡õx?€(;yòäºüüü9’*^oØÿ^ªÙC–jq¨ãņ\UKJÜÉZþ‚}`>˜º&nù.WåççÏ-++«u¹K@|”••íÌÏÏŸ+©Jõvöi¸&£ƒ¸!R°S•꺾¨ï_U.I•ÙÙÙÔPµÿñbÏ:X_×ã o#Œ6‰Çå¢5a+ÐO"û™Dßö´èÞÿ±Õçæ¿/.áñHOÈzn¾£“þýï]öh¨º®áì@÷þµ†z/1aüeV.¼ÁpÎÓÎ÷^~ë|Ûºt!@=C½†ïP¶CÁƒZ°  tÕÃ`á븾ƒ½†ÿºúÔi˜Dâ`êОǞö,{xþ¢2éÂ?´g»E5ÿ#1é»àöíaK1„Ë7 )šœå?—'ÐÄ,ÿ@NšÀ &ƒ8PïÖ³ìÛËõï û?öð´óüãJ5‡­å·ÝÿJ]N14 õè8­(û÷Šý{ÂuÍ”ôØÿ5ýÛZ6\<‚8ÜžmCö/ÕaO{ÂÛ÷Þ÷¹n¿vþ=bßapßרjˆ:ÐcßçKÃ8ÖÁóàŽW¨Dsö´zµï:ÿY×fÕœ„U׌kÿ}‡û^ ) 6”\×Ìgÿ0–jö¦ƒÍЧ7œ³¥=qhZЬl¸Zú®—ëé»UsˆÛ7¤é@ÃÔÕ#öl÷ïñú‡­ÿ¶ÿ¾# È„––«Gìß.’^q]§7:9عȾíüû×øŽع<öfÿcÂuý˜C çǪ7\Ÿv‹W8–lj}{Â&my¸ü¶ûöŠ=íÍúnfu°ýø¾4uÁ¾']u,‡š”l’V Ž…¸ô uhZ <#ÚwïiJþÏñŸ°¥Ë†°=m\u,‡B8hìêPuM° 4ü*”ƒ½~}zà %ž¡­áiÿÇN- 4l]×úP³WˆL P 4É*Ø0s8ÃÏÁNWJøaiéÂÅ,⩾³§ý—ƒ£ÿzw€uþ=ê`µú‚ €ÈúiC©î^k}B8XX6´7·t¼&’^±ïr¨^q°õáLö öØ$‚"lH¹®ö]ª×ª7ì¨ Õ–³G¨]¨°ôïµÖÕ3ö<öÿÃí!B 6ÑÊ¿:¬# á¤`DO/Va,…î{n ^o°×¨«Fhª""§·ÜNøÞ°”ØA¨m¸¹êÔÁ&o«£¾lɪ¡¡TW/Ø{¸AÉ„¬úÎôŽ£"Ú½â@Û" ê@Ûµ„§®^p 6¡‚6ܰMŠÞ°”øA¨m¤a\ßíuµ„'Pð*ÀºpB6Ö!\ß¶QadÀÄ#Œ=Ë‘œUŸu€ï„¼¾ëêÀÁÚ„³-C&yÅ{Ö´¿X…q¸ë„éP:4V‘õÖ÷˜n¨ÙÐIÂ’ñÁÑ ö_WŸíuµ¥ ÑnO8Pû†„p°uÁ4Ù –ÆþëȾÛáó€Æ رbßí¡‡»½®õ mu‰4õ­!Ò0Õ&T(‡ÓþÅ5ês¬7Ò6áhòA,E/Œ}·E#X£Ñ[€Æ*Z½Îúw}Â=ï•Há‹0w¿¡Â;‘>'H&¡z­áa¤=ìúì×0‰0‘Ô‹!eŽ@ô…:N¨} Çuµ‹d߆J´°‰´žhôzcõ\hŠêÓãmÈs# T‚¸± cß¶Ñ|ï‰ø9@€=sp5æ÷MQ£ __M!¬šÂ{€Æ¬Q°GS ©¦ö~ Y5êðõÕÔƒ©©¿HM&xýD5ñy@|4ÙàõGð„‡Ï "Cà†@ÀDŸ)€ÆŽpD×ÿ÷Ý|auпIEND®B`‚Portfolio-1.0.2/screenshots/en/4.png000066400000000000000000001143161476103320100173050ustar00rootroot00000000000000‰PNG  IHDRâ6*Ù€³sBIT|dˆtEXtSoftwaregnome-screenshotï¿>-tEXtCreation TimeFri 09 Feb 2024 04:05:36 PM -03¸° IDATxœìÝyxTõ¡>ðw–Ì–„$Ä$,ILØB a3B@¬Tj—ÖZ\ðZë½Õªå*½÷÷ØÖV%zÛKëu©VQ¹¬‰Ö ²ôF `DTBÀÈb’I&³žùýæ83™Éd’™9“ð~žgž™³Ì9ß3óÎw9g""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""‚LêŒBüL‰h´sH]€Ñ„¡18üœˆˆ††¡íÆ?"¢ð`@_r¹Ïå~üDD‘â² æË-ˆ.·ã%"©.›`¾‚ér8F"¢ÑlT‡òh©Ñ|lDD—£QÈ£-¬FÛñ‘w£&”GKpEâqDb™ˆˆ†#Ã/ËÑá<†Ñðy…C8rD‡ñH–P–]âí]ŽmhŽÈ@‰aŠ2ËC°M""òOÁ6GT ´ VyYã%"Š<Á¬1˜0Ia4ܲÊ<žÃ¹o"¢ËÕPÑáñÎ}‡ÝH˜á”q¨á;ØõGÂçGDƒ ½@Ã1¡ÑéA2ÔòÀþÖc2ÑÐøë4À‡ªÆ‘Äà aïh¹ÌÇk"":‡×­çkù¨ ãH šáôã6d=çå}DD40¡ëkù`Âz8ýÏ#f¨!h†"|#ñó$" …`‡ ¯PjµaiÁ1ÔAUÕt½Íó5ßß~ü­GDDÃk†4‡Z;Ž˜0Ž´P ¤<†°·ö÷Þá\òDDDýƒr°MÑÞ9˜aÌ ö"Ðv{ ÝHú|ˆˆF2oµäÁÔ| 䯑4¡ aÏËŽ|òPš¸ý‰”Ï–ˆ(X‚Ù믦ì9íyÔ¨ ãH‹P„°çzp µåÖ!"¢ÁÕ^ªõô~Ïa H|ÝXc°!ì+€ý-oèAD44þj²¾‚ÖWà6Œù1‰Ë:ˆƒÂróÀÔ¨‰ˆhp¢²àc>|¬ãda,uÀr JMÆ…ð`x þdÖˆ‰ˆ†f À¼Ì÷ÈC ã@š¨/Ë nmx(!ì/€}…q0qF^Z$xyíkdõpÂ8âkÅRI0Cx  õ¶Ì3€½Õ–MÍð%"Ø@—*y Uç´g &ŒÓgÑaéAì¯_ØW˽¬çú,÷ò^_ýÈòÊADDпÖê-D¯ cÀ{ x Q×®åŒË"ˆƒULÀ®ï“Ãû6‰ˆ(0žÁ)À{ x0<œ0ŽØZq$ñ`CØu~ !,÷2ßõµëûÄDDCã¼®MÌú‡®g0&Œ3ÚÚµ,ƒÖ V†sgl”ô`—y aoËäë9Cw  vODDs \ׇÌËk×÷È<^;—;_˽,ƒ—i rÙPÖ Špñpøº.Ø×ó`BXáñ,Cßguiž÷s0v„”àò°°°¡ï¼ê¸4OvéÙ®®ÏÎm8Ï­®Áíës˜G$±g³³ç2_ëûê/öÂJ¸‡põîÝ»üH­VÏ’Édcd2kÃDDÃàp8‡ÃÑe6›¿[ç¬V+¬V+ŒF#Ôj5t:]0ÙÛïÆ{rç+D÷ªö%iXGBØmýÕ–} è(¬‰ˆè“É„îîî€Þc6›a6›F3œÝû:g{ÎlíÖužäµ_oFríp0íZ ¨ˆˆÆ€CØUww7ŒFãpŠàíœ=˜K”Fì¹]ê ö×ìíÙ×·#Ï~oý 퓈è²f2™†¢úÂÜd2 õíÁ8Ç”!íSRñ`¸ðìèèÀý÷ß|Pê¢ Jss3𛛥.IÄn·«&ì©»»;nN4"²#”}ÄCý‚= =ìÿxôÑGQSSƒ©S§†{÷?úÑpúôi€\.GRRæÏŸŸþô§?~¼Ä¥˜Éd‚ÍfƒN§ƒ\Þ÷]Üh4bݺu€={ö@§ÓIYD’@0jÂÞ¶;ÜÍz¾÷×<Ô~âõ/”ñP…=„;;;Åžøà@jjª8^à™gžÁ;#)S¦`áÂ…(--ÅÏ~ö3¼üò˘3gŽøÞ×^{ ééé˜3g8€'Ÿ|cÇŽEaa!àßÿýßqøða"::}ôš››ñÆoˆ}¿žûŸ?¾ÄK—.ÅôéÓ}Ï@Û¿pá}ôQÄÆÆbíÚµ0›Í¸xñ"Cx„òw'¿ßýîwâ9î‘GL&Cuu5’’’ðÛßþvXÛ¦‘ă¹ž,¬{ì1ÔÔÔ ==›7oæ/']fAÀ™3gP\\ ¥R‰ùóç»-w Ø .àÝwßEFFÞxã (•J”””à7¿ù Þxã · €mÛ¶!** }ô6mÚ„íÛ·£°°gΜAYYòòòÄŠÉd¾}ûÐØØèVKñ øë®»}ôî½÷^Ÿƒ ýmÿܹs°Ùl˜3gþíßþ JåH<•“ó¶•¾Œ?EEEb@bb"6oÞŒ¤¤¤am;———÷ þ®®.Ÿû,ÛŸ>}:ÆŒƒþóŸ¸öÚk‘ŸŸ»îº‹W ŒbQQQP©Tâ´J¥r›“`fDÄ„u¤ñˆäÚlóóŸÿœMÓ—‘yóæáŠ+®Àĉqíµ×"##Ãm¹V«u›vþ¼¥ëHR³Ù ™LÖo]W===ú‚€8`檫®ÂÆÝÖMNNö¹ÿÁò·}•J…·ß~Û¶mÃÿýßÿaÏž=Ø·o^{í5dee iŸ$™L6`͵½½>ú(êêêÄ/šõõõxôÑGýžïd2Ö‹üí—/…E\\ŠŠŠžžŽúúz<òÈ#hoo—ºXÏ<ó ~õ«_áÞ{ïíÂÞ¤§§èèä¼ÌéСCp8ýj“ 0 ¨ÊÌÌ·£P(PYY‰„„¤¦¦ŠµgMܹm_åô·ýñãÇãg?ûJJJpÇw@=zÔïg@‘Ç_×Âã?.†pQQŠŠŠ0yòdÔ××ãñÇÖ¶‰5â q†±ë€ÖŒÉSFFòóóqèÐ!Ü~ûí˜6m>þøc¨T*lذ¡ßú?üá‘››‹@&“áÇ?þ1€¾þ¹ïÿûøÛßþ†»îº ×]wZZZÐÓÓãwðÌ”)SÏ=÷/^Œ»ï¾»_xûÛþÙ³g±iÓ&,X°ÑÑÑØ·o€o¿(ÐÈ5àèæªª*dffâ·¿ý­xN+**Âã?îv=º¯mÓÀ"½F<”6 ÉÚAX3¦ÁøÝï~‡›nº ===(++Cvv6þò—¿ô» d̘1˜9s&:„ &à¹çžCvv¶¸üÑGÅý÷ß¹\Ž­[·â«¯¾‚V«õ;8æ–[nÁªU«pîÜ9”””àìÙ³^×hûV«z½»víÂÖ­[!—ËñÈ# ??ø…³Ë×½{÷â…^p«Xèõz¼ð ^C<˜mÁˆÊ…ÁeáümÛ×/%¹.“{™ööûÂÞ~eIqéYyéµó9ÎápòV ÖÖV?EÏ[\>ÿüóCÚŽëe0ty),,ÄØ±cQ\\,uQè2a0‚~w-µZð-.e2Y>€Nv6—gáÒkÁåá¸ôp} —yp™ç9íº><–ù2ªnqÑßN†+>>›7oÆÔ©S¥UHD°PÜýjÞ³\’lé}ÄèñññC® …›B¡@LLLÐ~)&& …"(Û„ˆº)P¡ªGl@‘w&(µXN7Z<$$Ù6ÒkÄD£Òþýû¥.]¦œ?9ÔšqLLÌh áa‘Fƒ¨¨(ÆAàR«ÕÐétálŽ5ÄDDÔB¡@ll,t:Ìf3¬V+l6›xyœL&ƒR©DTTÔj5xÄ.”JeÄüRïFCD‘@¡PŒÄÑÏ#J¤ßÐ#¬BpáùERYˆˆ(tÄ.´ZmDÔD•JåoÖODD# ƒØCll¬¤a¬T*¾ \ÒWÿ"ŒB¡@||c ~¢AâzY’s`DGGKY,"¢aqžÃl6›ØÒ°VL âp~ƒŒ•º(DDà ³Ù »Ý.uQF%qxÖ†ív; ¢¢¢$.ÑðEEEA¡PÀn·³V â s^ÇÁYD4šèt:X­VÞ„(ÄAâyç,•J%u‘ˆˆ‚F¥R‰5b烂ƒA~ùË_"!!AÜîóÏ?={öÀl6céÒ¥x衇øKR4j444àèÑ£øæ›o Óépå•W⪫®‚ (//G]]ŒF#¢££1wî\L™2E|oqq1RSSÑØØˆ¶¶6ÄÅÅañâŸpá*++a00aŠ·ÅÅŘ:u*ª««ÑÖÖ†øøx,Z´ãÇ÷Z>ApäÈTWWÃf³!-- .ôú“ª'NœÀgŸ}¨©©ÁôéÓ¡ÕjQSSƒµk×B.—Ãjµbûöí˜?>L&S¿õ ‚ýÓ°Fn¾ùf$$$ॗ^òº<** “&MBQQ¶oߎn¸O<ñêêêÄu:::ð?ÿó?øùÏŽââbLž<ëׯGCCžyæ¼ñÆhllÄŸþô'ñ=›7oÆùóç±uëV¼õÖ[8wî¶lÙòã% ³ÙŒÝ»wcÚ´iøÑ~„•+W"==@ßÄÅÅaåÊ•¸é¦›0mÚ4”––¢££C|¿ÉdÂÑ£G±páBüà?@||<Š‹‹ÑÕÕ…+V`íÚµèêê‘#GÜö[VV† àæ›oFbb"þñÀb±x-cYY Ö¬YƒuëÖÁ`0 ¼¼Üëº999˜;w.222pÏ=÷   sæÌÃáÀW_}8vìÆŽ‹iÓ¦y]Ÿ"ƒ8ÉårlÚ´ o¿ý6ªªª¼®sóÍ7###&LÀ]wÝ…äädœF¹\ŽÅ‹ãرc¨¯¯Ç™3g°xñâ?) 76MG¨©S§âÖ[oÅ“O>‰W_}µßòŽŽìܹŸþ9 a2™ÜÖqý)Æ+¯¼`4¡×ëééé0€ºº:‚€ 6ˆ¿e³ÙÐÓÓ’ã# ·„„¤¤¤`ÇŽ˜¢H`6›qúôiôôôÀf³¡©© J¥Z­‹0™L¨®®ðmKÑP)•J´´´àüùó0™L8vì,‹ØU¤Ñh`0ôu%åää ¼¼ÝÝÝnýÔ{öìÁ‰'Äéèèh455¡»»6› ‡¥¥¥ÈÉÉANNƇO>ùÄçúØ4=<üðÃ8|ø°8––†x/¾ø"žþyäççãšk®ö~î¾ûn¨Õj¡¹¹z½wÜq²²²†½m"©™ÍfÔÔÔàÈ‘#°X,ˆÇŠ+ P(¼¼<=zŸ~ú)&Mš„´´´aïS¥R!##„ÑhDbb"V¯^-Žß˜5k8€o¾ù+W®Äܹs¡T*qèÐ!twwC§Ó!''ãLJÃá€Á`pëó2e °cÇ$''#996› ³fÍ`ÇŽÈÌÌDJJJ¿õ¿óï ûiøüwh„f»Þ–Ë\æ;Ÿå^¦]ב»¼Ï9í|(.=+/½v>ÇY,–CË€œ}H‚ ˆ·3 ˜8qb°vAD#Tqq1f̘éÓ§K]” hllDll,”J% …ø…b0}ã®är¹[k[VV–Ød®R©òt°°¹< —^ .‡ËÃ9 —i¸Ìóœv¸¼våo`@`MÓDDDbIˆALDD$!Ö"" ‘5kÖH]X#&""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHBüÄioo—ºDD40ˆCD¯×K]"¢ 2RaTbÓ4‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDbGM›6á¹çž û~- L&“Û¼[o½¯½öZØËB‘oçÎ8qâDX÷S\\ŒÊÊÊïs(\Ëi·Ûa³ÙýÞá— 8rä¶mÛ†W_}%%%8þ¼Ïõm6þú׿âðáÃCÚ…ƒ˜ðÊ+¯à׿þµÛ¼%K–`úô镈"•Ãá@ss3’““GÅ~†Ë³œÇÇÇ–}=zõõõX¶lÖ®]‹¤¤$ìÚµ F£Ñëú‡†Á`KÙ(0J© @‘éþûï—ºÚÚÚãÆû.)ËY__™3g")) põÕW£¦¦---HMMí·nSS¦M›ör’ âB¼þúëøàƒÐÞÞŽœœüâ¿@JJŠ¸Î¶mÛP\\Œ¶¶6Ìž=¿øÅ/0~üxlÙ²ÀÅ‹‘””„ 6`ÕªU€­[·â•W^ìÝ»k׮ů±~ýz¬[·7Þxã ö¿~ýz,]ºŸ|ò NŸ> &à¿ørssÃüIQ(555!)6Û€æ IDAT) r¹ûöíƒR©Ä’%KÄåŸþ9.^¼ˆåË—£¼¼uuu0ˆŽŽÆÜ¹s1eÊqÝââbÌž=gΜÁ… °|ùrLœ8±ß~œ.\¸€ªª*´¶¶"66‹-CÈápàĉ8}ú4z{{‘””„E‹!66VÜWjj*ÑÖÖ†¸¸8,^¼.\@ee% &L˜€ÂÂBh4ß6ýVWWÃf³!-- .DTT”×Ïãĉøì³Ï555˜>}: _~ù%N:…ÞÞ^\qÅneóu\ÇŽCMM Ö®] ¹\«ÕŠíÛ·cþüù˜6m4øEèëb2›Íˆwû73™LØ¿?–.]ŠÚÚÚáý `/½ô<ˆ§Ÿ~Z­øÃpß}÷áoû¢¢¢°uëVìÚµ ¿úÕ¯0vìX?~ ˆŠŠÂ¤I“PTTFƒÝ»wã‰'žÀÌ™3‘ššŠõë×£§§xòÉ'‡¼ÿŽŽ<ÿüóxíµ×’’‚^x7nÄ|àv⢑­©©Iüò•‘‘ýû÷Ãáp@&“è  œœÈårÄÅÅaåÊ•P*•¨®®Fii)ÆïûöíCAA®ºê*ÄÅÅyÝôööâôéÓøþ÷¿ØØX=z{÷îÅm·Ý¹\ŽcÇŽ¡¾¾+V¬€R©Dyy9>øàÜzë­Ëå0™L8zô(Ö¬Yƒ˜˜>|ÅÅÅÈÎÎÆŠ+{öìÁ‘#GPXX(++ƒÉdš5k JKKQ^^.†«g9srr`µZÑÙÙ‰k¯½V\çĉ¨ªªÂ²eË ÕjÑÔÔ­Vë÷¸æÌ™ƒêêj|õÕW˜={6Ž;†±cÇŠµÚùóçcçΰZ­HJJÂéÓ§û}ŽpàÀ$&&bâĉ âÅ>âÀb±àÍ7ßÄã?ŽÌÌLL˜0O<ñzzz°ÿ~Øív¼úê«xì±Ç0cÆ $&&âºë®ÿØo¾ùfddd`„ ¸ë®»œœŒ“'OmÿM›6!;;z½wÞy'ÚÚÚÐÐÐ’Ï„¤áÚ:yòd‚€ææf@OO:::0yòd@VVôz=ÆŒƒÜÜ\ÄÆÆ¢¥¥Åm{Ó§OGVVÆŽ ¥Réu? ÕjQPP€ÄÄDhµZäææÂh4¢³³v»_|ñ/^,îïšk®ÕjƒG£Ñ °°ãLJV«ÅìÙ³sæÌA\\âââ0uêTñX, *++‘ŸŸ­V‹èèhÌŸ?¿_ùëÇÇÇâÅ‹1nÜ8DGG#33S<ÖŽK.—cñâÅâ—Œ3gÎ`ñâÅâ¶“’’››‹¯¿þ€Z­Æ•W^é¶ÿ¯¿þ ÈÏÏø–$ÅñpîÜ9Øl6·ÁSZ­³fÍBmm-Î;£Ñˆ¬¬,¯ïïèèÀÎ;ñùçŸÃ`0 ±±±ß(éáìßɵ1%%Z­ÖçÀyÚÛÛa·ÛÅþP¹\ŽôôtÔÕÕ!99µµµHMMCÆd2¡ªª ÍÍÍ0›Íèêêê7¢811Ñï~œ\ÿÅÆÆB©TÂjµÂ`0@·õ•J%ÑÑÑáõXV«Uüš«Õ èìì„ÃáÀ{ï½'ÖöA—TNWƒV«uÀu|ж™™™Øµk–.]ŠèèhqÝÒÒRôôôছn‚B¡ÀÑ£GñöÛocõêÕÐëõèîîFYYrssãsÿ$=ñ`±X ìv»Û­J¥‚J¥Bww7€¾…B¡p{ooo/î¼óN,[¶ ?ùÉO””„{î¹v»=hû÷Åy£Ñ¡¹¹¹_¿mff&8 š5k€¾KeJJJžžŽyóæ!&&ï¿ÿ>Çöãóÿ—ÍfƒÃápk"…BáVËöÇõ½jµ°víZ±Ïx(å´X,ƒÞ¿·r¿(¸vñtuuáÌ™3¸ýöÛ¡Óé×^{-vïÞÏ>û Ë—/DZcÇ`±XpôèQ±ïZÀÉ“'±hÑ"̘1#àòQð1ˆG€I“&A&“áøñã¸êª«ôýAUTT`Íš5˜0aAÀ©S§Ä&7§S§N¡££<ð€8Ï98ÆÕ@×>úÛ?]<ûm¾–«ÕŠóçÏ£½½]lÕÒÒ“É„¼¼ùä}‡½{÷ââÅ‹0¨ªªÂ©S§úõSäc8¼ûî»x÷ÝwÝæýýïÇ£>ŠÿþïÿÆ#<›Í†¼¼´´4lܸYYY«ÕŠçŸ{öìÙlÆÒ¥KñÐC±¦Màĉøì³Ï555˜>}: Ž9‚êêjØl6¤¥¥aáÂ…ˆŠŠÂ±cÇPSSƒµk×B.—Ãjµbûöí˜?>L&“×í@SS’’’ —÷ Ñ)..ÆÔ©SQ]]¶¶6ÄÇÇcÑ¢E?~|¿r ‚€òòrÔÕÕÁh4"::sçÎÅ”)SÄu<›²‹‹‹‘––†ÆÆF´¶¶b̘1X´h’’’Bú™Òàq°Ö±bÅ ñ¹= /ˆ=û‡•J%ZZZpþüy˜L&;v ‹W^y%€¾¿mç K­V ‹Å‚ŠŠ ˜L&TWW€ØêE#›¦G˜+Vàá‡Ư~õ+·ù111X¹r%žzê)´¶¶böìÙxñÅÅæ²»ï¾jµEEEhnn†^¯ÇwÜ!^ÞDt¹›2e °cÇ$''ã;ßùæÎ ¥R‰C‡¡»»:999?~<¾øâ Øl6Ìš5 PPP€;v 33)))ý¶—ŸŸ«ÕÚ¯X¥R!##„ÑhDbb"V¯^-6_Ïš5 À7ß|ƒ•+W"//GŧŸ~ŠI“&!---ÜY¨: üm×Ûr™Ë|ç³ÜË´ë:r—÷9§Å¥gå¥×Îç8‹År(€c³oFØívØl6 Lœ81X»pS__[n¹{÷îu«¯_¿ëÖ­Ã7Þ’ýÑðTVV¢¦¦×_½Ûü@oa)¥ÆÆFq°—B¡¿,Úç,—ËÝZä²²²Äf|•J• €€ÍåY¸ôZpy8\Îi¸LÃežç´Ãåµ+îuÈ›¦G˜ÒÒR\uÕU¼ëÑ£Ñh-u1(±iz„Ù½{7Ö­['u1ˆ(@lB&_X#ApæÌ,Y²Dê¢Q°F<‚Lš4 GŽñºlëÖ­a. Ú5k¤.IŒ5b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$ÄŸA ‘ööv©‹@DD#ƒ8Dôz½ÔE " *£Ñ(uF%6MIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆALDD$!¥Ô oµ¶¶â¥—^Â'Ÿ|‚¶¶6èõzÌ™3<òÆŒƒ[o½×_=î¼óN©‹JDôÿ~TVVz]¶nÝ:Œ;Öë²ÖÖV”——£¥¥z½ ,@rr²×u÷íÛ‡³gÏär9¢££‘žžŽ @¡Pç@hØÄÂb±à_ÿõ_1cÆ üþ÷¿G||<.^¼ˆÏ>û 111€%K–`úôé—”ˆ‚!??W_}µÛ¼Ó§OãäÉ“>C¸³³|ðæÍ›‡ÂÂBœ={;wîÄ7Þèó=YYY¸úê«a³ÙÐÚÚŠÒÒR8,\¸0èÇDCà Ž§NBcc#¶mÛ&~SÕëõnÁ{ÿý÷KU<" 2¥²ÿé÷ìÙ³˜9s¦Ï÷|ùå—HMMŬY³¹¹¹hmmÅ—_~‰¥K—z}B¡€R©„R©Äĉ‘êêê ûˆ#„Ýn‡ÙlFSS“ÏuÖ¯_÷Þ{ð׿þyyyâcþüù˜?>ÚÛÛV«øÃpà 7`ÅŠxòÉ'a4Ãr,D¸ÖÖV´µµaêÔ©>ש®®FFF†Û¼ŒŒ ÔÖÖz?ÝÝÝÐëõC-&…ƒ8Bdgg#33ÿò/ÿ‚×_/^pýüà(//‹-Â7Þ(þmÞ¼çÏŸÇÖ­[ñÖ[oáܹsزeK8…ˆ† ¢¢P«Õ^—Ûl6˜L&Œ3Æm~\\, ¬V«×÷544 ¾¾µµµ(--Å×_ÜÜÜ —Ÿ†ŽA!T*^}õUüð‡?ĶmÛ°zõjlÚ´ gΜñûÞ;w¢²²>ø  §§%%%xøá‡¡×둘˜ˆû¥¥!> " ‹Å‚ªª*deeù\§··@߹•F£˜L&¯ïëèèÀ®]»°{÷nœ9s6› §N RÉ)ØGAt:Ö¯_Ûn» û÷ïǶmÛpçwâµ×^óÙ\ÕÑÑgŸ}?þ8bccuuu6l€L&Ð÷mº§§'lÇBDƒ÷õ×_#..‰‰‰>×qÖ”M&t:8ßb±´Z­×÷Íš5K˜eµZqúôi:tÉÉÉHKK ÒÐp0ˆ#R©Ä²e˰dÉÜrË-Ø»w¯Ï .**Âܹs±lÙ2qž3ß|óMÄÅÅ…¥ÌD4tÈÎÎp•J…¨¨(tuu¹õñvuuA£Ñxüå)**J¬ÕÔÔÄ ŽlšŽ` …ãÆóÙgTVV†C‡aãÆnó“““?þ8Å$¢ahjjBww7233ý®›ššŠªª*·y555HOOhŸ¦_7I‡A!¾üòK¡¼¼çÏŸG}}=^~ùeœ:u Ë—/ï·¾ÑhÄoû[<øàƒý®T*•X¿~=¶lÙ‚òòrØív\¸pá:"¤ŠŠ L™2QQQý–íÙ³'Nœ§gÏžúúzœ8q===8uêjjjÄË™¼v»]¼2£¦¦MMM‡7…›¦#„³ ¹¨¨ÍÍÍÐh4˜1c^xᤦ¦ö[ÿÃ?Ä… ðÔSOáé§ŸçoÞ¼ùùù¸ûV«ÅíéõzÜqÇ!¢ð2™L¨©©Áš5kú-s80 ˆŽŽç7×_=>ýôS?~cǎŪU«ïs'OžÄÉ“'ôÕ„õz=V®\ÉK˜"ˆL¢íz[.s™ï|–{™v]Gîò>ç´ó¡¸ô¬¼ôÚùg±Xp,r8¾ýÖi³Ù`00qâÄ`킈("466"66J¥ …ryß)Ú9(t°är¹[ ]VVA¨Tª|ìl.Ï¥ׂËÃáòpNÃe.ó<§.¯]y›Èò€±išˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ8ˆ\/jwÞ胈h4p=§zƒ8är¹x—"¢Ñ@ñNZ\üTƒÄù Q&“A¡PÀl6K\""¢à1›ÍP(nç: q9ƒ¸»»[ê¢Mww·[Sð0ˆƒH&“A&“A©TÂjµÂjµJ]$"¢asžÏ”J¥xž£àa“ëH™L¹\¹\•J…ööv KFDíííP©TâùÍó¼GÃà ç·D×Z± èêê’ºhDDCÖÕÕAÜjìƒ8Èœ}Ä …*• ]]]0R‹ˆ(`F£]]]P©Tây| â pEè|Èår(•Jh4´µµ±fLD#JWWÚÚÚ Ñh T*Å&i×Ú0C98”R`4r½ÖÎáp@§ÓÁ`0Àh4B¯×C¥RIX:""ß, ÚÛÛa·Û¡ÓéÄš°B¡àuÄ! ™L‡Ã!>kÅ Ñh`µZÑÜÜ •J…1cÆ@­V÷ø@DN‡‚ Àl6£«« ‹*•  …B¬ ;±6| âp6M ‚…Bá6O©TÂf³¡½½‚ ð\D$9çhh¥R)Ö‚år¹Xv®Ãð q¹Ö†Áëp8ÄÎ[Ä9r ‚‡ÃÁûR‘d\ǵ¸^‚éz©’ç(irp1ˆƒÌ3Œ]Ÿµdgø:_IÉ5ˆ¯oÇ»0„C‹Ažaìp8ú rðœÇ@&¢pó X×Jƒ¯u(øÄ!â­fì¾Îe `"’š·Xž¡Ëq¹°ç4GQ$b‡ƒ8 |2Q¤ây*|Äaäú›MÒDi¾Ò`K„ÿቈས‰ˆˆ$Å &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" ñG$Â__"¢H㑃8Œ¼…/™ˆ¤æù›é®ó(ôÄaàúŸÛùZŸë…ƒgØÊårq¾óœÄ@=qy°óa·Ûa·Ûa³Ùú2Q¸Éår(•J( ( Èd2ñÁ@=qˆ¸†° p8°Ùl°X,ËåÐjµˆ‰‰\.çp"’ŒÃဠ°Z­èíí…Ùl†J¥‚R©„L&kɇƒçªa‡€3„5`A`6›qqqP*ù±QdÉdbMX£ÑÀf³Á`0Àf³A­V‹ë8kÇ ãàãåKAæ‚ Àf³Áh4B­V#!!!LDM©T"!!jµF£QìBs=·Qp1ˆƒÈ³OX˜L&ÄÄÄ@§ÓIX2"¢Àèt:ÄÄÄÀd2¹1À06VÏB@Øív˜ÍfhµZh4©‹DD0F#žËœÍÔÎ>c ~¢AâzY’s`DGGKY,"¢aqžÃl6›ØÒ°VL âp~ƒŒ•º(DDà ³Ù »Ý.uQF%qxÖ†ív; ¢¢¢$.ÑðEEEA¡PÀn·³V â s^ÇÁYD4šèt:X­VÞ„(ÄAâyç,•J%u‘ˆˆ‚F¥R‰5b烂ƒAœ={¶ßü+¯¼Ë—/÷ù¾;v`Ê”)ÈÍÍõº¼µµåååhii^¯Ç‚ œœ,.·Ùl8zô(ª««!&NœˆE‹ù¼?ùPËéT\\Œ3f`úôé®çp8ðÕW_¡ªª ÐjµÈÎÎFvvö o°Ûpý,Þy礦¦báÂ…>Ëæï3Ê6C¹ÝÚÚZ|òÉ'˜5kfΜ9¤m¹þ»ËårÄÅÅaÆŒ˜9s¦x¯‚}ûöá›o¾ÁM7ÝäóøÞ}÷]¬^½Úçq‘´Äæ‰'žÀ±cÇpß}÷áÁ„ÕjEee%bbbBºßW^yxòÉ'CºŸH”••ÓëÏ IDAT…«®ºÊmž¿;£¥¥¥aܸq^—uvvâƒ>À¼yóPXXˆ³gÏbçθñÆ1vìXÀÁƒa4±jÕ*X­V:t»víÂêÕ«ƒZÎ@={çÏŸG^^bccÑÐЀ²²2èt:\y啃:¾ÁlÃÕáÇa0,×`>Ó@·ªí¶¶¶Šëuwwû\o°etþ»Ûl6466¢¬¬ V«sæÌ×iooG]]RSSû½ÿóÏ?÷»’ƒ8‚|ôÑG8zô(þú׿ºýŽñÔ©S%,Õè§P(¾7ø‚ |.ûòË/‘ššŠY³frssÑÚÚŠ/¿üK—.…ÙlÆ™3gpûí·‹?rÝu×áÍ7ßÄÅ‹‘˜˜´r*##™™™âtVVšššPWW'†¨¿ãÌ6œêëëÑÔÔ„iÓ¦ X.ûÊ6CµÝÊÊJ¤§§cÆŒصk—×u)£óß]¥RaêÔ©0›Í8~ü¸[«T*|þùçý‚¸³³MMMü%¸Ç>âòÒK/á'?ù‰[{Z¿~=vïÞxK–,Ayy9Àjµâøn¸á¬X±O>ù$ŒF£¸ì¹çžÃš5k°hÑ"¬[·~ø¡¸Í­[·â•W^ÁG}„¼¼<<ýôÓ~· ôýîòŸþô'¬^½«V­BQQQ(>I‹µ¤×^{ âüÊÊJ¯ï©®®FFF†Û¼ŒŒ ÔÖÖè;9ªT*·_èÒh4HIIA}}ýÊ)>ùälÛ¶ [·nÅþýûaµZÝÖ©¯¯ÇÛo¿¿üå/øÇ?þÎÎÎ~ÛñvkVNçvG%Ç7˜m€ÉdÂþýû‘ŸŸï·fïoŸƒÝæ»ï¾‹Ã‡‡t»ÈÎΆB¡ðz,·7III0™Lnÿ¾3fÌC×Õ‰'Í Žp âa2™P__/~3Èã?Ž%K–àå—_FNN`óæÍ8þ<¶nÝŠ·Þz çÎÖ-[ôý–è¤I“PTT„íÛ·ã†nÀO<ºº:}á¾aÃ\wÝu(//ÇÆýnþøÇ?âСCxöÙgñâ‹/¢··7ØKXØívØl6·‡ëƒöíÛ‡ÔÔT¬^½IIInËf³Ád2a̘1nóãââ`±X`µZ¡Ñh`±XÜ>/AÐØØˆžžž!•³¬¬ ƒkÖ¬Áºuë`0Ä/i@_ø×ÖÖbÉ’%¸é¦› Õjñ÷¿ÿ6›ÍïçÓÜÜŒñãÇúøümÃéÀHLLô;ž }úÛftt´8"TÛõg°Çí‹Ñh„N§s W¥R‰Y³fáøñã⼞žÔÖÖ"++kHû¡ðaÓt„hll„Ãá@BB‚ßuo¼ñF¬[·NœîééAII >øàèõzÀ}÷݇7Š¡zóÍ7‹ëßu×]xï½÷pòäI¯}JƒÙ¦ÅbÁöíÛñòË/‹Mç›6mÂÎ;‡öH¨¢¢nó\¶LŸ>}Ð'3g¸z6!;OÒÎÿ¸qã°ÿ~@¡Pà³Ï>C\\ÌfsÀå;v,*++qÛm·A«ÕæÏŸ={ö   @ߪY³f‰_$–,Y‚ÿýßÿEuuõ€]µµµèììy æø)S¦¸¼MWËìÏr»ÒÛ²Fމɓ'C.—wyÞÙ&>>ü1rrr0dȇ/ä¾ÄnäÑGÅ{gûî»N¯|÷Ýw˜?~‡ùgΜASSV®\)N+--E]]Ãröç {*sРAððð@qqq·#ˆŽ"##QVV†¨¨(qÚ¥K—0dÈ.×Ñëõ¨©©Á´iÓ\Þž¯¯/<==Q^^Þå(Ü7nàúõëÓêëë;=5!²²² “É:­3û×]iiiHKKs˜väÈÈåò.{&zÚfoÊìÏr;ÓÛ²l#¦áíí‘#GâÔ©SX¼x±Kõ#é°)ãF–,Y‚Q£FáÑGÅÎ;qáÂTTT`ÿþýÈÎÎîr=…BŒŒ ¬_¿ùùù°X,¨««Ï'jµZèõzìÞ½ÍÍÍÈÊʇÖ×/~ñ ¡¦¦7nÜè±LµZyóæá¯ý+NŸ>††¬[·®_;GBB***pâÄ ´¶¶âÌ™3¸té’Ã@¼’’TWWC¯×£¢¢™™™HLLìU F&“!11ùùù¨ªª‚ Ðëõ¨¯¯—Ñëõ8yò¤øþæååA‡nžKþöÛoqýúuÌš5 ÄaÎîŸ3e¸Ê™×Ô_ý5Nœ8ÑïåJiìØ±HOOï´'‹Ü[ÄnD¡Pàÿþïÿ°cÇ|ýõר°ad2†Úã¯j=ùä“ðòòš5kP[[ ­V‹Ç±±±ˆŠŠÂÊ•+±qãFlذ)))˜1c†ÃúóæÍCNN/^ŒqãÆáÿ÷»-V­Z…7ß|Ï?ÿ<üüüžžÞ«®Õ»MPPæÌ™ƒcÇŽ¡¸¸˜;w.Ül-ÖÕÕ¡°°‹˜0aB‡Ëh\1vìX( äææB¯×C­V#11Q©Œàà`ñG$BCC‘žžÞ¡kº¼¼\ü%§íÛ·;Ì[²d |}}{Ü?gÊpUOÛt†Õj…N§s¸<°¿Ê•’J¥â/hÝa:^ðw{Êíl¾‡ÝtÛ½¬“çöËÈìÖ³=·Ýä?Þ+~|l»÷7¹.ìK·l—‚ ^^¢Óéx$"rVUU4  …ÃήïŽL&s¸ 66V<_®T*S4°0ÛÝ ?>ìnV»›í9ìžÃnÚ­Ï­víu6Í•ù.c×4‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDRH]»Ucc£ÔU "¢;ƒ¸ŸhµZ©«@DÔ§ ƒÔU¸+±kšˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’Bê мüò˸pá>úè#xxxt˜îÜ9ü×ý6mÚ„±cÇöjF£)))Ø»w/ˆÅ‹cΜ9Xºt)@¬]»û÷ïG@@Þxã |ýõ×7nÆß'Ût'Ä… :L:t(î¹çž.×ÛµkFŒ¤¤¤Nç744 ??õõõÐjµ˜0aÂÂÂÄùf³¸xñ"AÀÀ1eÊxzzöi=möìÙƒ˜˜Œ5ªÛå¬V+N:…²²2455ÁÛÛqqqˆ‹‹szÿœ-ÃþµØ½{7"##1yòä.ëÖÓkÚ›2û³ÜË—/#//ñññ=zt¯Ê²ße2üýýƒÑ£G‹ÇˆƒâúõëX´hQ—û÷é§Ÿ"==½Ëý"i1ˆÝLYY>ŒiÓ¦u˜·yóæ>ß^ZZšÃÁyß¾}(((ÀŽ; “Éàç燬¬,x{{÷:ˆÝ]ll,&Nœè0M&ë¾³(** AAAÎknnFff&ƇiÓ¦áÂ… øòË/±`ÁŽ9ƒÁ€¹sçÂd2!77û÷ïGzzzŸÖÓU.\@uu5’““¡ÑhPYY‰œœ¨Õj :Ô©ýs¦ {G…N§ë¶^즮–Ù_å644ˆËéõú.—s¶Ž¶÷Ýl6£ªª 9990™L3fŒ¸Lcc#ÊËËÙaýãÇ÷¸ ’»¦ÝŒF£Á|ÐazEEŠŠŠàããÓ§Û{öÙg1iÒ$‡íÄÆÆ"((Z­ …»víBFFFŸn×Èår(•J‡›BÑýwÔ &tÙº?yò$"##Fƒ¤¤$ <'Ož´··£´´Ó§OG@@‚ƒƒ1{ölÔÕÕáêÕ«}ZOW 6 ³gÏFxx84 bcc1tèP”——;½ΔaSQQššŒ9²Ûzõ´ÍÞ”Ù_åž={C† ÁâÅ‹Ñé2®ÔÑö¾«ÕjDGGcüøñê§T*; ÜææfÔÔÔtÙÓBîAìfxà1tímÙ²¿þõ¯¡R©¦ ‚€Í›7cáÂ…˜1cž{î9TWW‹ó- Þ~ûm¤§§cîܹX³fÃúØ·oà/ù þõ¯áóÏ?Grr2–,Y".“™™)®c2™°nÝ:Üÿý¸÷Þ{ñꫯÂ`08½Í;Éž={ÄVÒ|€ªª*qúÙ³g;]çâÅ‹6l˜Ã´aÆáòåËnmV•J…ððpTTTôªž‚ //~ø!¶nÝŠììl˜L&‡e***°cÇlÞ¼ÿþ÷¿ÑÜÜÜ¡œÎN‰¨Õj‚àôþ9S´µµ!;;)))=¶ì{Ú¦³e~úé§8zôh¿–›ššŠ¸¸8ÈåòN÷Å•ýîLhh(ÚÚÚÞߘ˜1tí8qqqq b7Ç v3*• K–,q膾zõ*:Ôé9 M›6áàÁƒxíµ×°mÛ6(•J¬X±Bü¾õÖ[ÈÍÍÅÚµk±qãFܸq£Ëm¯^½=öÒÓÓ‘ŸŸ?ü°ÓåÞxã TWWcë֭ؾ};®\¹‚õë׋ó]Ù¦;°X,0›Í7«Õ*Î?xð "##‘žžŽÐÐÐnË2›ÍhkkƒŸŸŸÃtF˜L&¨T*F‡×ETUU¡µµµWõÌÉÉN§Ã<€… B§Ó!??_\·¹¹—/_FZZ-Zooo|þùç0›Í=¾>µµµvzÿz*Ãæðáà éqÜ€+Ûì©LñËl•Ûg÷»+ƒjµÚ!\ âããQ\\,NkmmÅåË—Û«íÐíà vC?ü0Nž<)¶¸¶mÛ†ùóçw8`FlÛ¶ «W¯ÆðáÃW^y­­­ÈÎΆÑhÄÎ;ñòË/#::ƒ ÂË/¿ü“êÖÚÚŠ½{÷â…^€V«EHHV¬XC‡‰uêëmö·’’¼ÿþû·ÚÚZqþ¨Q£‹ÀÀÀ»‚máªT*¦ÛÒ¶PP²³³ÑÚÚŠ¶¶6äååÁßßííí.×Óh4âìÙ³HII··7|||0~üx‡VÕjE||øà¬^½™™™Ø±cG‡å®\¹³Ùìp€óööF||<._¾Œ!C†Àb±8Ìÿ©ƒ{ÊËË!–-[&vAšÍf±%WUUÕçÛìoñññÝŽ€ qº,ÛA¯­­Í¡ëÙh4¸ùþÀ½÷Þ‹#GŽ`çÎP©THHH€‡‡,‹Ëõ¬¯¯‡Õjž}ûÄ÷D‡]@@  >—Ëå ESSS—Û3 8rä’““Űrvÿº+C¯×#''IIIðõõírû6ÎlÓÕ2û³Ü®ô¶¬“'OŠç„ýüüßi+W©T"66ÇÇÔ©SQZZЇzè'Õ™n±›zä‘Gð«_ý ¯½öfΜ١[¸yÀ‹Å!ìlyL&A€ }†ÀÍVº¿¿‡ùý±Í;‰R©„§§'ZZZ ÕjÅé---P©Tb‹Z£Ñ`Μ9ëîß¿ááá.oÓ(>ø ÓÝ£ÀÍ0îê=²X,8pàÂÃÃ#Nwvÿº+£°°F£âXÛùãÓ§OcÊ”).oÓÕ2û³Ü®ô¶¬‘#GbòäÉËå]žw¶‰ÇÇŒœœ 2Äá ¹/±›ÒjµHOOÇŽ;ðé§ŸvºÌ Aƒàááââbñ²APRR‚xÀaþ„ ú¤^aaaðññÁwß}‡ùóçw[§¾Úæ&22eeeˆŠŠ§]ºt C† ér½^šššN/[뉯¯/<==Q^^Þå(Ü7nàúõëÓêëë;½ÜEdeeA&“uZgö¯»2ÒÒÒÄ®q›#GŽ@.—wÙ3ÑÓ6{Sf–Û™Þ–eûbí oooŒ9§NÂâÅ‹]ªIçç×d¹ƒ<õÔSØ´iÜé|µZùóçãoûJJJÐØØˆ5kÖ@«ÕbÒ¤IP«Õ˜7oþú׿âôéÓhhhÀºuë~R 222°~ýzäççÃb± ®®%%%búz›wš„„TTTàĉhmmÅ™3gpéÒ%ÄÇNj˔”” ººz½ÈÌÌDbbb¯Z02™ ‰‰‰ÈÏÏGUUA€^¯G}}½¸Œ^¯ÇÉ“'QSSƒ7n //‚ 8pó\ò·ß~‹ëׯcÖ¬Y  svÿœ)ÃUμ¦Îøúë¯qâĉ~/WJcÇŽEzzz§=VäžØ"vcþþþ=þ‚Ö‹/¾ˆüãøÝï~³ÙŒääd¬_¿^>III@UU•8ýìÙ³®sñâE 6ÌaÚ°aÃpùòe7޶«J¥Bxx8***zUOA——‡?ü[·nEvv6L&“Ã2رc6oÞŒÿûßhnnîPŽ­»ÓžZ­† NïŸ3e@[[²³³‘’’Òc˾§m:[æ§Ÿ~êðå¶?ÊMMME\\äry§ûâÊ~w&44mmmïoLLŒºöNœ8¸¸8±›c»™¯¾úJ<ç7}út|÷Ýw¸­[·"33úÓŸðñÇcΜ90`@óÞxã TWWcë֭ؾ};®\¹‚õë×ZZZðâ‹/bþüùøâ‹/°víZÌœ9³Çy=• o½õrss±víZlܸ7nÜèß°, Ìf³ÃÍjµŠó<ˆÈÈH¤§§#44´Û²Ìf3ÚÚÚàççç0ÝßßF£&“ *• F£ÑáµUUUhmmíU=srr ÓéðÀ`áÂ…ÐétÈÏÏ×mnnÆåË—‘––†E‹ÁÛÛŸþ9Ìfs¯Omm-‚ƒƒÞ¿žÊ°9|ø0BBBz+àÊ6{*ÓÇÇ*•ª_Ë퉳û݃ÁµZí® …ñññ(..§µ¶¶âòåˈíÕvèöá9b7rãÆ dggãwÞLž<&“ ÇŽCJJ , Þÿ}¬_¿111€Ù³g@·óZ[[±wï^dffB«ÕV¬X—^z /½ôêëëa2™šš ???‡Swóz*×h4bçÎxçw xùå—ñå—_öçËè²’’”””8L³Ø2jÔ(§f¶p½õ¼¾í m;ð!;;©©©Ëå(**‚¿¿?ÚÛÛ]®g`` Ξ=‹%K–ÀÛÛ0~üx|ýõ×HMMpsU||¼øE"-- }ô.^¼(¾7¹|ù2š››ÅA^Îìß­­¯[Ë€óçÏ£²²?üp—Û¶qv›Î”iûLôg¹Ýqe¿o%qìØ1$$$t˜?zôhœ8q  Â?ü€èèhxyy¹¼-º½Än$;;ÞÞÞ}ú4¦L™âò6]-³?ËíJoË9r$&Ož ¹\Þåyg›øøx|üñÇÈÉÉÁ!C¾`ûb» N‡ÜÜ\üë_ÿrø0655aöìÙ(,,ÄÈ‘#!Μ9Ó¡k*""¢ËyaaaðññÁwß}‡ùóçwY‡qãÆaܸqxì±Ç°xñb\½zUl v6¯§r c„ ?åå¹£DFF¢¬¬ QQQâ´K—.aÈ!]®£×ëQSSÓé—°žøúúÂÓÓååå]ŽÂ½qã®_¿î0­¾¾¾ÓË]A@VVd2Y§õqfÿº+#-- iiiÓŽ9¹\ÞeÏDOÛìM™ýYngz[–m„¼3¼½½1räHœ:u ‹/v©~$ŸG3åðÍ7ß 88¸Ã7 $&&â›o¾¿¿?f̘×^{ ååå0 ÈÎΆÁ`èvžB¡@FFÖ¯_üü|X,ÔÕÕ‰çZðÙgŸáêÕ«hkkCaa!¼½½Ðí¼žÊU«Õ˜7oþú׿âôéÓhhhÀºuënûk{»%$$ ¢¢'Nœ@kk+Μ9ƒK—.!>>^\¦¤¤ÕÕÕÐëõ¨¨¨@ff&{Õ‚‘ÉdHLLD~~>ªªª ôz=êëëÅeôz=Nž<‰ššܸqyyyÁ!€€›ç’¿ýö[\¿~³fÍq`˜³ûçL®ræ5uÆ×_'Nô{¹R;v,ÒÓÓ;í¥"÷ı›°-}«éÓ§cË–-XµjþøÇ?bíÚµX¾|9L&¢££1bĨÕênç=ùä“ðòòš5kP[[ ­V‹Ç±±±Ðétøæ›oðÖ[o¡µµ‘‘‘xíµ× T*Q__ßå<Ý– «V­Â›o¾‰çŸ~~~HOOïU÷ë$((sæÌÁ±cÇP\\ŒÀÀ@Ì;n¶ëêêPXX‹Å‚ÀÀ@L˜0¡Ãe4®;v, rss¡×ë¡V«‘˜˜(ŽTFpp°ø#¡¡¡HOOïÐ5]^^.þ’ÓöíÛæ-Y²¾¾¾=îŸ3e¸ª§m:ÃjµB§ÓÁÇǧßË•’J¥â/hÝa:^ðw{Êíl¾‡ÝtÛ½¬“çöËÈìÖ³=·Ýä?Þ+~|l»÷7¹.ìK·l—‚ ^^¢ÓéÜâ版úRUU4  …ÃήïŽL&s¸ 66V<_®T*S4°0ÛÝ ?>ìnV»›í9ìžÃnÚ­Ï­víu6Í•ù.c×4‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDb÷!ûŸy³ýô%ÑÝÀþ˜æêOZR÷Äý@&“‰¿›JDt7ø9ý_ñÛ¯j±}Côðð€\.G{{»Ä5""ê;íííËåÇ:ê â>f b½^/uUˆˆúŒ^¯wbê; â>äáá( ˜L&˜L&©«DDô“ÙŽg …B<ÎQßaÿDöÉdÉdP*•hll”°fDD}£±±J¥R<¾ÝzÜ£Ÿ†AÜGlßí[Å‚  ¥¥EêªõZKK Aph ³UÜ·Ä}ÌvŽX.—C©T¢¥¥ƒAêj¹Ì`0 ¥¥J¥R<®1€ûƒ¸Ø"´Ýd2 T*®]»Æ–1ÝQZZZpíÚ5¨T*( ±KÚ¾5ÌPî ©+p7²¿ÖÎjµB­VC§ÓÁ`0@«ÕB©TJX;"¢®F466Âb±@­V‹-a¹\Îëˆû ƒ¸xxxÀjµŠ÷ÄV1¨T*˜L&ÔÖÖB©TÂÏÏ^^^>ÝNV«‚  ½½---0P*•P©TËåbk؆­á¾Ç î¶®iA —˦) ˜Íf466BþIÎ6Z¡Pˆ­`™L&¶„mË0|ûƒ¸Ù·†mÁkµZŶŸˆ³ äV«•¿KMD’±×b ¦ý¥J·Ž’f ÷-q»5Œíïm­d[øÚIÉ>ˆmÿŒwa÷/q?¸5Œ­Vk‡A·Nc ÑívkÀÚ7ºZ†úƒ¸ŸtÖ2¶_Û<0I­³X·†.C¸ÿ0ˆû‘}ßúœ#‰È1€o?ñmÐU ¹+§nñmdÿ‡Í.i"r7 _i0ˆ%Â?x""ø[ÓDDD’bIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„øO$Âÿ¾DDî†ÿŒF âÛ¨³ðe ‘ÔnýŸéöÓ¨ÿ1ˆoû?nÛcAº\†ˆèv¸5le2™8ÝvLb ÷?q?º5€m7‹Å‹Å³ÙÜ!‰ˆn7™L…B¹\¹\ñÆ@î â~b‚ ÀjµÂl6Ãh4B&“ÁÛÛ¾¾¾Édü'"ÉX­V‚“É„7n ½½J¥ …b+ÙjµòXÕOÄýÀ¶° hooøûûC¡àËNDîÁÃÃCl «T*˜Ífèt:˜Ífxyy‰ËØZÇ ã¾ÇË—ú˜} ‚³Ù ƒÁ/// 0€!LDnM¡P`À€ðòò‚Á`O¡ÙÛ¨o1ˆûЭç„A@[[|}}¡V«%¬‘kÔj5|}}ÑÖֿĸ¯±yÖA€ÅbA{{;¼½½¡R©¤®‘ËT*•x,³uSÛÎSßá+ÚGì/K² Ì)«EDô“ØŽaf³YìéØ*îK â~`û©Ñh¤® ÑO¦ÑhÐÞÞ‹Å"uUîJ â>pkkØb±@.—ÃÓÓSâšýtžžžËå°X,l÷q³]ÇÁYDt7Q«Õ0™Lü¢~À î#·þr–R©”ºJDD}F©TŠ-bÛúƒø'êìg,AàÈB"º«Èd2ñô/eê[L‹>rëybþú ÝMl¿¬ÅóÃ}AÜx…ˆîF<¶õqâ7D"ú9à±®o1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB ©+@ŽAÀ–-[‹/"<<ãÆÃ3Ï<ÃÿmLäæl¿Å¬P8whݳgbbb0jÔ(—·% PVV†¶¶6hµZLœ8ááá.o6›±{÷nDFFbòäÉ.oú[ÄnÄd2áé§ŸÆ7ß|ƒåË—ãÃ?Ä3Ï<ƒââb<òÈ#¸zõªÔU$¢nã»ï¾»-Û*((@EEfΜ‰|¡¡¡Ø¿? C§Ë=z:î¶Ô\à v#;wîDSSÞ}÷]L™2˜6m6oÞ ???¼ýöÛRW‘ˆÜDEEFÐÐP`Ò¤IðööF}}}§ËÖÔÔ`äȑԔz v#Û¶mÃO<///‡éJ¥Ï<ó ¾øâ ´´´àøV¯^-Î7?~<Þ|óMqšÁ`À¤I“påÊdddàý÷ßÇSO=…´´4,Y²Ç—5™LX·nî¿ÿ~Ü{ï½xõÕW¾UgddàÀX¹r%ÒÒÒŸŸß¯Ñéĉ(**Â… ðî»ïâÈ‘#⼓'ObçÎØ²e öïßïÐ2­««Cff&>øà|òÉ'¨­­b÷îÝâï;›L&lß¾çΨT*\»vM,Çh4¢½½õjkkCvv6RRRø_áÜß7¡×ëQ__¸¸¸Nç3V«ååå˜6mŽ;&þÞkAAàСCâòEEE8p """ÐÔÔ„ 6`åʕسgâââðÒK/Ád2Þxã TWWcë֭ؾ};®\¹‚õë×;lõêÕHKKÃ;#ÄÄÄ~xˆîl‰‰‰;v,† †åË—#55ÀÍ€.--ÅÌ™3±hÑ" >ÞÞÞ€7nàܹs˜8q"/^ŒdeeAñ3êÔ)7ƒ900PlÕŽ?çÏŸÇ·ß~‹3gÎà‹/¾ÀĉáïïïP¯Ã‡#$$¼¯¹‚Aì&jjj èt¾Z­†ŸŸ’’N‡²²2@nn.~ó›ß ©© /^äçç‹‚€€¼üòˈ‹‹ƒV«ÅÒ¥KqíÚ5TVV¢µµ{÷îÅ /¼­V‹¬X±Â!Ô`Á‚X¸p!¢££¡R©úéU º»‚€ââbL:AAAðññÁðáÃÅÁ\ÞÞÞHMMEHH¼½½‘””ƒÁ€ææfÈd2L:………¨¨¨@ii)¦N*–Ф¤$œ?‡†——†ê°ýóçÏ£²²)))·u¿É55í&‚ƒƒµµµÐh4æFèõzDDDÀ××cǎűcÇ0bÄäää`ݺu8wî>Œ¡C‡"??/¾ø¢¸¾}—Txx8¼½½a0P^^A°lÙ2ñ(›Íf´¶¶:l?!!¡?v›è®¦Óé`2™Ôå2öŸMF…B!öV…††bøðáØ¿?¦OŸîpåÄ¡C‡ÐÚÚŠE‹A.—£  ;vì@zz:´Z-ôz=rrr””__ßþÛIúÉÄn" þþþ(..ƈ#:Ì?uêd2 HKKÃÑ£G‘šš ¹\ŽÁƒcúô騵kæÎ‹ºº:Œ3¦ËíÙB×úÛ¶mëÐ¥ED?ÑhtyÛgÓÆÖíéé)NkiiAii)}ôQ¨ÕjÀ¬Y³pàÀáž{îAaa!ŒF# PTTà?ÿOøôéÓ˜2e bbbzµ_Ô·Änäá‡ÆÖ­[1oÞ<‡o¾‚ `Ó¦MX¼x±Ø-ùeeeøè£ T*Åéááá˜8q¢„5#"wSQQÑ£G#440iÒ$\ºt õõõˆŒŒì°lMM FŽ)EU©€›]U›7oÆÂ… 1cÆ <÷Üs¨®®×›={6¾ÿþ{ñù™3g0eʇrwî܉§žz iiixüñÇQRRÒÇ{Itw:q⊊Špá¼ûî»8räˆ8ïäɓعs'¶lÙ‚ýû÷;´Lëêê™™‰>øŸ|ò jkk………ؽ{7A˜L&lß¾çΨT*\»vM,Çh4¢½½õjkkCvv6RRR “ñïŽø®¸‰––\½z111N-¿zõj¤¥¥áwÞAbb"`Ó¦M8xð ^{í5lÛ¶ J¥+V¬€Édrº¯¿þ:ž}öYìÚµ xöÙgÙEä„ÄÄDŒ;Æ ÃòåË‘šš àf@—––bæÌ™X´h†oooÀ7pîÜ9Lœ8‹/FHH²²² ÆŒ«ÕŠS§N¸Ìb«vüøñ8þ<¾ýö[œ9s_|ñ&Nœ‡z>|!!!8pàm|5È b7qõêU? ÀÍ`?~¼xûÿïÿ‰ó,X€… "::*• F£Û¶mÃêÕ«1|øpDDDà•W^Akk+²³³®Çþð$%%!$$Ï?ÿ< €ôÝŽýŒ‚€ââbL:AAAðññÁðáÃÅÁ\ÞÞÞHMMEHH¼½½‘””ƒÁ€ææfÈd2L:………¨¨¨@ii)¦N*–Ф¤$œ?‡†——†ê°ýóçÏ£²²)))·u¿É5 b7aéXYY)N[ºt):„C‡aÞ¼yË'$$8<¿rå Ìf³ÃèKoooÄÇÇãòåËN×ÃÃÃC|,“É€òòrWv…ˆ~¤Óé`2™Ôå2öÝÅ …BìÅ Åðáñÿ~Lš4 >>>ⲇBuu5-Z„Å‹C©TbÇŽhllèõzäää )) ¾¾¾ý´‡Ô8XËM¨Õj„‡‡ãðáÃbÈ*•Jñ|±§§g·ëF‚‹ÅâðÁ¶/£7är9Gkõ’Ñhtyû/ÃÀzÉì---(--Å£>*Žž5k8€¢¢"ÜsÏ=(,,„ÑhDAAŠŠŠ@<ß|úôiL™2ÅéSaÔ¿Ø"v#O<ñ>úè#—Z°6ƒ ‚‡‡Š‹‹Åi‚  ¤¤Çpó}éÒ%‡õlLhhhÀ§Ÿ~ê0ÿôéÓ6l˜Ëõ!ú¹²ÿLi4X­VÔ××÷ª¬¦¦&””” 99999b°···èÚâ¼´´4<õÔSxê©§°|ùr,_¾±±±ˆÇòåËÂn„AìF,X€ääd<þøãøøãQVV†êêjdeeá›o¾éöÇ<Ôj5æÏŸ¿ýío())Acc#Ö¬Y­V‹I“&¸9˜dß¾}¨©©Á•+Wðꫯ: äR©T8}ú4Ž;†ææflÚ´ ƒ3gÎì÷}'ºøøø ¦¦z½f³*• QQQÈÉÉASSL&ÊËË@iµZqèÐ!$&&"11AAAÈËËp3pýýý‘••…«W¯Â`0 ¬¬ gΜépž˜Ü»¦ÝˆL&ÃÚµk±gÏ8p›6m‚B¡@TT~÷»ßá¾ûîëvý_|ÿøÇ?ð»ßýf³ÉÉÉX¿~½ø­ù¿ÿû¿ñÊ+¯`ñâňˆˆÀìÙ³ÎIûúúâ—¿ü%þö·¿¡¡¡ ظqcÝâDtÓˆ#PYY‰]»v!,, ÷ÝwÒÒÒ››‹Ï>û ‚ 00=~®~øá˜ÍfÄÇÇRSS±k×. >ááá˜7o¾ÿþ{8pF£~~~7n[ºw žé—r;›ïa7Ýv/ëä¹ý22»õlÏm7ù÷ŠÛîýFc® ûÒ-«Õ âùY³Ù NwG^*‘‘… bÁ‚RW…ˆÜPUU•8 L.—‹ãQní"ï‰L&sø‚ØØX±K_©T¦h``¶»~|,ØÝ¬v7ÛsØ=‡Ý´[Ÿ[íÛëlš+ó]Æ®i""" 1ˆ‰ˆˆ$Ä &""’k‘hëÖ­RWˆèg‡-b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" )¤®Àݪ±±Qê*Ñ€AÜO´Z­ÔU "êSƒAê*Ü•Ø5MDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆAìfAÀæÍ›ñÄO -- <òÖ¬YƒÖÖV©«FD=°X,Ø´iZZZnËövíÚ…ãÇ;lßl6ß–mSßQH]ú“É„gŸ}mmmX±b¢¢¢páÂlܸ<òÞ}÷]„„„H]M"rQQQ Ÿ£¹¹³fÍ’°Vä*¶ˆÝÈÎ;ÑÔÔ„wß}S¦LADD¦M›†Í›7ÃÏÏo¿ý¶ÔU$"72a 8PêjÐOıٶm~ûÛßÂËËËaºR©Ä3Ï<ƒçž{/¼ð^ýu‚€¿üå/£Ñˆ””,Y²ÿó?ÿ0 ˜9s&>ùäüþ÷¿ÇôéÓ‘——‡sçÎ!""«V­BRR€›-ñ 6à믿F{{;¦OŸŽçŸjµ‘‘G}™™™8qâ^ýu$''w¨ÿ‡~ˆ={öàÚµkHHHÀªU«Œõë×ãðáøzõ*BCC±lÙ2Ì;W\/##£ÛúýT{öìABBJKKQWW‡{ øþûïqñâE˜ÍfDEEaòäÉðôô„ ÈÏÏGyy9 |||0vìXŒ1B,W ¬¬ V«ƒvª>ß|ó ¬V«ØrµX,xï½÷É“'¸ù¹Ü²e ~øaþøcÌ™3 €§§' „5kÖ`çθÿþûñÊ+¯ ¼¼\\·§úõ…ƒ"22ééé äää@§ÓáÀÂ… ¡Ó韟Édð÷÷Ç/ùK,Z´#GŽÄ¡C‡ÐÔÔ$–ùý÷ߣªª ³gÏFzzºÓ³‘‘‘¸råŠø¼ººpùòeqZMM üüüàçç×eýí%&&bìØ±6l–/_ŽÔÔÔ÷‘܃ØMÔÔÔ Ðé|µZ ???444 %%:eee€ÜÜ\üæ7¿ASS.^¼ÈÏÏ?ˆxùå—­V‹¥K—âÚµk¨¬¬Dkk+öîÝ‹^xZ­!!!X±b…C¨À‚ °páBDGGC¥R9̳X,xÿý÷ñûßÿ111 ÁìÙ³áíí x衇0lØ0DDDà‰'ž@XXNŸ>-®ß]ýˆúʨQ£·…­‹÷IDAT‹ÀÀ@( Fœ={)))ðöö†Æï†±±±ÐjµðóóCRR4 êëëÜü»?}ú4¦NŠÀÀ@øùùaÚ´iNÕeРA0hllTVVbâĉhkkÃõë×UUU-ì[ëï gö‘¤Ç®i7 ¨­­…F£é0ßh4B¯×#""¾¾¾;v,Ž;†#F ''ë֭ùsçpøða :ùùùxñÅÅõe²ÿ|ç ‡··7 ÊËË!–-[€Ùlî0J;!!¡Ëº_¹rƒ±±±Îojj—_~‰ãÇC§Ó¡ªª mmmËtU?¢¾rë@ÇææfX­VìÛ·OüÛÁ¡UÛÖÖ†²²2ÔÖÖ¢½½---â¨ä––X­V‡ÁR¶rz¢T*†+W®@«Õ¢¢¢sæÌACCÊËË1`À\¹rS¦Lé²þÎpfIz b7;œƒ²9uêd2™ø 9-- GEjj*är9ŒéÓ§c×®]˜;w.êêê0f̘.·gûPÚBÛ¶mð÷÷ïUÝõz=€›p¹\î0ïÆXºt)fΜ‰§Ÿ~¡¡¡X¾|9,K·e:{@#ê-ÛXŒ|°C/pó éÞ½{1dÈŒ7¾¾¾øì³ÏÄSB‹V«V«µW¯‘‘‘b«×Ö …’’DGGC¯×wÚÝ—ûHî]Ónäá‡ÆÖ­[;´FAÀ¦M›°xñbñÃ4mÚ4#;;S§N¤¦¦âìÙ³øê«¯œœìT÷UXX|||ðÝwßõºÞgΜé0ïÌ™3hjjÂÊ•+1tèP¨ÕjqÀ ‘”|}}áééé0^Á^}}=ÚÚÚœœ,Žw¸víšøÅÓöŵ¶¶¶WÛŠŠBMM ÊËËÅ/؃F}}=ÊÊÊ0pà@‡ž"g ‚ >îiÉ=0ˆÝÈc=Fƒ¥K—âðá误GQQ~ûÛߢ¥¥Ë–-— Cxx8Þyç1ˆ}}}1fÌüýïÏ÷D¡P ##ëׯG~~>, êêêPRRât½ýýý1cÆ ¼öÚkâÓììl hµZèõzìÞ½ÍÍÍÈÊÊp³û™HJ2™ ‰‰‰ÈÏÏGUUAM€··7ŒF#JJJÐÖÖ&Ž¿°õ"yzz"::Z¼"À`0tõù矋ƒ)oåëë Fƒ¢¢"DFFøO—u^^žÓ#°íùøø ¦¦z½f³¹Ç}$÷À®i7¢V«±eËlܸÿüç?QYY‰¨¨(Lš4 Ë—/‡R©tX~Ú´ixï½÷.ó™1còòòÄK œñä“OÂËË kÖ¬Amm-´Z-üñ.Ïùvæü#Ö®]‹åË—Ãd2!::#FŒ@TTV®\‰7bÆ HIIÁŒ3œ.—¨?; …¹¹¹ÐëõP«ÕHLLDpp0œœŒ‚‚;v ƒ BTT”ÃúS¦LÁÑ£GñÕW_ÁËË ÑÑÑc<ÚÛÛÅË;…¢¢"‡.訨(TUUaРA.ïψ#PYY‰]»v!,, ÷Ýw_·û(Û¹õ††‰Ýý?Wýu"®§r;›ïa7Ýv/ëä¹ý22»õlÏm7ù÷ŠÛîýFc® ûÒ-Û âÏËét:^dODزe î¹çDDDH]•>QUUF…B¹\.v÷湇‡<<<Äóì6J¥2@3 ³Ý½ðãcÁîfµ»ÙžÃî9ì¦ÝúÜj÷Ø^Oß úü[[ÄDDý@§ÓÁb±üäWw«[øçŒçˆ‰ˆú§§'æÌ™ÓáJ¢[1ˆ‰ˆúJ¥BXX˜ÔÕ ;ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" 1ˆ‰ˆˆ$Ä &""’ƒ˜ˆˆHB b""" )¤®Àݪ±±Qê*Ñ€AÜO´Z­ÔU "êSƒAê*Ü•Ø5MDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIˆALDD$!1‘„ÄDDDbIH!u¨£¬¬,|öÙg(++ƒN§Chh(&OžŒ+V@­VK]="ºƒY,¼÷Þ{øõ¯ ???©«C`»«ÕŠÕ«W£´´O?ý4¢££¡T*QUU…ââb¨T*©«HDD}ŒAìF>ù䔕•á£>‚R©§‡‡‡câĉÖŒˆˆú ƒØ¼ÿþûxá…B¸3xôÑG‘™™‰'Nàõ×_Grr2AÀ–-[™™‰ÆÆF$&&bÕªUÌž=þóŸÅP?sæ –/_Žœœ±Üûï¿YYY8w¢ðÒK/!66¶Óz|øá‡Ø³g®]»†„„¬Zµ ÁÁÁX¿~=>Œ«W¯"44Ë–-Ãܹsê?}útäååáܹsˆˆˆÀªU«””Ô/#ÑiÏž=HHH@ii)êêêþ{wÛTÙ‡üZ×m챡 2¦›ƒ 7ÂH¦5Ã4/‰t$$>̘`Œ¨jf”E£ 3á‰øb$0†E}$š,>É‚6$tc[[{ú|§œÞ»Ïk{ÚÓöú%MÏË}NO϶^ûßçn‹'žxË–-ƒ¢(8}ú4Ο?P(„šš<üðÃÈË˃¢(èëëÃÅ‹ Q\\ŒuëÖaÅŠÑý*Š‚³gÏbhh‘H÷Þ{o Ÿ%Ép°–Gܸq£££Xµj•¥ö»víÂã?ŽÏ?ÿ€žžœ|kÖ¬Á[o½eë8‰2ÑÉ“'qß}÷aëÖ­X²d à—_~ÁÄÄžyæ´µµabb}}}ŸÏ‡²²2lÞ¼Ï=÷xàüüóÏ‹îóôéÓFKK ¶nÝÊ¿3b{Äèè( °°0º¬§§ëׯÞ:;;£ëZ[[ÑÖÖ†úúz,X°3338xð víÚ…åË—£ªª @SSS8uê”åãx÷Ýw±víZTVVâ7Þ@yy9Ž?Ó&cÿþýxûí·±jÕ*TVV¢¥¥%zìÏ?ÿ<êêêPUU…mÛ¶áž{îÁÀÀ@tûE‹áwÞÁš5kPQQööv\¿~—.]rtîˆ2ÅÊ•+±zõj,^¼~¿333øóÏ?±a⸸ëׯDž ¢Û¬^½X¸p!Ö®]‹ÒÒR\½zÀÜßêÀÀ}ôQ,^¼ .Äc=–¢gGzØ5íj÷ñ¥K—P^^hooÇK/½øè£bÚ744ÄÌŒŒ  aåÊ•Ñe………xðÁcþhÍäääD§}>bªYõ±‚Á n—õØØŽ;†?þøÆôôtLŸïÎÿ€K—.Eaa!‚Á åã$ÊD•••1óãããˆD"8räHôoSQ”˜ªvzzCCC¸rå nݺ…7n  ˜ëi‹D"¸ë®»¢íµãä b(**ÂÒ¥KÑÛÛ Ùüüüèõâ¼¼<Ãígff ( ÂápLÈi÷áDnnî¼ÑÚ“““æ^rsscÖݼyíííØ´i^}õU,Y²Û·oG86|¾8ÍWPPxöÙg¥ïš…Bøî»ïP[[‹¦¦&”””àèÑ£ˆD"æ*âH$‚H$¿1c×´‡lÛ¶ _|ñ…­ VU]]œœüþûïÑeŠ¢`ppË—/0W!ÿõ×_1Û)оví¾ýöÛ˜õ¨««‹YVUUEQpîܹyÇqîÜ9Œ¡££÷ß?ŠŠŠ¢ƒOˆÈž’’äååÍë•R]½zÓÓÓhnnŽŽÑ¸~ýzôŸå²²2À•+W’vÌdƒØCZ[[ÑÜÜŒW^y‡ÂÐÐ._¾Œ'Nà§Ÿ~2ü0¢¢"<ýôÓØ½{7ñÏ?ÿ`Ïž=¨¨¨ÀC=hllÄ‘#Gð÷ßcdd|ðALׂ 000€3gÎ`||===ƒØ´iSÌc•••aãÆèêꊎÖº»»qøða?~===ðûý¨©©ÁÎ;ñä“Onÿæ›oâÓO?ÅÎ; …ÐÜÜŒÏ>û,Ú%õúë¯#àÅ_DUUZZZbH•””`óæÍؽ{7®]»†††ìÛ·OÚ-þÞ{ï¡»»Û·oÇìì,êëë±bÅ ÔÔÔ ££ûöíÃÞ½{±aÃlܸ1±'Š(‹¬[·~¿¿þú+&''QTT„ÆÆFÜ}÷ÝX´hš››qöìYœ9sÕÕÕ¨©©‰Ùþ‘GÁo¿ý†ü¨¯¯5yƒ[ Ìö+[Ÿ£Y®Þû$óÚ6>Ívê¼z˽}ï¿=­Þ—ÍÌÌüjã¹R¯Å¨×gC¡&&&°lÙ²D=DÒ¼üòËhkkCkkkª…ˆ""'ÒºøKU×tZŸ4""ÊH)ɦt»FÌ'""3i•^b''3­~DDdKÆå‚׃˜ˆˆ(£1ˆ‰ˆˆRˆALDD”B^ âDõãî'''g4ACDD báµ9)‘l^ b+Ä1Y'~®hxvvö.9tûµ9 ùk·–Ùë~ZñjG îÅi½›¢¹Wo0;::zÖ̓'""ûn¿6«ßÍ*~¹ƒø%²$Ó²{OIe[=!NOœì?&@hÿþý¿8Ü'¹äök³úMK€qåk•ÛY77?Òʾs$Óâ=0ÿëÅ›Þ×!æjnÚ¯C,ù÷ßß)..~ÚÞSš/“¾‘ˆÈˆ›_ƒ855u´¼¼ü“ˆýúðæfôõ‡zÕ±rçQ +d+AìJX{©k:žï€Ôþ  3­½ÝêîîþóC%"¢Dºýš| Ö»œÅ%ý{…r3ˆãíFÐÛÞÎ…ym@k¯3„:;;/ô÷÷#%H ³³óîtK‹•®À5Ë·»¸móRElDïDËNŒÑÀ-íütSSÓ±‘‘‘O\9b""2522òISSÓ1Ó°×í¬¥— ž©z¤:ˆã9IzoU’uI«óÚk €›µµµ_²2&"J¾þþþ@mmí—n"öµY} `þÚÍr§RØ©b«Ì† ëýP´ÿQ…5óê: `ª©©é‡@ ðÂÔÔÔQŽˆˆ4¦¦¦Žššš~0ù`,ík¶Ñk¼¸Lvïi¹.ïßéÈií´Ñhjq$µl¿âkÙ>½½½ã]]]ÿ ‡ÃÇëêê.…ü~ÿÅž€¹Ô‘H³³³(--µºQZ˜œœDAAAtÄ´:ZÚlÔtNNÎèìììï—/_>üñǼeË–z{{¯bnp–Z)’im0òP6»TiÖ&åÜ|û’ÕÇ0 Pñ^ SÙ2Ÿæˆ}+“ì^œöknâ¾’q¾ˆˆ2ž æc©7Yð†uîɾôÞ#.ƒÁ½x¬fÏÅ~7wnQÖNm§½‡°LÛVÁ\€ªÓf¡íÊÁ¼’&""}zA)ûÔC½ð•UÃF!.³sœ)ã… Öc7 Õi5€Õy•úUæ~Ðêú\Äþ’äàNpk+l€ALDd•6e£Ÿeƒ³Ä±<²Yb §Màêñb˪[u^[劕°8­RÛ©?\ ¶ÛÂzñ“ºÂ`×4‘]ÚJVCY¬ŠÅJØh¤´ÑubÙ§iéͧ\2‚ØjeÏþù!¬°ÞÚ{í¶¡Xk»ÁµCDDr² Ö¬‹Z6­Ý‡±ÛÁêzp'+T9z˜_¡ÊT‰ÝÊâà-ÙgTëuGk߿Š&"2¦×•¬×M- f ¶š÷#Û·vHÖM[y.®ðb×4ଊÖë®îT¹jEAl·6¤YÅǨ"V׋¯¶b—¶¸o'é¹niÀ[±ØÎIUlôö&±2ÖV¿²íµU°øÁ' c""91ìÉ´Ìâ5aY;£kÆnUÃvÚ9–¬ŠØÍëÄÚJX%´¥R„õÚªXmïÑÕzûÑ>Q¶Ó{¿°A f³€Õ».OelUR*h¯vMòÑÚeÚ·)‰Ûˆ¶ ™—ua«mƒy3 g"ÊtvÊh€•¬ûÙ,”õßN5ì)É DuO‹Ó²·ɺ­–› Ìb¸9# EÙ +½nf+ÝÏzoWò|·4àþgM‹ìŽžçõ‚Q\‘,+j½c‘ýR0ˆ‰ˆœ‘}µ!`\µÚ a½°Œ·NZì€qRkçͪb½åV{éMç€ALDä”^—²Qk—™U½fÕ°¨žª†oVIJvfa)V­F•±:-žd«2™Óh%¶ÌÃÚi§…TTzn…1`^ëm+«zõÃ艈²•“.b+Õró&DDäżÉ<ñ©§BðnÉcq[/Ÿ"¢tOwµ¸do믇M¼Ç—ˆççõsDDäu‰Àx÷áÉÒ#d¼¦ép’ɰóBˆ»*Â$QÇšNÏ™ˆ(%*8=Àªt %Žt&"Ê\‰Ò1ŒØÍLD”9¼Ú4é@n{:Ÿ""/s3$Ó*€U™8É|™p¾ˆˆ’!™¡˜–¬Ê”`ñâóðâ1ÅËçÅc²%ÓÂ"Ӟɥ}«29¸2ù¹e£Œ _­l«lxŽDD™,#X•m!•mÏ—ˆ(]etøje{0eûó'"òЬ ^ƒ(ÏQrdmðŠ<Öð<9ÃÀ5Á€I<žS"Êt W""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""J¬ÿ‡®3IIEND®B`‚Portfolio-1.0.2/screenshots/en/5.png000066400000000000000000000320601476103320100173010ustar00rootroot00000000000000‰PNG  IHDRâ6*Ù€³sBIT|dˆtEXtSoftwaregnome-screenshotï¿>-tEXtCreation TimeFri 09 Feb 2024 04:06:23 PM -03~vÌ" IDATxœíÝœÜu}èû÷lv³É’Ÿ$Ai¯P{­"õ„”Òã/xŽõz¡Õ¢‡ä(Q{/T£½åxKµh+ˆJ)Ôñ*ò#´ÅKk‹T…¹4DÙdÙÙÝûG2ËìdfçÇîì{f÷ù|<ö1¿¾3ó™Ìk?ßïw¾@²0 yOÙn,{³‰h4ÆûÐÑ®C`&ò~Ì Þo®‡g®¿~€N1gÃ<×B4×^/@·š3až aš ¯`6›ÕQžÍ‘šÍ¯ `.š•Ažm±šm¯€êfM”gK¸:ñut☦¢ã׉cjÊlˆÅL¾†Ùð~Ì„™ dWǸ›ÃÒαÚüøsÑX´7š]änŒM;ÆÜӆǠ¾Ñ67À\ÕJÇ*Ngò¹g\7f*cl5¾.ß ïÀLh4zÍÆq:¢ÜÑAîô´:¾f\o9ÛZSop³o5ªãNñT"\モÝ^¨q€ÖÕ8?ÙrµnŸU1îÔÐLe;n£‘­¼¾•û0¹zÑ­u{#±žÊöçŽÑ‰i5Â͆´ñíÄ÷ ¦;‚µ¢ÜjÈ[ÇŒë´p´ºSÕd3Ýj×Õº¾ÞóÔ[€©­†n6È­ÎŽ;&Æ•fÆÓl„«¸Þ}§ò•' e£«¢«y:c,ÄU4áépµèvÒûÐͪ͒™ù6䮋q§„¦]®üÚQ­ ·²Š»žNyo¦Ëtn­7S®¼\ù5¨YãNˆE;"\¹Üdnf¶<Ù246{lÖ;Ùý+#;+bÜ Aif µ¬Ñh„k¸^hР5õf²µB[+¸Æ¸™?&1§C<Ýî©q}#nfF @ch³A­q}ÔX¦ž´g¦™CPÖ[e[ËÃ]ë4b†c:âÊÕΕ·ÕZ¾ÖöâZî‰þ[n¹å5§žzêYýýý' …%…BÁl` ÆÆÆFÇÆÆžº÷»ßýîWÏ8ãŒ;#b(öE¹áB<èZ1Ž˜Ýñ§¨xÊZ.yǘÉÙ[3«¥«­~nækHµV3÷Äs3Ýye× ÜrË-§­^½ú#óæÍ;²Ñ@óFFFÞ²eË'Î8㌈ˆÁxnõõH<7s®¶J»Úöæˆú_{*×qùè´W›Õ–.×Zý\m/èjÛ|K³ãy11Æ;vìøø²eËÖ6ùz˜‚;wÞ¸|ùòƾ—G¸t¾r‡®Ê0Ç$×EÔqT¹¾š9âf#\¹LùNX¥Ë5#¼{÷îÏ œÚÔ+`Z ~÷ ƒ:/&qù^Õ•{S×:èÇtÌŠg$Ä3µý³Á¯Ü&\¹SWùl¸|'­…O>ùä'D ÏÀÀÀ©O>ùä'"baLÜdXþ™]ù¹^ë°ÆÓmF&«´#R½´j͆£Êåj«¤Ë·÷Ý|óͧ-_¾üÍÓ3tZµ|ùò7ß|óͧÅÄo¬T~~W pµÔÚŸ¨RÇ|Ãe¦Òêjéˆê‡«¬ö†—φËÿ#öÆÄ´z#â áááoõöö>¿Ö`öìÙCCCQ,úÔõööF,\¸pFžhŸÍ+‹?ïëë{}DìŽ}{O—ï´Uº\¹ZºÖÁB&;,fT,_OÛWOwÊŒ¸V¨ëýÁ‡ÒåZ¿UîQ=/"z¿õ­oZ+Â###±sçÎØ½{÷Œý#Šˆ(‹±{÷îØ¹sgŒŒŒÌØóÓÇçGëz{{Ÿÿ­o}ëÔØ7Y*?¶CDõ£F•ËQåréþÕtĬx&B</´Þª†j«#*ÿÕ.ÏÍk^sV­'Úµk׌þªT,c×®]iÏ´ÎçÇÔìÿlžÕ¿‚Zkíh¹FWK7£í±î”q£*ªw]µÿ˜óN¨öà{öìIýGTR,cÏž=ÙÚàócêö6—φ+7;6ò¹_y¾ãurˆÝ®\íºÊÃRNØY«P(,©ö`CCCÍŽ±m:i,@}ôo¶“ÆÒŒýŸÍ•;k•”ï¸uÀ]yø)°M:á—ÍîÑÖÌöªGתuØÊNøm¶¤“ÆÔ×Iÿf§s,###144ÃÃÃQ,cllß¾K…B!z{{£¯¯/úûûcÞ¼yS~®ýŸÍµŽŠXÚø]ë3¿Þ1¦›½~Æ´;ÄÍüRoÙÉfïÕ6âW>nµÝߨbdd$kÎ®ÇÆÆbxx8†‡‡cpp0úûûc```:‚\íïÆW*¿®VDKǪ®¥™·5Ö0#®¦ÞºþÉfËÕ¾ÚTízªØ»wo<óÌ3MÝghh(†††bÑ¢E±`Á‚©<}­ÏìÊëÝ–_—>û­¦›g‡º|<Ùö"bpp°é—{æ™gbppp*C¨ö™ÝÈW”ºö³={F\o;pµÓZ¿ÕúºRµßª¦Í}÷Ý7ÞxcÜ{ï½ñôÓOÇÒ¥KãÄOŒµkׯ 'TÝ9 #íÝ»wªˆ}1ïééiuf<Ùg|ùŸ?¬¶ê¹üöz¯¸ò9ÓfÊÙ!nTÇý¦322W\qE|ãߘpý¶mÛâŽ;îˆ;î¸#Ö®]çŸþ´ìÄÐN###Sš Wzæ™g¢¯¯/ûó¯#WEWjgˆ[çtï†Þ–ˆ—"Üßßï|ç;ãôÓOU«VÅã?[¶l‰k¯½6n¼ñÆ‹÷¿ÿýí]ìõ¯}<þøã5o?÷Üsã}ï{_[žû´ÓN‹… Æw¾ó¶<>õ­Y³¦¥ûÝvÛmÓ<’çLÇL¸Úc.^¼xªÓìç}½ð¶ç¶E½[fÄ­jK„ï»ï¾ñoÚ´)Ž;î¸ñÛ?üð8ûì³ã”SN‰uëÖÅæÍ›cõêÕVS3Á©§žO=õTDDÜÿýñè£ÆÉ'ŸË—/ˆˆ_ýÕ_ÍsLé+JÓmhhhºö¤î¸µ¢Ói¶„xFÿ#mÞ¼9""ÞùÎwNˆp¹ãŽ;.ÞñŽwÄÕW_›7ob&ذaÃøù‹/¾8nºé¦8ï¼óâä“ON3¥3ÛVÔ‹ðš5kâ¥/}i\|ñűtéÒˆˆxúé§ãâ‹/Žþð‡“¾žRŒÛ¤+V=דâFãÙÎUÜ-ùÑ~§Ÿ~ú¤Ë­^½:®¾úêñå¡Y§vZ¬^½:?üð¸ñÆãðÃK/½4¾ð…/Ä?þã?Æã?|p¬Y³&Î?ÿü˜?~DDÜyçñÅ/~1¶nÝtP}ôÑñ©O}*–,ÙwP¹áááøô§?·ß~{‹Å8óÌ3ã½ï}oæK%Ñðð𤷿ð…/Œûî»/Ö­[›6mŠˆˆ}èCñàƒÆQG5¥ÇnÂTZÐH¬Ó¢>[fÄ3êé§ŸŽˆˆU«VMºÜ!‡1¾ ZñÍo~3""^ð‚ıÇýýýqçwÆÑG'tRÜ}÷ÝqÍ5×ÄüùóãüóÏG}4Ö¯_‹/Ž·¾õ­144?þøx„#öý?üw÷wñüç??þõ_ÿ5®¼òÊ8þøããÔSOÍz™$ªw$®O~ò“±nݺغuk¬[·. …Büô§?Ã;,6nÜ8¥Ç¦;CÜÈ÷ÉÚjÉ’%±}ûöxüñÇãðï¹\igœò@hžð…xùË_>~ù¦›n?ÿÈ#ÄÚµkã®»îŠóÏ??~ö³ŸE±XŒ—¿üåñ¾÷½/z{«ÿ3¿þú룿¿?®¼òʸòÊ+ãž{îâÒi;k•[Y˪U«bÓ¦Mã1ŽØ7ѸôÒKã°Ã›ÒcOƒÊ™l×­®î´zLWTÛç“N:)""¶lÙ2ér·ß~{DDœxâ‰í³\ÿ„GìÛÁëcûX¼éMoŠw½ë]1666¾æå%/yI,Y²$¶lÙ§Ÿ~z\tÑEñãÿxÂýW¬XýýýñÜÿŸ¥5=PM__ßø¦ˆˆùóçO¸6“ë´qW8á„bíÚµ144ëÖ­‹k®¹&~ñ‹_D±XŒGy$¾üå/Çúõëchh(–,YGqDö™E~ðƒÄÞ½{ãÕ¯~u¼â¯ˆeË–EÄÄmq«V­Š .¸ n¼ñÆ8çœsbtt4î¾ûî¬!Óájm¾(Ù°aÃx„7mÚ›6mŠ£Ž:*zè¡ ßhå±1#nÙùçŸccc±yóæ¸úê«ãꫯžp{¡PˆeË–ÅÎ;ã /ŒË.»lü;¢0‡zhDDÜxã1<<?øÁ"â¹¼Ÿüä'ñ‘|$^ñŠWÄA4¾‰ä˜cŽÉ0¯¯¯oÒ½›|ðÁ8æ˜cbãÆã³ßM›6ņ âÁ¬ûØL®ÓCÜÊ:Y2oÞ¼xÿûß«W¯ŽÍ›7ǽ÷Þ;v숥K—ÆI'k×®#Ž8"Ö¯_[·nc¦Í)§œï~÷»ãë_ÿz|ûÛߎ׾öµqä‘GÆý÷ßû¾.rðÁÇÍ7ß»wïŽÃ;,Ö­[¯zÕ«’GNI§í¬Õßß?鑵ª=ïÁŸÿüçzìiÖÊÎX½W;£Uï±ký¥¤òÛzª\®ö÷…«ý•¥yûO{÷Ÿ/.»«Ú€¶mÛVgÈÍ{ê©§Æ÷4<òÈ#cÓ¦M±bÅŠ†î»råÊiÐÍ|~ÌDˆ›ýüصk×´]«¿¿¿éC\ …WEÄS1ŲÓÑýçGË~Æöÿ”Ÿ²ë¢ìºÊËåËGÅmµ´%æY!®õ'¬f]ˆ#"vîÜ9>3~Ñ‹^ŸûÜ纟C÷h×çG«šýü‰;vLë–/_Þô΀É!Ž×5r[˺}g­®ØoÙ²eq饗Ƌ_üâºß¹È0oÞ¼X´hÑ´=Þ¢E‹frü®hA-íÚFÜÕoJ;,[¶,®¸âŠìaÔ´`Á‚ò_bhõowº¶lkîö1Óh```J3ãE‹µó<ÌJ¾×43lÁ‚Ñ×׃ƒƒ ïÀÕßß?]òpÎb0oÞ¼X¼xq ÄÐÐP G±X?W¡PˆÞÞÞèëë‹þþ~ž!.ÓÛÛÛ1)ÄÑh »ÌÖÏyóæYÕÜf¶—iÃÏ[ÖIcê뤳4êâ2 .숙hooïú:›ÏZ%Ä/^œú©···é#ÑÁç­Èÿõ­ÃÌ›7/–-[{ö쉡¡¡ÛæÓÛÛýýý~“….æóƒVq .ô?5П4êiH$ÄHˆ ‘@"!€Dsr¯é‡~8FGGë/ÀŒéé™›sÃ9âqh§ÿOJ0½sôOÙÏÍ_? C1$bH$ÄHˆ ‘@"!€DB ‰„ 1$𓇏lÅ¢ùcñ¼Å#ÑÛä¯.»ã‰=14ê­à@fÄ :¬…GDFGbÑÈ/£ÞÈô €®'Ä ê›Â;5::‹F‹þžá鳂ÏÑ‘‘X4ú¸0Ï 1 ’=ˆZðøãÅèèè¤ËôôôÄ!‡•^std$Åã=‡ÄÐh_G @7ârÈa /» Aì~晿NŒ(â43#îíí‹+VÅÐÐÞ–(ì‡w 1À\&Ä-hfFÑÛÛ½½‹ªÞVØ1Võ–îõG|w¬~Ý[âµgü§–î?üì³166óû´ey€N"Ä-hj1MÛü×WÅ/ýyüÁúKÚ²<@'â,ÓEˆ[°mÛ/£XlíHY½½óbåÊC§u'œtJ¼çýÅâ¥ËÆïóš5oˆþî–øÙO‡?ÿqÎyƯ¼è¸ªã+‡ãú/ÿyüÓwoág‡â”Wžg½ç‚X°`áË~óo¾7^wUDDüóÿ{Gü‡ß~s,Y¶<î¾ëïã/ÿRÌ›7/öîÝÿíüwÄïœõÞxú©,îùë¦õýh'!nÁt‡t*v?³+>³ñÃqöïýa¼ò´ßŠÛŸˆgŸŠˆˆÞÞ¾8ôðçÇ?ò©˜ß¿ þéÎÛâÊË?¿zìññ¼#ŽŠˆˆ]O?7|åʸäÓ+V_ùÂý×wÇoü¸à¢ccqù'7Äu_þ\¼çý?ïÕŸ¿,>ö'Ÿ‹C=<¾ùõ¯Æ§>ú‡qù_þm tà¶ð/ÿùgb×Ó;ã’OÿEŒ‹ñç—<þêKWT æÏ<;öª¹XŽïÝy{|ç¯Ë;ãë_ûbõÂcâ´5oˆˆ8`y€n"Ä-è¤ñŽíODqx8þ—W¼:Z´8Z´xÂík^ÿÖñóoúÏ¿ËMñ“ß?âÅK–Æ›Þö»ã³Ù7¼õ]ñÿpk¼ùíçÆÒeGDÄo®yCÜñoLxÜ÷¼ÿÃñâã_g¿çãÿrWüã?ܧ¿î-–Û3¸;îøÎæøì—n¼ß9û½ñÙOnhxæÚÛÛ¿÷þÇÿõü×8üù/ˆ;oÿVlüì—}‹:š· “fćùÂ8þ¤SâC¿ÿ®xù¯¿:^{ƛ℗ýÚøí»žÞwýÝ-ñÿÝÿÃØýÌ®øåcij_¥*ž;ÀÚGC{öD,{{+žù¹#• =qìq'Æc<|Àø{äá‹‹ÿÛÿ…ý÷)ŽcÏàî¦^ç±Ç¯úÍߊM—¬‹ó>ø±X¾bUS÷èTBÜ‚Nš÷ôôÄEÿlü¾³cÇö'â—þoþÏç°ìÊU‡Å‚…qÏ÷¾;¾M·ÅbqÂåGy(nû¾ïü/Wþ²8áe¿6a{tåòÝBˆ[ÐI3âgv=÷|ïÎ8ñå¿-ZÜ÷¯Ñ¿`A,Z¼4öìŒÁÁÝqû·¾ÿëkNûá÷#"bÕ¡Ï›Òsö÷÷ÇOÿçqÿ¾G}LÜòÍbïžÁxÅ«_‹–,‹'~ù‹‹y½½ñÆ3ÏŽ¿ºêŠ8xåª8îÄSbçŽmñôŽqô‹^ÿ}ãEñ«/>!ÞxæÙqðÊCcËÍ7ƶÇ‹ÅK–FßüþøóÏüq¼ñ̳â o=+¸ï_ãkñÇ{þð¢ªË›1ÝDˆ[ÐI3âÁÝÏÄ?ßuGüÕU{öìŽçqT\ðáÑ7~þüÄ;Îýýø›¯}1®ÿÊ•qÒɯŒ_ûßœòs ,ŠW¾æôøÒŸ];wl½ä¥ñ‘O^½½û×ùºµïˆ¿¼âOâçm~äSñæ·óç÷Ç—¯¼<¶?ñX,]vp¼ñ̳ãè½$ÆÆFã‰_>¯ùdÃã[½zuœ}öÙmÐù{ì±øô§?÷Þ{o GDD___¼ìe/‹ /¼0V­Z•<ÂîgF<W]uU\wÝu G8bß²®½öÚ¸úê«Û8²}®¹æš¸á†Žpľñ]ýõñÕ¯~µ#ºÅe—]÷ÜsÏx„#ö}ðî»ïŽÏ|æ3‰#›=Ly¦àÖ[oˆˆÏ~ö³qüñÇ7tŸ¿ÿû¿?þã?Ž[n¹%~ï÷~¯Ã‹;î¸#""6mÚÇw\C÷yàbݺuqûí·Ç9çœÓÎáÁœ¶sçÎØ°aCÌŸ??.¿üòìáÔtß}÷EDÄßþí߯âÅ‹#"âÉ'ŸŒ·½ímñ£ý(sh³†OAi&Üh„wïÞ_ùÊW&Ü·JÏÑh„Ë—‰ñÁ\µsçÎX¿~}lݺ5Ž=öØìáLpÁÄ¿ýÛ¿pý[Þò–®Û»wo¬Y³füòK_úÒŽþ¥¢SY5=CŠÅb\rÉ%±uëÖ졉žzê©ñuÔQññ<{H …”ûÎefÄ3`ll,.»ì²øþ÷¿+V¬0Û„9ê©§žŠuëÖGxÓ¦MqðÁgk‚ÒŒ¶4Ó½í¶Û&]¾Ñå¨MˆgÀUW]·Þzk,\¸0>ñ‰OÄyç—=$ ÁE]4¾V졇Š·½ím“.?00§œrJ¼ç=ï‰#Ž8b&†XÓ–-[bõêÕœg꬚n³oûÛñÕ¯~5zzzbÆ qÌ1Çd 胃ƒqçwÆûÞ÷¾Ø¶m[êX6nÜXõðijÏ>7nŒeË–EDÄÝwß=¾šçø@üÆoüFæd7nß>|ôÑGÇ¥—^:þyQͶmÛâ3ŸùL|ï{ß‹ÏþóñÑ~tGËL1#ž&Ï>ûlüøÇ?Žõë×ÇÎ;ã'?ùI\rÉ%122guV¼þõ¯Ï"lÙ²eq饗ÆÑG[·nÿ¼¨eåÊ•ñ| ""þå_þe¦†É âi²qãÆxá _[·nuëÖņ bpp0Ö¬Yçž{nöð€Qã 6d‰dB~8»ôåJ1>öØc£¿¿¿ærO<ñÄøæ­SN9e¦†Ç k×6ⱈ˜sõYºti\z饱aÆ( qñÅG___ö°€´lÙ²ø³?û³š·—XcñâÅm?_=å3÷9<‹kǃvûŒ¸-oJ£V¬X1á(4¥\W\qE,Z´è€û”–]¹råŒïhø>÷ßDÌÌø€Ú …B Äi§ú§:ã__*}~”qYþu¥Òù{ï½w²‰R[0UY{MÏŠógœ×^{m\pÁ-Ý·ÝV¯^×_}¬[·®éûž~úémШìCE®Y³&®»îºñÅ&3Ÿg3$%èÝöõ¥Ž xé"ÜrË- -kåÊ•qÆgÄïþîï¶shqöÙgGľ/ß73¾ÓO?=Þõ®wµsh@‡+íd:ÙŸy]±bEüÖoýV'þ˜®š!·3jõ»òöBÙu…ŠëÊ/÷TÜÖSåü¼ý?=±ï—ye§KÇÆÆîªLö—åëÙ¾}{'¬þ™”ÕÙÌUöùQíßb7Œ1"¢P(¼*"žŠˆ‘ˆ(–Žî??ûB;ºÿ´ò|”]޲ëË/GÅuQq[-¶Àl#ÄHˆ÷ÈsP§…xºÖ¿Oú8ccc£•×õövÛ~k@§è¤ÏZcé†1Vûl®\dš†ÐQ;suZˆ1ÙÆõj·Un˜{ºòAçÏŸïXSà½c.›ìèX3­ÖXºaŒû?›K;c5ºSU³;\uœN qµ½ÛJ§Õö„«ö3ZvZú‰ˆ|îe:è é{sŒ÷޹láÂ…1ãìíí… V½­Ƹÿ³yxÿÅòÏîÊ=£kýD•óÕN;Jfˆ}CZ}ãªýÆ4Å[o½õ¯«ÝaÁ‚¶·``` ,X= HµxñâÔÐõööÆâÅ‹']¦ÓǸÿ³¹ôU¥ˆÉg¾jwk¦,ó{Ä•ËTûqIOTÿnqµï—.÷Äsß'.ÿqoD,þ›ÞÞÞçWÔððpìÝ»7ž}öÙëŒ_ :í{Ä…B!æÏŸ ,pñÄ´råÊÿÔÄë`šmÛ¶í¦U«V]{¢±ÙpåiÄq.ß$1ùl¸ÚåJm w§î¬U©ÖVí™lÇ­òË{W­Zõ‰]»vp¸KfÆ®]»îZµjÕ'"bo4·Ú¹\«qíÙ!žÊ›Të«JÕVI—.—ÿV5{–,Yrá¶mÛnšÂ8hÁ¶mÛnZ²dÉ…±o&\þÙ\ú SDýÏö(»¾U©ÁÎq£Y¥PëºÒoTå /?xøîU«V}ü†nø`±XüyÆ@™b±øón¸áƒ«V­úxDìŽê«Ÿ«ýq‡r“Ÿ£¿®T){qå2“íE]¹¸Ú_bª¶'ui;qùioLÜ~Ü}ÑÝu×½òu¯{ÝïtÐAÇ …%…B¡[~YèHccc£cccOïÞ½ûþoûÛ7¼ýíoÿ§Ø·cÖpLœÆÄ¿´T~ZoUuåöâÒuQåúÉÎ×| ,Ó’™8Òtí°UíO"Öú3‰å‘.ßi«ÚiåùÞ²ŸÊÇrø(€ÆT²ÙÒOùÚÉÊó•§£U«Ö7c*¯‹IN+ÇZïµ´E'|}©Þ×–*—+?ŠëÊ—-}¥©t¾Þs”¯Ê.FíÐP_­PV;êa­ø–·ü|­ç¨¼®™q¦é„×Òl KçË¿S\þ—~£*ľÿÐ¥ÛçÅÄÿI ñ\¸ËgØB Шò V[¥\mç¬Ê}yªí˜Uä® n-âj³ÛÒåòYnåL¸ò|Iù>J1î©8-¿½òH]#aÕ4@³ªm§­Œr嬸r&<ÙžÒÕÎW>gårÕ.§›‰7:³ÊãGáR€Kñ.?-¿ïXÅr•3âòÕàåÏ@uÕf°õVQW;_~ÿˆê1nwXÛÊtî=Qêòë*÷ºî‰‰;aM¶Çuåc7úZæ²Z«’'Ûó¹2ÆgÓµöŠnd6Ü‘{K—tâªéˆÖfѵVWG<7Ë-͈Çbâ*îòH›LÍd3âÒí•3ÞÊØV®Ò®|ìVÙq«¥#:kF\¹\+³âɾÞTíûȵ¾‹\¾\åùÊqðœÊØV9_æÊmÂÕ–›l›q»fÃÍ,ײ™š·s;qùL¸¤ÚN[%£·—ÏŠKË÷Äs{V×zœòç˜ëj}NVþ…¤Ê0× l­íÂS™7jFfкj:¢úÑåוM©ò>•;lE•ËÕVa—–är=â ÌvÍj²¬ª­~®åZÏßÌl¸£Ìd4¦kõtåùj_-ª¶Úz²ëëí˜%®­©Åj;YÕZÍÜÈêçZ_WêøÕÒûf1“šÝ{ºòr­0V^?VåºÊu­±TûŸBˆZSíOFL>km&µb9ÕÙðŒÍ g:0­ÌŠË/×›׺¾‘½j/„´ªÖ*åÉ\~]½Yo½ÙpeP;j6Ñ™3âjËÕ‹eå¬u²™qé|å›Üè €újíhU¹LDýX·á®1ÓkWŒ#êÏŒkÝ·Ú¬·ÖsL6F€¹ª•UÄÌ–§᎟ Gtvˆ«-ÛèŽ\͆ºÖÎ[µÆÑ,ÁºÕT£4Ù,¸òöFCÛÊYÍîé=c²1ݳâj·µêj·W[€ÆL6 ®¶L½Ð6Û®˜ Gt~ˆ«-ÛjŒ›½}²åhLµðF•ë‰l»#Üì²Ó"303ãÒåV¾ÕÌu<§‘ð–_×L€k-ÓÈmõ¤ìä5Ó{MWjWŒ½®Ñ°Z]•0[µºª·Ùmºõö†îêGä‡c*!®¼®™Û'[Ö,`ú4:®¶üT"\ëºZælˆ#¦ãÊë§äòÛ:áý˜ jm+.¿½ÞùFoŸìú©.;í:!4ÍŽ¡Õ×[¦^”Y€Æ®Ñ̶ÞV—iÄœqÄôŸü¶éëtÌÖf«éšu6îfâ^OúQ¸:)íˆq£[/Þô>t“z³ÖFBØê »™ÇMÓiie<íX¥lû0Àô«·¸ÚòÕÎO¶\+ªÓbÓêx¦cÖÛ®ûÌEÍÌx§rßVƒ*Ä“hgŒË—Î×Þ‰ï#ÀL˜Î MeÆÜìótŒN ÈTÆÕÊ}{ê/@ŒÖ_äS iGE8¢sC1ó1®¼o'¿7Ýh*««+c¦ïÛ6›©Žo:^_§¿Gn:8ÕÇèÈGtGd:5¦ÝðÞ̤vÄ®"ÞVÝ“ék7½f€¹hºÂÙÑ.é¶(ÙÓ`öšî=°»B7ÆÈjf€Ù£SWgϘnP;ÇÞÍï @'kg$»*À%³!83ùfÃû0f2Š]à’Ù–N|8&€©èÄàu☚2Ûb1Û^Õu}€Kfs¸fók˜‹fM|ËÍ…XÍ…×0›Íʗ̵HÍµ× Ð­fu|ËÍõ0Íõ×Ð)æLx+ ÑDÞ€™1gÃ[Ixã}hàÖ!0ÓÏ{ Ìvâ Àôúÿ“~ ùò_IEND®B`‚Portfolio-1.0.2/src/000077500000000000000000000000001476103320100142535ustar00rootroot00000000000000Portfolio-1.0.2/src/__init__.py000066400000000000000000000016201476103320100163630ustar00rootroot00000000000000# __int__.py # # Copyright 2021 Martin Abente Lahaye # # 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 . import os import logging level = "DEBUG" if os.environ.get("PORTFOLIO_DEBUG") else "INFO" logger = logging.getLogger("portfolio") logger.setLevel(level=getattr(logging, level)) logger.addHandler(logging.StreamHandler()) Portfolio-1.0.2/src/about.py000066400000000000000000000015231476103320100157400ustar00rootroot00000000000000# about.py # # Copyright 2023 Martin Abente Lahaye # # 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 . from gi.repository import Gtk @Gtk.Template(resource_path="/dev/tchx84/Portfolio/about.ui") class PortfolioAbout(Gtk.Box): __gtype_name__ = "PortfolioAbout" Portfolio-1.0.2/src/about.ui000066400000000000000000000230631476103320100157300ustar00rootroot00000000000000 Portfolio-1.0.2/src/cache.py000066400000000000000000000035521476103320100156750ustar00rootroot00000000000000# cache.py # # Copyright 2021 Martin Abente Lahaye # # 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 . from gi.repository import GObject from . import logger class PortfolioCache(GObject.GObject): __gtype_name__ = "PortfolioCache" def __init__(self): super().__init__() self.deactivate() def has(self, key): if not self._active: return False return key in self._cache def retrieve(self, key): if not self.has(key): return None return self._cache[key] def store(self, key, value): if not self._active: return self._cache[key] = value def activate(self): logger.debug("cache activated") self._active = True self._cache = {} def deactivate(self): logger.debug("cache deactivated") self._active = False self._cache = {} class cached(object): def __init__(self, function): self._function = function def __call__(self, *args, **kwargs): key = (self._function.__name__, args) if default_cache.has(key): return default_cache.retrieve(key) value = self._function(*args, **kwargs) default_cache.store(key, value) return value default_cache = PortfolioCache() Portfolio-1.0.2/src/dev.tchx84.Portfolio.in000077500000000000000000000022741476103320100204660ustar00rootroot00000000000000#!@PYTHON@ # portfolio.in # # Copyright 2020 Martin Abente Lahaye # # 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 . import os import sys import signal import locale VERSION = '@VERSION@' pkgdatadir = '@pkgdatadir@' localedir = '@localedir@' sys.path.insert(1, pkgdatadir) signal.signal(signal.SIGINT, signal.SIG_DFL) if __name__ == '__main__': from gi.repository import Gio resource = Gio.Resource.load(os.path.join(pkgdatadir, 'portfolio.gresource')) resource._register() from portfolio import translation translation.init(localedir) from portfolio import main sys.exit(main.main(VERSION)) Portfolio-1.0.2/src/devices.py000066400000000000000000000373721476103320100162630ustar00rootroot00000000000000# devices.py # # Copyright 2021 Martin Abente Lahaye # # 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 . from gi.repository import Gio, GLib, GObject from . import logger from . import utils class PortfolioDrive(GObject.GObject): __gtype_name__ = "PortfolioDrive" def __init__(self, object): GObject.GObject.__init__(self) self._object = object self._drive_proxy = object.get_interface("org.freedesktop.UDisks2.Drive") self.uuid = self._get_drive_uuid() self.is_ejectable = self._get_drive_is_ejectable() self.can_power_off = self._get_drive_can_power_off() self.model = self._get_drive_model() self.size = self._get_drive_size() def __repr__(self): return f"Drive(uuid={self.uuid}, is_ejectable={self.is_ejectable}, can_power_off={self.can_power_off})" def _get_drive_uuid(self): return self._drive_proxy.get_cached_property("Id").unpack() def _get_drive_is_ejectable(self): return self._drive_proxy.get_cached_property("Ejectable").unpack() def _get_drive_can_power_off(self): return self._drive_proxy.get_cached_property("CanPowerOff").unpack() def _get_drive_model(self): return self._drive_proxy.get_cached_property("Model").unpack() def _get_drive_size(self): return self._drive_proxy.get_cached_property("Size").unpack() def _on_eject_finished(self, proxy, task, callback, device): logger.debug(f"eject finished {self} {device}") try: proxy.call_finish(task) # all devices should power off whenever possible if self.can_power_off: self.power_off(callback, device) return device.safely_removed = True callback(device, True) except Exception as e: logger.debug(e) callback(device, False) def _on_power_off_finished(self, proxy, task, callback, device): logger.debug(f"power_off finished {self} {device}") try: proxy.call_finish(task) device.safely_removed = True callback(device, True) except Exception as e: logger.debug(e) callback(device, False) def eject(self, callback, device): logger.debug(f"eject {self} {device}") self._drive_proxy.call( "Eject", GLib.Variant("(a{sv})", ({},)), Gio.DBusCallFlags.NONE, -1, None, self._on_eject_finished, callback, device, ) def power_off(self, callback, device): logger.debug(f"power_off {self} {device}") self._drive_proxy.call( "PowerOff", GLib.Variant("(a{sv})", ({},)), Gio.DBusCallFlags.NONE, -1, None, self._on_power_off_finished, callback, device, ) def shutdown(self, callback, device): if self.is_ejectable: self.eject(callback=callback, device=device) elif self.can_power_off: self.power_off(callback=callback, device=device) class PortfolioBlock(GObject.GObject): __gtype_name__ = "PortfolioBlock" __gsignals__ = { "updated": (GObject.SignalFlags.RUN_LAST, None, ()), } def __init__(self, object): GObject.GObject.__init__(self) self._object = object self._block_proxy = object.get_interface("org.freedesktop.UDisks2.Block") self.label = self._get_block_label() self.uuid = self._get_block_uuid() self.drive = self._get_block_drive() self.crypto_backing_device = self._get_block_crypto_backing_device() self.hint_system = self._get_block_hint_system() self.drive_object = None self.safely_removed = False def __repr__(self): return f"Block(uuid={self.uuid}, label={self.label})" def _get_block_drive(self): return self._block_proxy.get_cached_property("Drive").unpack() def _get_block_label(self): for property in ["IdLabel", "IdUUID"]: if label := self._block_proxy.get_cached_property(property): return label.unpack() return None def _get_block_uuid(self): if uuid := self._block_proxy.get_cached_property("IdUUID"): return uuid.unpack() return None def _get_block_crypto_backing_device(self): if device := self._block_proxy.get_cached_property("CryptoBackingDevice"): return device.unpack() return None def _get_block_hint_system(self): if (hint := self._block_proxy.get_cached_property("HintSystem")) is not None: return hint.unpack() return None class PortfolioDevice(PortfolioBlock): __gtype_name__ = "PortfolioDevice" def __init__(self, object): PortfolioBlock.__init__(self, object) self._filesystem_proxy = object.get_interface( "org.freedesktop.UDisks2.Filesystem" ) self._filesystem_proxy.connect( "g-properties-changed", self._on_filesystem_changed ) self.mount_point = self._get_preferred_mount_point() self.encrypted_object = None def __repr__(self): return f"Device(uuid={self.uuid}, label={self.label}, mount_point={self.mount_point})" def _get_string_from_bytes(self, bytes): return bytearray(bytes).replace(b"\x00", b"").decode("utf-8") def _get_filesystem_mount_points(self): return [ self._get_string_from_bytes(m) for m in self._filesystem_proxy.get_cached_property("MountPoints") if m ] def _get_fstab_mount_points(self): return [ self._get_string_from_bytes(c.get("dir")) for t, c in self._block_proxy.get_cached_property("Configuration") if t == "fstab" ] def _get_preferred_mount_point(self): mount_points = self._get_filesystem_mount_points() # Check if not mounted yet if len(mount_points) == 0: return None # Check udisks2 to see if there's only one option if len(mount_points) == 1: return mount_points[0] fstab_mount_points = self._get_fstab_mount_points() # Filter to active mount points that are in fstab fstab_mount_points = list( set(fstab_mount_points).intersection(set(mount_points)) ) # Check fstab to see if there's a valid option if fstab_mount_points: return fstab_mount_points[0] # Default to previous behavior return mount_points[0] def _on_filesystem_changed(self, proxy, new_properties, old_properties): properties = new_properties.unpack() if "MountPoints" in properties: self.mount_point = self._get_preferred_mount_point() self.emit("updated") def _on_mount_finished(self, proxy, task, callback): try: proxy.call_finish(task) callback(self, self.encrypted_object, True) except Exception as e: logger.debug(e) callback(self, self.encrypted_object, False) def _on_unmount_finished(self, proxy, task, callback): logger.debug(f"unmont finished {self}") try: proxy.call_finish(task) except Exception as e: logger.debug(e) callback(self, False) return self._shutdown(callback) def _shutdown(self, callback): if self.encrypted_object is not None: self.encrypted_object.lock(callback, self) else: self.drive_object.shutdown(callback, self) def mount(self, callback): self._filesystem_proxy.call( "Mount", GLib.Variant("(a{sv})", ({},)), Gio.DBusCallFlags.NONE, -1, None, self._on_mount_finished, callback, ) def unmount(self, callback): logger.debug(f"unmont {self}") self._filesystem_proxy.call( "Unmount", GLib.Variant("(a{sv})", ({},)), Gio.DBusCallFlags.NONE, -1, None, self._on_unmount_finished, callback, ) def eject(self, callback): if self.mount_point is not None: self.unmount(callback) else: self._shutdown(callback) class PortfolioEncrypted(PortfolioBlock): __gtype_name__ = "PortfolioEncrypted" def __init__(self, object): PortfolioBlock.__init__(self, object) self._encrypted_proxy = object.get_interface( "org.freedesktop.UDisks2.Encrypted" ) self._encrypted_proxy.connect( "g-properties-changed", self._on_cleartext_device_changed ) self.mount_point = None self.cleartext_device = self._get_encrypted_cleartext_device() self.cleartext_device_object = None def _get_encrypted_cleartext_device(self): return self._encrypted_proxy.get_cached_property("CleartextDevice").unpack() def _on_cleartext_device_changed(self, proxy, new_properties, old_properties): properties = new_properties.unpack() if "CleartextDevice" in properties: self.cleartext_device = self._get_encrypted_cleartext_device() self.emit("updated") def _unlock_finish(self, proxy, task, callback): try: proxy.call_finish(task) self.cleartext_device_object.mount(callback) except Exception as e: logger.debug(e) callback(None, self, False) def _lock_finish(self, proxy, task, callback, device): logger.debug(f"lock finished {self}") try: proxy.call_finish(task) device.drive_object.shutdown(callback, device) except Exception as e: logger.debug(e) callback(device, False) def get_friendly_label(self): size = utils.get_size_for_humans(self.drive_object.size) return f"{self.drive_object.model} ({size} Drive)" def unlock(self, passphrase, callback): self._encrypted_proxy.call( "Unlock", GLib.Variant("(sa{sv})", (passphrase, {})), Gio.DBusCallFlags.NONE, -1, None, self._unlock_finish, callback, ) def lock(self, callback, device): logger.debug(f"lock {self}") self._encrypted_proxy.call( "Lock", GLib.Variant("(a{sv})", ({},)), Gio.DBusCallFlags.NONE, -1, None, self._lock_finish, callback, device, ) def eject(self, callback): self.drive_object.shutdown(callback, self) class PortfolioDevices(GObject.GObject): __gtype_name__ = "PortfolioDevices" __gsignals__ = { "added": (GObject.SignalFlags.RUN_LAST, None, (object,)), "removed": (GObject.SignalFlags.RUN_LAST, None, (object,)), "encrypted-added": (GObject.SignalFlags.RUN_LAST, None, (object,)), } def __init__(self): GObject.GObject.__init__(self) self._drives = {} self._devices = {} self._encrypted = {} self._manager = None try: self._manager = self._get_manager_proxy() except Exception as e: logger.debug(f"No udisk2 service found: {e}") return self._manager.connect("object-added", self._on_object_added) self._manager.connect("object-removed", self._on_object_removed) def _get_manager_proxy(self): return Gio.DBusObjectManagerClient.new_for_bus_sync( Gio.BusType.SYSTEM, Gio.DBusObjectManagerClientFlags.NONE, "org.freedesktop.UDisks2", "/org/freedesktop/UDisks2", None, None, None, ) def _on_object_added(self, manager, object): self._add_object(object) def _on_object_removed(self, manager, object): self._remove_object(object) def _on_encrypted_updated(self, encrypted): if encrypted.cleartext_device in self._devices: self.emit("removed", encrypted) def _remove_device(self, object_path): device = self._devices.get(object_path) if device is None: return self.emit("removed", device) del self._devices[object_path] def _remove_encrypted(self, object_path): encrypted = self._encrypted.get(object_path) if encrypted is None: return # XXX unsafely removed devices don't emit removed signal self._remove_device(encrypted.cleartext_device) self.emit("removed", encrypted) del self._encrypted[object_path] def _update_drive_mapping(self): # XXX defer added until we can determine real hint system for _, device in self._devices.items(): if device.drive_object is None: device.drive_object = self._drives.get(device.drive) if device.drive_object and device.hint_system is False: self.emit("added", device) if device.drive_object is None: if encrypted := self._encrypted.get(device.crypto_backing_device): device.encrypted_object = encrypted encrypted.cleartext_device_object = device device.drive_object = self._drives.get(encrypted.drive) if device.drive_object and encrypted.hint_system is False: self.emit("added", device) # XXX defer encrypted-added until the corresponding drive is found for _, encrypted in self._encrypted.items(): if encrypted.drive_object is None: drive = self._drives.get(encrypted.drive) encrypted.drive_object = drive if encrypted.hint_system is True: continue if drive is None or encrypted.cleartext_device != "/": continue self.emit("encrypted-added", encrypted) def _add_object(self, object): if drive := object.get_interface("org.freedesktop.UDisks2.Drive"): self._drives[drive.get_object_path()] = PortfolioDrive(object) elif device := object.get_interface("org.freedesktop.UDisks2.Filesystem"): self._devices[device.get_object_path()] = PortfolioDevice(object) elif proxy := object.get_interface("org.freedesktop.UDisks2.Encrypted"): encrypted = PortfolioEncrypted(object) encrypted.connect("updated", self._on_encrypted_updated) self._encrypted[proxy.get_object_path()] = encrypted self._update_drive_mapping() def _remove_object(self, object): if drive := object.get_interface("org.freedesktop.UDisks2.Drive"): del self._drives[drive.get_object_path()] elif device := object.get_interface("org.freedesktop.UDisks2.Filesystem"): self._remove_device(device.get_object_path()) elif encrypted := object.get_interface("org.freedesktop.UDisks2.Encrypted"): self._remove_encrypted(encrypted.get_object_path()) def scan(self): if self._manager is None: return for object in self._manager.get_objects(): self._add_object(object) Portfolio-1.0.2/src/files.py000066400000000000000000000334411476103320100157340ustar00rootroot00000000000000# files.py # # Copyright 2023 Martin Abente Lahaye # # 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 . import os from pathlib import Path from gi.repository import Gtk, GLib, GObject from . import utils from . import logger from .settings import PortfolioSettings from .translation import gettext as _ @Gtk.Template(resource_path="/dev/tchx84/Portfolio/files.ui") class PortfolioFiles(Gtk.ScrolledWindow): __gtype_name__ = "PortfolioFiles" __gsignals__ = { "selected": (GObject.SignalFlags.RUN_LAST, None, ()), "activated": (GObject.SignalFlags.RUN_LAST, None, (str,)), "rename-started": (GObject.SignalFlags.RUN_LAST, None, ()), "rename-finished": (GObject.SignalFlags.RUN_LAST, None, ()), "rename-failed": (GObject.SignalFlags.RUN_LAST, None, (str,)), "add-failed": (GObject.SignalFlags.RUN_LAST, None, ()), "adjustment-changed": (GObject.SignalFlags.RUN_LAST, None, (bool,)), } treeview = Gtk.Template.Child() name_column = Gtk.Template.Child() name_cell = Gtk.Template.Child() sorted = Gtk.Template.Child() filtered = Gtk.Template.Child() selection = Gtk.Template.Child() liststore = Gtk.Template.Child() ICON_COLUMN = 0 NAME_COLUMN = 1 PATH_COLUMN = 2 def __init__(self, **kwargs): super().__init__(**kwargs) self._setup() def _setup(self): self._is_editing = False self._to_select_path = None self._to_select_row = None self._to_go_to_path = None self._to_go_to_row = None self._last_clicked = None self._dont_activate = False self._force_select = False self._last_vscroll_value = None self._filter = "" self._sort_order = PortfolioSettings.ALPHABETICAL_ORDER self.filtered.set_visible_func(self._filter_func, None) self.sorted.set_default_sort_func(self._sort_func, None) self.selection.connect("changed", self._on_selection_changed) self.selection.set_select_function(self._on_select) self.treeview.connect("row-activated", self._on_row_activated) self.name_cell.connect("editing-started", self._on_rename_started) self.name_cell.connect("edited", self._on_rename_updated) self.name_cell.connect("editing-canceled", self._on_rename_finished) self._adjustment = self.get_vadjustment() self._adjustment.connect("value-changed", self._on_adjustment_changed) self.clicks = Gtk.GestureClick.new() self.clicks.connect("pressed", self._on_clicked) self.treeview.add_controller(self.clicks) self.gestures = Gtk.GestureLongPress.new() self.gestures.connect("pressed", self._on_long_pressed) self.treeview.add_controller(self.gestures) @property def filter(self): return self._filter @filter.setter def filter(self, value): self._filter = value self.filtered.refilter() @property def to_select_path(self): return self._to_select_path @to_select_path.setter def to_select_path(self, value): self._to_select_path = value @property def to_go_to_path(self): return self._to_go_to_path @to_go_to_path.setter def to_go_to_path(self, value): self._to_go_to_path = value @property def sort_order(self): return self._sort_order @sort_order.setter def sort_order(self, value): self._sort_order = value @property def selected_count(self): return self.selection.count_selected_rows() @property def is_editing(self): return self._is_editing @property def is_empty(self): return len(self.sorted) == 0 def _filter_func(self, model, row, data=None): path = model[row][self.PATH_COLUMN] if not self._filter: return True return self._filter.lower() in os.path.basename(path).lower() def _sort_func(self, model, row1, row2, data=None): path1 = model[row1][self.PATH_COLUMN] path2 = model[row2][self.PATH_COLUMN] row1_is_dir = utils.is_file_dir(path1) row2_is_dir = utils.is_file_dir(path2) if row1_is_dir and not row2_is_dir: return -1 elif not row1_is_dir and row2_is_dir: return 1 if self._sort_order == PortfolioSettings.ALPHABETICAL_ORDER: return self._sort_by_a_to_z(path1, path2) else: return self._sort_by_last_modified(path1, path2) def _sort_by_last_modified(self, path1, path2): st_mtime1 = utils.get_file_mtime(path1) st_mtime2 = utils.get_file_mtime(path2) if st_mtime1 < st_mtime2: return 1 elif st_mtime1 > st_mtime2: return -1 return 0 def _sort_by_a_to_z(self, path1, path2): path1 = path1.lower() path2 = path2.lower() if path1 < path2: return -1 elif path1 > path2: return 1 return 0 def _get_path(self, model, treepath): return model[model.get_iter(treepath)][self.PATH_COLUMN] def _go_to_selection(self): model, treepaths = self.selection.get_selected_rows() treepath = treepaths[-1] self.treeview.set_cursor_on_cell( treepath, self.name_column, self.name_cell, False ) self.treeview.scroll_to_cell(treepath, None, False, 0, 0) def _go_to(self, row): result, row = self.filtered.convert_child_iter_to_iter(row) result, row = self.sorted.convert_child_iter_to_iter(row) treepath = self.sorted.get_path(row) self.treeview.scroll_to_cell(treepath, None, False, 0, 0) self._clear_to_go_to() def _select_and_go(self, row, edit=False): self.switch_to_selection_mode() result, row = self.filtered.convert_child_iter_to_iter(row) result, row = self.sorted.convert_child_iter_to_iter(row) self._select_row(row) GLib.idle_add(self._go_to_selection) if edit is True: GLib.timeout_add(100, self._wait_and_edit) self._clear_select_and_go() def _wait_and_edit(self): value = self._adjustment.get_value() if value == self._last_vscroll_value: self.rename_selected_row() self._last_vscroll_value = None return False self._last_vscroll_value = value return True def _clear_select_and_go(self): self._to_select_path = None self._to_select_row = None def _clear_to_go_to(self): self._to_go_to_path = None self._to_go_to_row = None def _on_selection_changed(self, selection): self._update_mode() self.emit("selected") def _on_select(self, selection, model, treepath, selected, data=None): should_select = False if self._force_select is True: should_select = True elif treepath != self._last_clicked and selected: should_select = False elif treepath != self._last_clicked and not selected: should_select = False elif treepath == self._last_clicked and not selected: should_select = True elif treepath == self._last_clicked and selected: should_select = True if treepath == self._last_clicked: self._last_clicked = None self._dont_activate = True return should_select def _on_row_activated(self, treeview, treepath, treecolumn, data=None): if self._dont_activate is True: self._dont_activate = False return if self.selection.get_mode() == Gtk.SelectionMode.NONE: path = self._get_path(self.sorted, treepath) self.emit("activated", path) def _on_clicked(self, gesture, n_press, x, y): result = self.treeview.get_path_at_pos(x, y) if result is None: return treepath, column, x, y = result self._last_clicked = treepath def _on_rename_started(self, cell_name, treepath, data=None): self._is_editing = True self.emit("rename-started") def _on_rename_updated(self, cell_name, treepath, new_name, data=None): old_path = self._get_path(self.sorted, treepath) directory = os.path.dirname(old_path) new_path = os.path.join(directory, new_name) if new_path == old_path: self._on_rename_finished() return try: # respect empty folders if os.path.lexists(new_path): raise FileExistsError(_("%s already exists") % new_path) os.rename(old_path, new_path) _treepath = Gtk.TreePath.new_from_string(treepath) _treepath = self.sorted.convert_path_to_child_path(_treepath) _treepath = self.filtered.convert_path_to_child_path(_treepath) row = self.liststore.get_iter(_treepath) self.liststore.set_value(row, self.PATH_COLUMN, new_path) self.liststore.set_value(row, self.NAME_COLUMN, new_name) except Exception as e: logger.debug(e) self.emit("rename-failed", new_name) return # take the user to the new position self._on_rename_finished() self._go_to_selection() def _on_rename_finished(self, *args): self.name_cell.props.editable = False self._is_editing = False self.emit("rename-finished") def _on_long_pressed(self, gesture, x, y): if self.selection.get_mode() == Gtk.SelectionMode.MULTIPLE: return self.switch_to_selection_mode() path = self.treeview.get_path_at_pos(x, y) if path is None: self.switch_to_navigation_mode() return treepath = path[0] self.selection.select_path(treepath) # because of the custom selection rules, is not guaranteed # that this will actually be selected so always update mode. self._update_mode() def _on_adjustment_changed(self, adjustment): alloc = self.get_allocation() reveal = ( self._adjustment.get_value() > (alloc.height / 2) and not self._is_editing ) self.emit("adjustment-changed", reveal) def _update_mode(self): count = self.selection.count_selected_rows() if count == 0: self.switch_to_navigation_mode() def _select_row(self, row): self._force_select = True self.selection.select_iter(row) self._force_select = False def select_all(self): self._force_select = True self.selection.select_all() self._force_select = False def unselect_all(self): self._force_select = True self.selection.unselect_all() self._force_select = False def switch_to_navigation_mode(self): self.selection.set_mode(Gtk.SelectionMode.NONE) def switch_to_selection_mode(self): self.selection.set_mode(Gtk.SelectionMode.MULTIPLE) def update(self, sensitive): self.treeview.props.sensitive = sensitive def update_scrolling(self): if self._to_select_row is not None: self._select_and_go(self._to_select_row) elif self._to_go_to_row is not None: self._go_to(self._to_go_to_row) else: self.go_to_top() def go_to_top(self): if len(self.sorted) >= 1: self.treeview.scroll_to_cell(0, None, True, 0, 0) def get_selection(self): model, treepaths = self.selection.get_selected_rows() selection = [ ( model[treepath][self.PATH_COLUMN], Gtk.TreeRowReference.new(model, treepath), ) for treepath in treepaths ] return selection def get_selected_path(self): model, treepaths = self.selection.get_selected_rows() treepath = treepaths[-1] path = model[treepath][self.PATH_COLUMN] return path def add_row(self, icon, name, path): row = self.liststore.append([icon, name, path]) if self._to_select_path == path: self._to_select_row = row if self._to_go_to_path == path: self._to_go_to_row = row def add_new_folder_row(self, directory): folder_name = utils.find_new_name(directory, _("New Folder")) path = os.path.join(directory, folder_name) try: Path(path).mkdir(parents=False, exist_ok=True) except Exception as e: logger.debug(e) self.emit("add-failed") return icon = utils.get_file_icon(path) row = self.liststore.append([icon, folder_name, path]) self._select_and_go(row, edit=True) def remove_row(self, row): if row is None or not row.valid(): return treepath = row.get_path() treepath = self.sorted.convert_path_to_child_path(treepath) treepath = self.filtered.convert_path_to_child_path(treepath) self.liststore.remove(self.liststore.get_iter(treepath)) def rename_selected_row(self): self.name_cell.props.editable = True model, treepaths = self.selection.get_selected_rows() treepath = treepaths[-1] self.treeview.set_cursor_on_cell( treepath, self.name_column, self.name_cell, True ) def clear(self): self.liststore.clear() Portfolio-1.0.2/src/files.ui000066400000000000000000000047211476103320100157200ustar00rootroot00000000000000 liststore filtered Portfolio-1.0.2/src/loading.py000066400000000000000000000032201476103320100162370ustar00rootroot00000000000000# loading.py # # Copyright 2023 Martin Abente Lahaye # # 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 . from gi.repository import Gtk @Gtk.Template(resource_path="/dev/tchx84/Portfolio/loading.ui") class PortfolioLoading(Gtk.Box): __gtype_name__ = "PortfolioLoading" title = Gtk.Template.Child() progress = Gtk.Template.Child() description = Gtk.Template.Child() details = Gtk.Template.Child() def update(self, title=None, progress=None, description=None, details=None): if title is not None: self.title.set_text(title) if progress is not None: self.progress.set_fraction(progress) if description is not None: self.description.set_text(description) if details is not None: self.details.set_text(details) self.description.props.visible = True if self.description.props.label else False self.details.props.visible = True if self.details.props.label else False def pulse(self): self.progress.pulse() def clean(self): self.update("", 0.0, "", "") Portfolio-1.0.2/src/loading.ui000066400000000000000000000044061476103320100162330ustar00rootroot00000000000000 Portfolio-1.0.2/src/main.py000066400000000000000000000033311476103320100155510ustar00rootroot00000000000000# main.py # # Copyright 2020 Martin Abente Lahaye # # 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 . import sys import gi gi.require_version("Gtk", "4.0") gi.require_version("Gio", "2.0") gi.require_version("Adw", "1") from gi.repository import Adw, Gio from .window import PortfolioWindow from .service import PortfolioService class Application(Adw.Application): def __init__(self): super().__init__( application_id="dev.tchx84.Portfolio", flags=Gio.ApplicationFlags.HANDLES_OPEN, ) self._service = PortfolioService(self) def show_properties(self, path): self.activate() self.props.active_window.show_properties(path, force_page_switch=True) def open_path(self, path): self.activate() self.props.active_window.open(path, force_page_switch=True) def do_open(self, files, hint, data): self.open_path(files[0].get_path()) def do_activate(self): win = self.props.active_window if not win: win = PortfolioWindow(application=self) win.present() def main(version): app = Application() return app.run(sys.argv) Portfolio-1.0.2/src/menu.py000066400000000000000000000072531476103320100156000ustar00rootroot00000000000000# menu.py # # Copyright 2023 Martin Abente Lahaye # # 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 . from gi.repository import Gtk, Gio, GObject from .settings import PortfolioSettings @Gtk.Template(resource_path="/dev/tchx84/Portfolio/menu.ui") class PortfolioMenu(Gtk.Popover): __gtype_name__ = "PortfolioMenu" __gsignals__ = { "show-about": (GObject.SignalFlags.RUN_LAST, None, ()), } menu_box = Gtk.Template.Child() show_hidden_button = Gtk.Template.Child() a_to_z_button = Gtk.Template.Child() last_modified_button = Gtk.Template.Child() help_button = Gtk.Template.Child() about_button = Gtk.Template.Child() def __init__(self, settings): super().__init__() self._setup(settings) @property def is_sensitive(self): return self.menu_box.props.sensitive @is_sensitive.setter def is_sensitive(self, value): self.menu_box.props.sensitive = value def _setup(self, settings): self._settings = settings self._settings.connect("notify::show-hidden", self._on_show_hidden_changed) self._settings.connect("notify::sort-order", self._on_sort_order_changed) self._show_hidden_handler_id = self.show_hidden_button.connect( "toggled", self._on_show_hidden_toggled ) self._sort_order_handler_id = self.a_to_z_button.connect( "toggled", self._on_sort_by_toggled ) self.help_button.connect("clicked", self._on_help_clicked) self.about_button.connect("clicked", self._on_about_clicked) self._on_show_hidden_changed(self._settings, None) self._on_sort_order_changed(self._settings, None) def _on_show_hidden_changed(self, settings, data): GObject.signal_handler_block( self.show_hidden_button, self._show_hidden_handler_id, ) self.show_hidden_button.props.active = self._settings.show_hidden GObject.signal_handler_unblock( self.show_hidden_button, self._show_hidden_handler_id, ) def _on_sort_order_changed(self, settings, data): GObject.signal_handler_block( self.a_to_z_button, self._sort_order_handler_id, ) if self._settings.sort_order == PortfolioSettings.ALPHABETICAL_ORDER: self.a_to_z_button.props.active = True else: self.last_modified_button.props.active = True GObject.signal_handler_unblock( self.a_to_z_button, self._sort_order_handler_id, ) def _on_show_hidden_toggled(self, button): self._settings.show_hidden = self.show_hidden_button.props.active def _on_sort_by_toggled(self, button): if self.a_to_z_button.props.active: self._settings.sort_order = PortfolioSettings.ALPHABETICAL_ORDER else: self._settings.sort_order = PortfolioSettings.MODIFIED_TIME_ORDER def _on_help_clicked(self, button): Gio.AppInfo.launch_default_for_uri("https://github.com/tchx84/Portfolio", None) def _on_about_clicked(self, button): self.emit("show-about") Portfolio-1.0.2/src/menu.ui000066400000000000000000000121261476103320100155600ustar00rootroot00000000000000 Portfolio-1.0.2/src/meson.build000066400000000000000000000024741476103320100164240ustar00rootroot00000000000000python = import('python') python_bin = python.find_installation('python3') python_dir = join_paths(get_option('prefix'), python_bin.get_install_dir()) pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name()) moduledir = join_paths(python_dir, meson.project_name()) gnome = import('gnome') dependency('libadwaita-1', version : '>= 1.0') gnome.compile_resources('portfolio', 'portfolio.gresource.xml', gresource_bundle: true, install: true, install_dir: pkgdatadir, ) conf = configuration_data() conf.set('PYTHON', python.find_installation('python3').path()) conf.set('VERSION', meson.project_version()) conf.set('localedir', join_paths(get_option('prefix'), get_option('localedir'))) conf.set('pkgdatadir', pkgdatadir) configure_file( input: 'dev.tchx84.Portfolio.in', output: 'dev.tchx84.Portfolio', configuration: conf, install: true, install_dir: get_option('bindir') ) portfolio_sources = [ '__init__.py', 'main.py', 'window.py', 'popup.py', 'worker.py', 'places.py', 'utils.py', 'service.py', 'place.py', 'translation.py', 'cache.py', 'settings.py', 'trash.py', 'devices.py', 'properties.py', 'about.py', 'passphrase.py', 'placeholder.py', 'loading.py', 'files.py', 'menu.py', ] install_data(portfolio_sources, install_dir: moduledir) Portfolio-1.0.2/src/org.freedesktop.FileManager1.xml000066400000000000000000000010421476103320100223250ustar00rootroot00000000000000 Portfolio-1.0.2/src/passphrase.py000066400000000000000000000052451476103320100170040ustar00rootroot00000000000000# passphrase.py # # Copyright 2023 Martin Abente Lahaye # # 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 . import os from gi.repository import Gtk, GObject from . import logger from .translation import gettext as _ @Gtk.Template(resource_path="/dev/tchx84/Portfolio/passphrase.ui") class PortfolioPassphrase(Gtk.Box): __gtype_name__ = "PortfolioPassphrase" __gsignals__ = { "unlocked": (GObject.SignalFlags.RUN_LAST, None, (str,)), } passphrase_entry = Gtk.Template.Child() passphrase_label = Gtk.Template.Child() passphrase_spinner = Gtk.Template.Child() def __init__(self, **kwargs): super().__init__(**kwargs) self._setup() def _setup(self): self._encrypted = None self.passphrase_entry.connect("activate", self._on_passphrase_activate) def _on_passphrase_activate(self, button): self.passphrase_label.props.visible = False self.passphrase_spinner.props.visible = True self.passphrase_spinner.props.spinning = True passphrase = self.passphrase_entry.get_text() self._encrypted.unlock(passphrase, self._on_places_unlock_finished) def _on_places_unlock_finished(self, device, encrypted, success): self.clean() if device is None: logger.debug(f"Failed to unlock {encrypted}") elif not os.access(device.mount_point, os.R_OK): logger.debug(f"No permissions for {device}") elif success is True: self._encrypted = None self.emit("unlocked", device.mount_point) return self._encrypted = encrypted self.passphrase_entry.grab_focus() self.passphrase_label.set_text(_("Sorry, that didn't work")) def clean(self): self.passphrase_entry.set_text("") self.passphrase_label.set_text("") self.passphrase_label.props.visible = True self.passphrase_spinner.props.visible = False self.passphrase_spinner.props.spinning = False def unlock(self, encrypted): self.clean() self._encrypted = encrypted self.passphrase_entry.grab_focus() Portfolio-1.0.2/src/passphrase.ui000066400000000000000000000025071476103320100167670ustar00rootroot00000000000000 Portfolio-1.0.2/src/place.py000066400000000000000000000023011476103320100157050ustar00rootroot00000000000000# place.py # # Copyright 2021 Martin Abente Lahaye # # 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 . from gi.repository import Adw, Gtk @Gtk.Template(resource_path="/dev/tchx84/Portfolio/place.ui") class PortfolioPlace(Adw.ActionRow): __gtype_name__ = "PortfolioPlace" icon = Gtk.Template.Child() eject = Gtk.Template.Child() mount = None path = "" uuid = "" encrypted = None device = None def set_custom_icon_name(self, name): self.icon.set_from_icon_name(name) def set_subtitle_sately(self, subtitle): if subtitle is not None: self.set_subtitle(subtitle) Portfolio-1.0.2/src/place.ui000066400000000000000000000017611476103320100157030ustar00rootroot00000000000000 Portfolio-1.0.2/src/placeholder.py000066400000000000000000000015531476103320100171130ustar00rootroot00000000000000# placeholder.py # # Copyright 2023 Martin Abente Lahaye # # 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 . from gi.repository import Gtk @Gtk.Template(resource_path="/dev/tchx84/Portfolio/placeholder.ui") class PortfolioPlaceholder(Gtk.Box): __gtype_name__ = "PortfolioPlaceholder" Portfolio-1.0.2/src/placeholder.ui000066400000000000000000000012131476103320100170710ustar00rootroot00000000000000 Portfolio-1.0.2/src/places.py000066400000000000000000000317401476103320100161010ustar00rootroot00000000000000# places.py # # Copyright 2020 Martin Abente Lahaye # # 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 . import os from gi.repository import Adw, GLib, Gtk, GObject from . import utils from . import logger from .place import PortfolioPlace from .devices import PortfolioDevices from .translation import gettext as _ class PortfolioPlaces(Gtk.Stack): __gtype_name__ = "PortfolioPlaces" __gsignals__ = { "updated": (GObject.SignalFlags.RUN_LAST, None, (str,)), "removing": (GObject.SignalFlags.RUN_LAST, None, (str,)), "removed": (GObject.SignalFlags.RUN_LAST, None, (str, bool)), "failed": (GObject.SignalFlags.RUN_LAST, None, (str,)), "unlock": (GObject.SignalFlags.RUN_LAST, None, (object,)), } FLATPAK_INFO = os.path.join(os.path.abspath(os.sep), ".flatpak-info") PORTFOLIO_SYSTEM_DIR = os.path.abspath(os.sep) PORTFOLIO_SYSTEM_DIR_FLATPAK = os.path.join(os.path.abspath(os.sep), "run", "host") PORTFOLIO_HOME_DIR = os.environ.get("PORTFOLIO_HOME_DIR", os.path.expanduser("~")) XDG_DOWNLOAD = utils.get_path_for_xdg(GLib.UserDirectory.DIRECTORY_DOWNLOAD) XDG_DOCUMENTS = utils.get_path_for_xdg(GLib.UserDirectory.DIRECTORY_DOCUMENTS) XDG_PICTURES = utils.get_path_for_xdg(GLib.UserDirectory.DIRECTORY_PICTURES) XDG_MUSIC = utils.get_path_for_xdg(GLib.UserDirectory.DIRECTORY_MUSIC) XDG_VIDEOS = utils.get_path_for_xdg(GLib.UserDirectory.DIRECTORY_VIDEOS) XDG_TRASH = "Trash" XDG_TRASH_NAME = _("Trash") HOST_PERMISSION = ["host"] HOME_PERMISSION = ["host", "home"] DOWNLOAD_PERMISSION = ["host", "home", "xdg-download"] DOCUMENTS_PERMISSION = ["host", "home", "xdg-documents"] PICTURES_PERMISSION = ["host", "home", "xdg-pictures"] MUSIC_PERMISSION = ["host", "home", "xdg-music"] VIDEOS_PERMISSION = ["host", "home", "xdg-videos"] TRASH_PERMISSION = ["host", "home"] def __init__(self, **kargs): super().__init__(**kargs) self._setup() def _setup(self): self.props.visible = True self.props.transition_type = Gtk.StackTransitionType.CROSSFADE self._permissions = None self._devices = PortfolioDevices() self._devices.connect("added", self._on_device_added) self._devices.connect("removed", self._on_device_removed) self._devices.connect("encrypted-added", self._on_encrypted_added) # begin UI structure self._groups_box = Gtk.Box() self._groups_box.props.visible = True self._groups_box.props.orientation = Gtk.Orientation.VERTICAL self._message_box = Gtk.Box() self._message_box.props.visible = True self._message_box.props.orientation = Gtk.Orientation.VERTICAL self._places_group = Adw.PreferencesGroup() self._places_group.props.title = _("Places") self._places_group.props.visible = True self._devices_group = Adw.PreferencesGroup() self._devices_group.props.title = _("Devices") self._devices_group.props.visible = True self._devices_group.get_style_context().add_class("devices-group") # places if self._has_permission_for(self.HOME_PERMISSION): self._add_place( self._places_group, "user-home-symbolic", _("Home"), self.PORTFOLIO_HOME_DIR, ) if self._has_permission_for(self.DOWNLOAD_PERMISSION) and self.XDG_DOWNLOAD: self._add_place( self._places_group, "folder-download-symbolic", os.path.basename(self.XDG_DOWNLOAD), self.XDG_DOWNLOAD, ) if self._has_permission_for(self.DOCUMENTS_PERMISSION) and self.XDG_DOCUMENTS: self._add_place( self._places_group, "folder-documents-symbolic", os.path.basename(self.XDG_DOCUMENTS), self.XDG_DOCUMENTS, ) if self._has_permission_for(self.PICTURES_PERMISSION) and self.XDG_PICTURES: self._add_place( self._places_group, "folder-pictures-symbolic", os.path.basename(self.XDG_PICTURES), self.XDG_PICTURES, ) if self._has_permission_for(self.MUSIC_PERMISSION) and self.XDG_MUSIC: self._add_place( self._places_group, "folder-music-symbolic", os.path.basename(self.XDG_MUSIC), self.XDG_MUSIC, ) if self._has_permission_for(self.VIDEOS_PERMISSION) and self.XDG_VIDEOS: self._add_place( self._places_group, "folder-videos-symbolic", os.path.basename(self.XDG_VIDEOS), self.XDG_VIDEOS, ) if self._has_permission_for(self.TRASH_PERMISSION): self._add_place( self._places_group, "user-trash-symbolic", self.XDG_TRASH_NAME, self.XDG_TRASH, ) # static devices if self._has_permission_for(self.HOST_PERMISSION): self._add_place( self._devices_group, "drive-harddisk-ieee1394-symbolic", _("System"), self.PORTFOLIO_SYSTEM_DIR, ) if self._has_permission_for(self.HOST_PERMISSION) and self._is_flatpak(): self._add_place( self._devices_group, "drive-harddisk-ieee1394-symbolic", _("Host"), self.PORTFOLIO_SYSTEM_DIR_FLATPAK, ) self._groups_box.append(self._places_group) self._groups_box.append(self._devices_group) self._places_listbox = utils.find_child_by_id(self._places_group, "listbox") self._devices_listbox = utils.find_child_by_id(self._devices_group, "listbox") # no places message message = Gtk.Label() message.props.visible = True message.props.label = _("No places found") message.get_style_context().add_class("no-places") self._message_box.append(message) # finalize UI structure self.add_named(self._groups_box, "groups") self.add_named(self._message_box, "message") # dynamic devices self._devices.scan() # update visibility self._update_visibility() def _update_visibility(self): self._update_stack_visibility() self._update_places_group_visibility() self._update_device_group_visibility() def _update_stack_visibility(self): groups = len(list(self._places_listbox)) devices = len(list(self._devices_listbox)) if not groups and not devices: self.set_visible_child_name("message") else: self.set_visible_child_name("groups") def _update_places_group_visibility(self): visible = len(list(self._places_listbox)) >= 1 self._places_group.props.visible = visible def _update_device_group_visibility(self): visible = len(list(self._devices_listbox)) >= 1 self._devices_group.props.visible = visible def _get_permissions(self): if self._permissions is not None: return self._permissions info = GLib.KeyFile() info.load_from_file(self.FLATPAK_INFO, GLib.KeyFileFlags.NONE) permissions = info.get_value("Context", "filesystems") if permissions is not None: self._permissions = set(permissions.split(";")) else: self._permissions = set() return self._permissions def _is_flatpak(self): return os.path.exists(self.FLATPAK_INFO) def _has_permission_for(self, required): # not using flatpak, so access to all if not self._is_flatpak(): return True permissions = self._get_permissions() required = set(required) negated = set([f"!{r}" for r in required]) if required.intersection(permissions): return True if negated.intersection(permissions): return False return False def _add_place(self, group, icon, name, path): place = PortfolioPlace() place.set_custom_icon_name(icon) place.set_title(name) place.set_subtitle_sately(path) place.path = path place.props.activatable = True place.connect("activated", self._on_place_activated) group.add(place) return place def _find_place_by_device_uuid(self, listbox, device): for place in listbox: if place.uuid == device.uuid: return place return None def _filter_device(self, device): if device.mount_point is None: logger.debug(f"No mount point for {device}") return False if not os.access(device.mount_point, os.R_OK): logger.debug(f"No permissions for {device}") return True if device.mount_point == "/" and self._is_flatpak(): logger.debug(f"Skip root directory for {device}") return True return False def _update_place_from_device(self, place, device): if place is None: return if self._filter_device(device): self._devices_group.remove(place) return place.path = device.mount_point place.set_title(device.label) place.set_subtitle_sately(device.mount_point) def _on_place_activated(self, place): if place.path is not None: self.emit("updated", place.path) elif place.encrypted is not None: self.emit("unlock", place.encrypted) elif place.device is not None: place.device.mount(self._on_insert_finished) def _on_insert_finished(self, device, encrypted, success): logger.debug(f"insert finished {device}") if success: self.emit("updated", device.mount_point) def _on_encrypted_added(self, devices, encrypted): logger.debug(f"added {encrypted}") place = self._add_place( self._devices_group, "system-lock-screen-symbolic", encrypted.get_friendly_label(), None, ) place.uuid = encrypted.uuid place.encrypted = encrypted place.eject.props.visible = True place.eject.connect("clicked", self._on_encrypted_eject, encrypted) self._update_visibility() def _on_device_added(self, devices, device): logger.debug(f"added {device}") if self._filter_device(device): return place = self._add_place( self._devices_group, "drive-removable-media-symbolic", device.label, device.mount_point, ) place.uuid = device.uuid place.device = device place.eject.props.visible = True place.eject.connect("clicked", self._on_eject, device) device.connect("updated", self._on_device_updated) self._update_place_from_device(place, device) self._update_visibility() def _on_device_removed(self, devices, device): logger.debug(f"removed {device}") place = self._find_place_by_device_uuid(self._devices_listbox, device) if place is not None: self._devices_group.remove(place) self.emit("removed", device.mount_point, device.safely_removed) self._update_visibility() def _on_device_updated(self, device): logger.debug(f"updated {device}") place = self._find_place_by_device_uuid(self._devices_listbox, device) self._update_place_from_device(place, device) self._update_visibility() def _on_eject(self, button, device): logger.debug(f"eject {device}") self.emit("removing", device.mount_point) device.eject(self._on_eject_finished) def _on_eject_finished(self, device, success): logger.debug(f"eject finished {device} {success}") if success: self._on_device_removed(None, device) else: self.emit("failed", device.mount_point) def _on_encrypted_eject(self, button, encrypted): logger.debug(f"encrypted eject {encrypted}") encrypted.eject(self._on_encrypted_eject_finished) def _on_encrypted_eject_finished(self, encrypted, success): logger.debug(f"encrypted eject finished {encrypted}") if success: self._on_device_removed(None, encrypted) else: self.emit("failed", None) Portfolio-1.0.2/src/popup.py000066400000000000000000000041571476103320100157770ustar00rootroot00000000000000# popup.py # # Copyright 2020 Martin Abente Lahaye # # 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 . from gi.repository import GLib, Gtk DEFAULT_CLOSE_TIME = 3 @Gtk.Template(resource_path="/dev/tchx84/Portfolio/popup.ui") class PortfolioPopup(Gtk.Revealer): __gtype_name__ = "PortfolioPopup" description = Gtk.Template.Child() confirm_button = Gtk.Template.Child() cancel_button = Gtk.Template.Child() trash_button = Gtk.Template.Child() def __init__(self, description, on_confirm, on_cancel, on_trash, autoclose, data): super().__init__() self.description.set_text(description) if on_confirm is not None: self.confirm_button.connect("clicked", on_confirm, self, data) else: self.confirm_button.props.visible = False if on_cancel is not None: self.cancel_button.connect("clicked", on_cancel, self, data) else: self.cancel_button.connect("clicked", self._on_default_callback, self, data) if on_trash is not None: self.trash_button.connect("clicked", on_trash, self, data) else: self.trash_button.props.visible = False if autoclose is True: GLib.timeout_add_seconds( DEFAULT_CLOSE_TIME, self._on_default_callback, None, None, None ) def set_description(self, description): self.description.set_text(description) def _on_default_callback(self, button, popup, data): if parent := self.get_parent(): parent.remove(self) Portfolio-1.0.2/src/popup.ui000066400000000000000000000045501476103320100157610ustar00rootroot00000000000000 Portfolio-1.0.2/src/portfolio.gresource.xml000066400000000000000000000007411476103320100210110ustar00rootroot00000000000000 window.ui popup.ui properties.ui place.ui style.css about.ui passphrase.ui placeholder.ui loading.ui files.ui menu.ui org.freedesktop.FileManager1.xml Portfolio-1.0.2/src/properties.py000066400000000000000000000071351476103320100170270ustar00rootroot00000000000000# properties.py # # Copyright 2023 Martin Abente Lahaye # # 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 . from gi.repository import GObject, Gtk @Gtk.Template(resource_path="/dev/tchx84/Portfolio/properties.ui") class PortfolioProperties(Gtk.Box): __gtype_name__ = "PortfolioProperties" property_name = Gtk.Template.Child() property_location = Gtk.Template.Child() property_type = Gtk.Template.Child() property_size = Gtk.Template.Child() property_created = Gtk.Template.Child() property_modified = Gtk.Template.Child() property_accessed = Gtk.Template.Child() property_permissions_owner = Gtk.Template.Child() property_permissions_group = Gtk.Template.Child() property_permissions_others = Gtk.Template.Child() property_owner = Gtk.Template.Child() property_group = Gtk.Template.Child() def __init__(self, worker, **kwargs): super().__init__(**kwargs) self._worker = worker self._setup() def _setup(self): self._worker.bind_property( "name", self.property_name, "label", GObject.BindingFlags.SYNC_CREATE, ) self._worker.bind_property( "location", self.property_location, "label", GObject.BindingFlags.SYNC_CREATE, ) self._worker.bind_property( "type", self.property_type, "label", GObject.BindingFlags.SYNC_CREATE, ) self._worker.bind_property( "size", self.property_size, "label", GObject.BindingFlags.SYNC_CREATE, ) self._worker.bind_property( "created", self.property_created, "label", GObject.BindingFlags.SYNC_CREATE, ) self._worker.bind_property( "modified", self.property_modified, "label", GObject.BindingFlags.SYNC_CREATE, ) self._worker.bind_property( "accessed", self.property_accessed, "label", GObject.BindingFlags.SYNC_CREATE, ) self._worker.bind_property( "permissions_owner", self.property_permissions_owner, "label", GObject.BindingFlags.SYNC_CREATE, ) self._worker.bind_property( "permissions_group", self.property_permissions_group, "label", GObject.BindingFlags.SYNC_CREATE, ) self._worker.bind_property( "permissions_others", self.property_permissions_others, "label", GObject.BindingFlags.SYNC_CREATE, ) self._worker.bind_property( "owner", self.property_owner, "label", GObject.BindingFlags.SYNC_CREATE, ) self._worker.bind_property( "group", self.property_group, "label", GObject.BindingFlags.SYNC_CREATE, ) Portfolio-1.0.2/src/properties.ui000066400000000000000000000364621476103320100170210ustar00rootroot00000000000000 Portfolio-1.0.2/src/service.py000066400000000000000000000047641476103320100163000ustar00rootroot00000000000000# service.py # # Copyright 2021 Martin Abente Lahaye # # 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 . import os from gi.repository import Gio class PortfolioService: def __init__(self, app): self._app = app self._dbus_id = None self._name_id = Gio.bus_own_name( Gio.BusType.SESSION, os.environ.get("PORTFOLIO_SERVICE_NAME", "org.freedesktop.FileManager1"), Gio.BusNameOwnerFlags.NONE, self._on_bus_acquired, self._on_name_acquired, self._on_name_lost, ) def __del__(self): self.shutdown() def _on_bus_acquired(self, connection, name): xml = ( Gio.resources_lookup_data( "/dev/tchx84/Portfolio/org.freedesktop.FileManager1.xml", Gio.ResourceLookupFlags.NONE, ) .get_data() .decode("utf-8") ) info = Gio.DBusNodeInfo.new_for_xml(xml) activation_id = connection.register_object( "/org/freedesktop/FileManager1", info.interfaces[0], self._on_called ) assert activation_id > 0 def _on_name_acquired(self, connection, name): pass def _on_name_lost(self, connection, name): pass def _on_called(self, connection, sender, path, iface, method, params, invocation): paths, _ = params if len(paths) == 0: invocation.return_value(None) return last_path = paths[-1] if method == "ShowItemProperties": _method = self._app.show_properties elif method in ["ShowFolders", "ShowItems"]: _method = self._app.open_path else: raise ValueError(f"{method} not supported") # at this point we know we can handle it, so just respond invocation.return_value(None) _method(last_path) def shutdown(self): Gio.bus_unown_name(self._name_id) Portfolio-1.0.2/src/settings.py000066400000000000000000000036351476103320100164740ustar00rootroot00000000000000# settings.py # # Copyright 2021 Martin Abente Lahaye # # 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 . from gi.repository import Gio, GObject class PortfolioSettings(GObject.GObject): __gtype_name__ = "PortfolioSettings" SCHEMA = "dev.tchx84.Portfolio" ALPHABETICAL_ORDER = "alphabetical" MODIFIED_TIME_ORDER = "modified_time" def __init__(self): super().__init__() self._settings = None if Gio.SettingsSchemaSource.get_default().lookup(self.SCHEMA, False): self._settings = Gio.Settings(self.SCHEMA) @GObject.Property(type=bool, default=False) def show_hidden(self): if self._settings is None: return False return self._settings.get_boolean("show-hidden") @show_hidden.setter def show_hidden(self, value): if self._settings is None: return self._settings.set_boolean("show-hidden", value) @GObject.Property(type=str) def sort_order(self): if self._settings is None: return self.ALPHABETICAL_ORDER return self._settings.get_string("sort-order") @sort_order.setter def sort_order(self, value): if self._settings is None: return value = [self.ALPHABETICAL_ORDER, self.MODIFIED_TIME_ORDER].index(value) self._settings.set_enum("sort-order", value) Portfolio-1.0.2/src/style.css000066400000000000000000000052221476103320100161260ustar00rootroot00000000000000/* app */ .select-all-button, .previous-button { padding-right: 12.5px; padding-left: 12.5px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; } .select-none-button, .next-button { padding-right: 12.5px; padding-left: 12.5px; border-left: none; border-top-left-radius: 0px; border-bottom-left-radius: 0px; } .tool-button { margin-left: 5px; } /* placeholder */ .placeholder { color: @insensitive_fg_color; } .placeholder label { margin: 10px; font-size: medium; } /* about */ .about-box { margin: 10px 20px; margin-top: 60px; } .about-box .name-label { margin-top: 10px; font-size: medium; font-weight: bold; } .about-box .version-label { margin-top: 10px; font-size: medium; } .about-box .description-label { margin-top: 15px; font-size: medium; } .about-box .website-label { margin-top: 10px; font-size: medium; } .about-box .author-label { margin-top: 10px; font-size: small; } .about-box .license-label { margin-top: 10px; font-size: small; } /* credits */ .credits-grid { font-size: small; margin: 20px 0px; } .credits-grid .credits-label { font-weight: bold; margin-right: 5px; } /* menu */ .menu-box { margin: 5px; } .menu-box .menu-title { font-weight: bold; padding: 7.5px; } .menu-box .menu-item { padding: 7.5px; } /* loading */ .loading-bar { margin: 10px 10px; } .loading-label { color: @insensitive_fg_color; margin: 10px; font-size: x-large; } .loading-description { color: @insensitive_fg_color; margin: 10px; font-size: small; font-weight: bold; } .loading-details { color: @insensitive_fg_color; margin: 0px; font-size: x-small; } /* go to top button */ .go-top-button { border: none; box-shadow: none; border-radius: 9999px; margin: 12px; padding: 5px; min-width: 30px; min-height: 30px; } /* properties */ .properties { margin: 0px 25px 25px 25px; } .properties preferencesgroup { margin-top: 25px; } .properties .property { margin: 10px; font-size: medium; } .properties .value { margin: 10px; font-size: medium; color: @insensitive_fg_color; } /* places */ .places { margin: 25px; } .devices-group > box { margin-top: 15px; } label.no-places { margin: 10px; font-size: 20px; color: @insensitive_fg_color; } .eject-button { border: none; box-shadow: none; border-radius: 9999px; } /* passphrase screen */ .passphrase-details { color: @insensitive_fg_color; margin: 10px; font-size: small; font-weight: bold; } .passphrase-spinner { margin: 10px; } Portfolio-1.0.2/src/translation.py000066400000000000000000000023071476103320100171650ustar00rootroot00000000000000# translation.py # # Copyright 2021 Clayton Craft # Copyright 2021 Martin Abente Lahaye # # 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 . from . import logger gettext = lambda msg: msg def init(localedir): global gettext try: import locale gettext = locale.gettext locale.bindtextdomain("portfolio", localedir) locale.textdomain("portfolio") except AttributeError: logger.debug("Using fallback gettext module") import gettext as _gettext gettext = _gettext.gettext _gettext.bindtextdomain("portfolio", localedir) _gettext.textdomain("portfolio") Portfolio-1.0.2/src/trash.py000066400000000000000000000143101476103320100157450ustar00rootroot00000000000000# trash.py # # Copyright 2021 Martin Abente Lahaye # # 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 . import os import shutil from datetime import datetime from urllib.parse import quote, unquote from configparser import ConfigParser from gi.repository import GLib, Gio, GObject from . import utils from . import logger class PortfolioTrash(GObject.GObject): # https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html __gtype_name__ = "PortfolioTrash" def __init__(self): super().__init__() self._manager = Gio.VolumeMonitor.get() self._manager.connect("mount-added", self._on_mount_changed) self._manager.connect("mount-removed", self._on_mount_changed) self.setup() def _setup_trash_dir(self, trash_dir): os.makedirs(os.path.join(trash_dir, "info"), exist_ok=True) os.makedirs(os.path.join(trash_dir, "files"), exist_ok=True) def _on_mount_changed(self, monitor, mount): self.setup() def setup(self): self._trash = {} mounts = self.get_devices_trash() + [self.get_home_trash()] for mount_point, trash_dir in mounts: self._trash[mount_point] = trash_dir def is_trash(self, path): return path == "Trash" def in_trash(self, path): if self.is_trash(path): return True for _, trash_dir in self._trash.items(): if path.startswith(trash_dir): return True return False def has_trash(self, path): mount_point = utils.find_mount_point(path) return mount_point in self._trash def get_home_trash(self): if "PORTFOLIO_XDG_DATA_DIRS" in os.environ: data_dir = os.environ.get("PORTFOLIO_XDG_DATA_DIRS") elif utils.is_flatpak() and "HOST_XDG_DATA_DIRS" in os.environ: data_dir = os.environ.get("HOST_XDG_DATA_DIRS") elif not utils.is_flatpak() and GLib.get_user_data_dir(): data_dir = GLib.get_user_data_dir() else: data_dir = os.path.join(os.path.expanduser("~"), ".local", "share") self._home_trash = os.path.join(data_dir, "Trash") mount_point = utils.find_mount_point(data_dir) return mount_point, self._home_trash def get_devices_trash(self): trash = [] for mount in self._manager.get_mounts(): mount_point = mount.get_root().get_path() if not mount_point or not os.access(mount_point, os.W_OK): logger.debug("Can't find mount_point, skipping...") continue user_id = os.getuid() if not user_id: logger.debug("Can't find user_id, skipping...") continue path = os.path.join(mount_point, f".Trash-{user_id}") trash.append((mount_point, path)) return trash def get_info_path(self, path): files_dir = os.path.dirname(path) trash_dir = os.path.dirname(files_dir) info_dir = os.path.join(trash_dir, "info") info_path = os.path.join(info_dir, f"{os.path.basename(path)}.trashinfo") return info_path def get_orig_path(self, path): mount_point = utils.find_mount_point(path) trash_dir = self._trash.get(mount_point) info_path = self.get_info_path(path) info_file = ConfigParser(interpolation=None) info_file.optionxform = str info_file.read(info_path) orig_path = unquote(info_file["Trash Info"]["Path"]) # restore absolute path if trash_dir != self._home_trash and not os.path.isabs(orig_path): orig_path = os.path.join(mount_point, orig_path) return orig_path def list(self): paths = [] for name, trash_dir in self._trash.items(): files_dir = os.path.join(trash_dir, "files") if not os.path.exists(files_dir): continue for filename in os.listdir(files_dir): path = os.path.join(files_dir, filename) paths.append(path) return paths def trash(self, path): # prevent shutil.move strange behavior if not os.access(path, os.W_OK, follow_symlinks=False): raise PermissionError(f"Can't access {path}") mount_point = utils.find_mount_point(path) trash_dir = self._trash.get(mount_point) self._setup_trash_dir(trash_dir) files_dir = os.path.join(trash_dir, "files") info_dir = os.path.join(trash_dir, "info") name = utils.find_new_name(files_dir, os.path.basename(path), fmt="%s.%d") dest_path = os.path.join(files_dir, name) info_path = os.path.join(info_dir, f"{name}.trashinfo") if trash_dir == self._home_trash: orig_path = path else: orig_path = os.path.relpath(path, mount_point) info_file = ConfigParser(interpolation=None) info_file.optionxform = str info_file["Trash Info"] = {} info_file["Trash Info"]["Path"] = quote(orig_path) info_file["Trash Info"]["DeletionDate"] = ( datetime.utcnow().replace(microsecond=0).isoformat() ) with open(info_path, "w") as info: info_file.write(info) shutil.move(path, dest_path) def restore(self, path): orig_path = self.get_orig_path(path) info_path = self.get_info_path(path) os.remove(info_path) shutil.move(path, orig_path) def remove(self, path): info_path = self.get_info_path(path) os.remove(info_path) if os.path.isdir(path): shutil.rmtree(path) else: os.remove(path) default_trash = PortfolioTrash() Portfolio-1.0.2/src/utils.py000066400000000000000000000065251476103320100157750ustar00rootroot00000000000000# utils.py # # Copyright 2020 Martin Abente Lahaye # # 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 . import os import re from gi.repository import GLib, Gio from .cache import cached def find_new_name(directory, name, fmt="%s(%d)"): counter = 1 while os.path.lexists(os.path.join(directory, name)): components = re.split(r"(\(\d+\)$)", name) if len(components) > 1: name = "".join(components[:-2]) name = fmt % (name, counter) counter += 1 return name def count(path): _count = 1 if os.path.isdir(path): for directory, dirs, files in os.walk(path): _count += len(files) return _count def flatten_walk(path): _paths = [] def _callback(error): raise error if os.path.isdir(path): for directory, dirs, files in os.walk(path, topdown=False, onerror=_callback): for file in files: _paths += [os.path.join(directory, file)] _paths += [directory] else: _paths += [path] return _paths def get_uri_path(string): try: uri = GLib.uri_parse(string, GLib.UriFlags.NONE) return uri.get_path() except: return string def get_path_for_xdg(directory): path = GLib.get_user_special_dir(directory) if path is None: return None if path == GLib.get_home_dir(): return None if not GLib.file_test(path, GLib.FileTest.EXISTS): return None if not GLib.file_test(path, GLib.FileTest.IS_DIR): return None return path @cached def get_file_mtime(string): return os.lstat(string).st_mtime @cached def is_file_dir(string): return os.path.isdir(string) def get_file_icon(path): return ( Gio.file_new_for_path(path) .query_info( Gio.FILE_ATTRIBUTE_STANDARD_ICON, Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, ) .get_icon() ) def is_flatpak(): return os.path.exists(os.path.join(os.path.sep, ".flatpak-info")) def sync_folder(path): fd = os.open(path, os.O_DIRECTORY) os.fsync(fd) os.close(fd) def find_mount_point(path): path = os.path.abspath(path) while not os.path.ismount(path): path = os.path.dirname(path) return path # https://gist.github.com/cbwar/d2dfbc19b140bd599daccbe0fe925597 def get_size_for_humans(num): for unit in ["", "k", "M", "G", "T", "P", "E", "Z"]: if abs(num) < 1000.0: return "%3.1f %sB" % (num, unit) num /= 1000.0 return "%.1f%sB" % (num, "Y") def find_child_by_id(container, child_id): for widget in list(container): if widget.get_buildable_id() == child_id: return widget if child := find_child_by_id(widget, child_id): return child return None Portfolio-1.0.2/src/window.py000066400000000000000000001134311476103320100161370ustar00rootroot00000000000000# window.py # # Copyright 2020 Martin Abente Lahaye # # 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 . import os from .translation import gettext as _ from gi.repository import Adw, Gtk, GLib from . import utils from . import logger from .popup import PortfolioPopup from .worker import PortfolioCutWorker from .worker import PortfolioCopyWorker from .worker import PortfolioDeleteWorker from .worker import PortfolioLoadWorker from .worker import PortfolioOpenWorker from .worker import PortfolioPropertiesWorker from .worker import PortfolioRestoreTrashWorker from .worker import PortfolioDeleteTrashWorker from .worker import PortfolioSendTrashWorker from .worker import PortfolioLoadTrashWorker from .places import PortfolioPlaces from .properties import PortfolioProperties from .about import PortfolioAbout from .passphrase import PortfolioPassphrase from .placeholder import PortfolioPlaceholder from .loading import PortfolioLoading from .files import PortfolioFiles from .menu import PortfolioMenu from .settings import PortfolioSettings from .trash import default_trash @Gtk.Template(resource_path="/dev/tchx84/Portfolio/window.ui") class PortfolioWindow(Adw.ApplicationWindow): __gtype_name__ = "PortfolioWindow" previous = Gtk.Template.Child() next = Gtk.Template.Child() search = Gtk.Template.Child() rename = Gtk.Template.Child() detail = Gtk.Template.Child() delete = Gtk.Template.Child() cut = Gtk.Template.Child() copy = Gtk.Template.Child() paste = Gtk.Template.Child() select_all = Gtk.Template.Child() select_none = Gtk.Template.Child() new_folder = Gtk.Template.Child() delete_trash = Gtk.Template.Child() restore_trash = Gtk.Template.Child() close_button = Gtk.Template.Child() stop_button = Gtk.Template.Child() go_top_button = Gtk.Template.Child() about_back_button = Gtk.Template.Child() properties_back_button = Gtk.Template.Child() search_box = Gtk.Template.Child() search_entry = Gtk.Template.Child() popup_box = Gtk.Template.Child() action_stack = Gtk.Template.Child() tools_stack = Gtk.Template.Child() navigation_box = Gtk.Template.Child() selection_box = Gtk.Template.Child() selection_tools = Gtk.Template.Child() navigation_tools = Gtk.Template.Child() places_box = Gtk.Template.Child() places_inner_box = Gtk.Template.Child() places_popup_box = Gtk.Template.Child() content_stack = Gtk.Template.Child() loading_box = Gtk.Template.Child() loading_inner_box = Gtk.Template.Child() content_box = Gtk.Template.Child() files_stack = Gtk.Template.Child() files_box = Gtk.Template.Child() files_title = Gtk.Template.Child() about_box = Gtk.Template.Child() about_inner_box = Gtk.Template.Child() close_box = Gtk.Template.Child() close_tools = Gtk.Template.Child() stop_box = Gtk.Template.Child() stop_tools = Gtk.Template.Child() trash_tools = Gtk.Template.Child() about_deck = Gtk.Template.Child() content_deck = Gtk.Template.Child() placeholder_box = Gtk.Template.Child() placeholder_inner_box = Gtk.Template.Child() menu_button = Gtk.Template.Child() home_menu_button = Gtk.Template.Child() content_inner_box = Gtk.Template.Child() go_top_revealer = Gtk.Template.Child() properties_box = Gtk.Template.Child() properties_inner_box = Gtk.Template.Child() passphrase_title = Gtk.Template.Child() passphrase_box = Gtk.Template.Child() passphrase_inner_box = Gtk.Template.Child() passphrase_back_button = Gtk.Template.Child() SEARCH_DELAY = 500 LOAD_ANIMATION_DELAY = 250 def __init__(self, **kwargs): super().__init__(**kwargs) self._setup() def _setup(self): self._popup = None self._places_popup = None self._worker = None self._busy = False self._to_copy = [] self._to_cut = [] self._force_go_home = False self._history = [] self._index = -1 self._search_delay_handler_id = 0 self._load_delay_handler_id = 0 self._settings = PortfolioSettings() self._settings.connect("notify::show-hidden", self._on_show_hidden_changed) self._settings.connect("notify::sort-order", self._on_sort_order_changed) self.previous.connect("clicked", self._on_go_previous) self.next.connect("clicked", self._on_go_next) self.rename.connect("clicked", self._on_rename_clicked) self.detail.connect("clicked", self._on_detail_clicked) self.delete.connect("clicked", self._on_delete_clicked) self.cut.connect("clicked", self._on_cut_clicked) self.copy.connect("clicked", self._on_copy_clicked) self.paste.connect("clicked", self._on_paste_clicked) self.restore_trash.connect("clicked", self._on_restore_trash_clicked) self.delete_trash.connect("clicked", self._on_delete_trash_clicked) self.select_all.connect("clicked", self._on_select_all) self.select_none.connect("clicked", self._on_select_none) self.new_folder.connect("clicked", self._on_new_folder) self.close_button.connect("clicked", self._on_button_closed) self.go_top_button.connect("clicked", self._go_to_top) self.stop_button.connect("clicked", self._on_stop_clicked) self.about_back_button.connect("clicked", self._on_about_back_clicked) self.properties_back_button.connect("clicked", self._on_properties_back_clicked) self.passphrase_back_button.connect("clicked", self._on_passphrase_back_clicked) # XXX no model for options yet so this... self.menu_popover = PortfolioMenu(self._settings) self.menu_popover.connect("show-about", self._on_about_clicked) self.menu_button.props.popover = self.menu_popover self.menu_button.connect("activate", self._on_menu_button_clicked) self.home_menu_popover = PortfolioMenu(self._settings) self.home_menu_popover.connect("show-about", self._on_about_clicked) self.home_menu_button.props.popover = self.home_menu_popover self.home_menu_button.connect("activate", self._on_menu_button_clicked) self.search.connect("toggled", self._on_search_toggled) self.search_entry.connect("search-changed", self._on_search_changed) self.search_entry.connect("stop-search", self._on_search_stopped) # XXX Compose these widgets directly in .ui files self.files = PortfolioFiles() self.files.connect("activated", self._on_files_activated) self.files.connect("selected", self._on_files_selected) self.files.connect("rename-started", self._on_files_rename_started) self.files.connect("rename-finished", self._on_files_rename_finished) self.files.connect("rename-failed", self._on_files_rename_failed) self.files.connect("add-failed", self._on_files_add_failed) self.files.connect("adjustment-changed", self._on_files_adjustment_changed) self.files.sort_order = self._settings.sort_order self.content_inner_box.append(self.files) places = PortfolioPlaces() places.connect("updated", self._on_places_updated) places.connect("removing", self._on_places_removing) places.connect("removed", self._on_places_removed) places.connect("failed", self._on_places_failed) places.connect("unlock", self._on_places_unlock) self.places_inner_box.append(places) self._properties_worker = PortfolioPropertiesWorker() self.properties_inner_box.append(PortfolioProperties(self._properties_worker)) self.about_inner_box.append(PortfolioAbout()) self.passphrase = PortfolioPassphrase() self.passphrase.connect("unlocked", self._on_places_unlocked) self.passphrase_inner_box.append(self.passphrase) self.placeholder_inner_box.append(PortfolioPlaceholder()) self.loading = PortfolioLoading() self.loading_inner_box.append(self.loading) self.content_deck.connect("notify::visible-child", self._on_content_folded) self.connect("close-request", self._on_close_request) def _populate(self, directory): self.files.switch_to_navigation_mode() if self._worker is not None: self._worker.stop() if default_trash.is_trash(directory): loader_class = PortfolioLoadTrashWorker else: loader_class = PortfolioLoadWorker self._worker = loader_class(directory, self._settings.show_hidden) self._worker.connect("started", self._on_load_started) self._worker.connect("updated", self._on_load_updated) self._worker.connect("finished", self._on_load_finished) self._worker.connect("failed", self._on_load_failed) self._worker.start() def _paste(self, Worker, to_paste): directory = self._history[self._index] self._worker = Worker(to_paste, directory) self._worker.connect("started", self._on_paste_started) self._worker.connect("updated", self._on_paste_updated) self._worker.connect("post-update", self._on_paste_post_updated) self._worker.connect("finished", self._on_paste_finished) self._worker.connect("failed", self._on_paste_failed) self._worker.connect("stopped", self._on_paste_stopped) self._worker.start() def _paste_finish(self): self._busy = False self._clean_workers() self.loading.clean() self._to_cut = [] self._to_copy = [] self.files.unselect_all() self._update_all() def _delete_finish(self): self._busy = False self._clean_workers() self.loading.clean() self.files.unselect_all() self._update_all() def _get_row(self, model, treepath): return model.get_iter(treepath) def _go_to_top(self, *args): self.files.go_to_top() def _go_back_to_homepage(self): self.content_deck.set_visible_child(self.places_box) def _go_to_files_view(self, duration): self.content_deck.set_visible_child(self.files_stack) self.files_stack.props.transition_duration = duration self.files_stack.set_visible_child(self.files_box) def _move(self, path, navigating=False): self._clean_popups() if path is None: return elif default_trash.is_trash(path) or utils.is_file_dir(path): self._update_history(path, navigating) self._populate(path) else: self._open(path) def _open(self, path): self._worker = PortfolioOpenWorker(path) self._worker.connect("started", self._on_open_started) self._worker.connect("updated", self._on_open_updated) self._worker.connect("finished", self._on_open_finished) self._worker.connect("failed", self._on_open_failed) self._worker.start() def _reset_to_path(self, path): self._history = [] self._index = -1 self._move(path, False) def _refresh(self): if self._index > -1: self._move(self._history[self._index], True) def _notify(self, description, on_confirm, on_cancel, on_trash, autoclose, data): self._clean_popups() self._popup = PortfolioPopup( description, on_confirm, on_cancel, on_trash, autoclose, data ) self.popup_box.append(self._popup) self._popup.props.reveal_child = True def _places_notify(self, description): self._clean_places_popups() self._places_popup = PortfolioPopup(description, None, None, None, False, None) self.places_popup_box.append(self._places_popup) self._places_popup.props.reveal_child = True def _clean_workers(self): del self._worker self._worker = None def _clean_popups(self): if self._popup is None: return if self._popup.get_parent() is not None: self.popup_box.remove(self._popup) self._popup = None def _clean_places_popups(self): if self._places_popup is None: return if self._places_popup.get_parent() is not None: self.places_popup_box.remove(self._places_popup) self._places_popup = None def _clean_loading_delay(self): if self._load_delay_handler_id != 0: GLib.Source.remove(self._load_delay_handler_id) self._load_delay_handler_id = 0 def _update_history(self, path, navigating): if path not in self._history or not navigating: del self._history[self._index + 1 :] self._history.append(path) self._index += 1 def _update_all(self): self._update_search() self._update_treeview() self._update_content_stack() self._update_navigation() self._update_navigation_tools() self._update_trash_tools() self._update_selection() self._update_selection_tools() self._update_action_stack() self._update_tools_stack() self._update_menu() def _update_search(self): sensitive = not self.files.is_editing and not self._busy self.search.props.sensitive = sensitive self.search_entry.sensitive = sensitive def _update_treeview(self): self.files.update(not self._busy) def _update_content_stack(self): if self._busy: return elif self.files.is_empty: self.content_stack.set_visible_child(self.placeholder_box) else: self.content_stack.set_visible_child(self.content_box) def _update_navigation(self): count = self.files.selected_count selected = count >= 1 if selected or self._busy: self.previous.props.sensitive = False self.next.props.sensitive = False return self.previous.props.sensitive = True self.next.props.sensitive = ( True if len(self._history) - 1 > self._index else False ) def _update_selection(self): sensitive = not self.files.is_editing and not self._busy self.select_all.props.sensitive = sensitive self.select_none.props.sensitive = sensitive def _update_action_stack(self): count = self.files.selected_count selected = count >= 1 child = self.selection_box if selected else self.navigation_box self.action_stack.set_visible_child(child) def _update_tools_stack(self): directory = self._history[self._index] if default_trash.in_trash(directory): self.tools_stack.set_visible_child(self.trash_tools) return count = self.files.selected_count selected = count >= 1 child = self.selection_tools if selected else self.navigation_tools self.tools_stack.set_visible_child(child) def _update_selection_tools(self): count = self.files.selected_count sensitive = count >= 1 and not self.files.is_editing and not self._busy self.delete.props.sensitive = sensitive self.cut.props.sensitive = sensitive self.copy.props.sensitive = sensitive self._update_rename() self._update_detail() def _update_navigation_tools(self): count = self.files.selected_count selected = count >= 1 to_paste = len(self._to_cut) >= 1 or len(self._to_copy) >= 1 self.paste.props.sensitive = not selected and to_paste and not self._busy self.new_folder.props.sensitive = not selected and not self._busy def _update_trash_tools(self): selected = self.files.selected_count >= 1 is_trash = default_trash.is_trash(self._history[self._index]) self.restore_trash.props.sensitive = selected and is_trash self.delete_trash.props.sensitive = selected and is_trash def _update_rename(self): count = self.files.selected_count sensitive = count == 1 and not self.files.is_editing and not self._busy self.rename.props.sensitive = sensitive def _update_detail(self): count = self.files.selected_count sensitive = count == 1 and not self.files.is_editing and not self._busy self.detail.props.sensitive = sensitive def _update_directory_title(self): directory = self._history[self._index] if default_trash.is_trash(directory): name = PortfolioPlaces.XDG_TRASH_NAME else: name = os.path.basename(directory) self.files_title.props.title = name def _update_filter(self): self.files.filter = self.search_entry.get_text() self._update_content_stack() self._search_delay_handler_id = 0 return GLib.SOURCE_REMOVE def _update_menu(self): self.menu_popover.is_sensitive = not self._busy self.home_menu_popover.is_sensitive = not self._busy def _reset_search(self): self.search.set_active(False) self.search_entry.set_text("") self.search.grab_focus() def _on_files_activated(self, files, path): self._move(path) def _on_files_selected(self, files): if self._busy is True: return self._update_all() def _on_files_rename_started(self, files): self._update_search() self._update_selection() self._update_selection_tools() def _on_files_rename_finished(self, files): self._update_all() def _on_files_rename_failed(self, files, new_name): self._on_rename_clicked(None) self._notify( _("%s already exists") % new_name, None, self._on_popup_closed, None, True, None, ) def _on_files_add_failed(self, files): self._notify( _("No permissions on this directory"), None, self._on_popup_closed, None, True, None, ) def _on_files_adjustment_changed(self, files, reveal): self.go_top_revealer.props.reveal_child = reveal def _on_open_started(self, worker): self._busy = True self.loading.update(_("Opening"), 0.0, "", "") self.content_stack.set_visible_child(self.loading_box) self._update_all() def _on_open_updated(self, worker): self.loading.pulse() def _on_open_finished(self, worker): self._busy = False self._clean_workers() self._update_all() def _on_open_failed(self, worker, path): self._busy = False self._clean_workers() name = os.path.basename(path) self.loading.update(description=_("Could not open %s") % name) self.action_stack.set_visible_child(self.close_box) self.tools_stack.set_visible_child(self.close_tools) def _on_load_started(self, worker, directory): self._busy = True self.files.clear() self._update_directory_title() self._reset_search() self._update_all() self._load_delay_handler_id = GLib.timeout_add( self.LOAD_ANIMATION_DELAY, self._on_load_started_delayed, ) def _on_load_started_delayed(self): self.loading.update(_("Loading"), 0.0, "", "") self.content_stack.set_visible_child(self.loading_box) self._update_all() self._load_delay_handler_id = 0 return GLib.SOURCE_REMOVE def _on_load_updated(self, worker, directory, found, index, total): for name, path, icon in found: self.files.add_row(icon, name, path) self.loading.update(progress=(index + 1) / total) def _on_load_finished(self, worker, directory): self._busy = False self._clean_workers() self._clean_loading_delay() self._update_all() self.files.update_scrolling() def _on_load_failed(self, worker, directory): self._busy = False self._clean_workers() self._clean_loading_delay() name = os.path.basename(directory) self.loading.update( title=_("Loading"), description=_("Could not load %s") % name ) self.content_stack.set_visible_child(self.loading_box) self.files._clear_select_and_go() self._force_go_home = True self.action_stack.set_visible_child(self.close_box) self.tools_stack.set_visible_child(self.close_tools) def _on_go_previous(self, button): if self._index == 0: self._go_back_to_homepage() else: self.files.to_go_to_path = self._history[self._index] self._index -= 1 self._move(self._history[self._index], True) def _on_go_next(self, button): self._index += 1 self._move(self._history[self._index], True) def _on_search_toggled(self, button): toggled = self.search.get_active() self.search_box.props.search_mode_enabled = toggled def _on_search_changed(self, entry): if self._search_delay_handler_id != 0: GLib.Source.remove(self._search_delay_handler_id) self._search_delay_handler_id = GLib.timeout_add( self.SEARCH_DELAY, self._update_filter ) def _on_search_stopped(self, entry): self._reset_search() def _on_detail_clicked(self, button): path = self.files.get_selected_path() self.show_properties(path) def _on_rename_clicked(self, button): self.files.rename_selected_row() def _on_delete_clicked(self, button): selection = self.files.get_selection() count = len(selection) directory = self._history[self._index] if count == 1: name = os.path.basename(selection[0][0]) else: name = _("these %d files") % count description = _("Delete %s permanently?") % name self._notify( description, self._on_delete_confirmed, self._on_popup_closed, self._on_trash_instead if default_trash.has_trash(directory) else None, False, selection, ) def _on_cut_clicked(self, button): selection = self.files.get_selection() count = len(selection) self._to_cut = selection self._to_copy = [] if count == 1: name = os.path.basename(selection[0][0]) description = _("%s will be moved") % name else: description = _("%d files will be moved") % count self._notify(description, None, None, None, True, None) self.files.unselect_all() def _on_copy_clicked(self, button): selection = self.files.get_selection() count = len(selection) self._to_copy = selection self._to_cut = [] if count == 1: name = os.path.basename(selection[0][0]) description = _("%s will be copied") % name else: description = _("%d files will be copied") % count self._notify(description, None, None, None, True, None) self.files.unselect_all() def _on_paste_clicked(self, button): to_paste = self._to_copy if self._to_copy else self._to_cut Worker = PortfolioCopyWorker if self._to_copy else PortfolioCutWorker directory = self._history[self._index] should_warn = any( [ os.path.dirname(path) != directory and os.path.lexists(os.path.join(directory, os.path.basename(path))) for path, ref in to_paste ] ) if not should_warn: self._paste(Worker, to_paste) return self._notify( _("Files will be overwritten, proceed?"), self._on_paste_confirmed, self._on_popup_closed, None, False, (to_paste, Worker), ) def _on_paste_confirmed(self, button, popup, data): to_paste, Worker = data self._clean_popups() self._paste(Worker, to_paste) def _on_paste_started(self, worker, total): self._busy = True self.loading.update(_("Pasting"), 0.0, "", "") self.content_stack.set_visible_child(self.loading_box) self._update_all() self.action_stack.set_visible_child(self.stop_box) self.tools_stack.set_visible_child(self.stop_tools) def _on_paste_post_updated(self, worker, name, path, icon, overwritten): if overwritten: return if not os.path.exists(path): logger.debug(f"Attempting to add unexisting {path}") return self.files.add_row(icon, name, path) def _on_paste_updated(self, worker, path, index, total, current_bytes, total_bytes): description = os.path.basename(path) human_current_bytes = utils.get_size_for_humans(current_bytes) human_total_bytes = utils.get_size_for_humans(total_bytes) self.loading.update( description=description, progress=index / total, details=_("%s of %s") % (human_current_bytes, human_total_bytes), ) def _on_paste_finished(self, worker, total): self._paste_finish() def _on_paste_failed(self, worker, path): self._busy = False self._clean_workers() self.loading.clean() self._to_cut = [] self._to_copy = [] name = os.path.basename(path) self.loading.update(description=_("Could not paste %s") % name) self.action_stack.set_visible_child(self.close_box) self.tools_stack.set_visible_child(self.close_tools) def _on_paste_stopped(self, worker): self._paste_finish() # XXX nuclear fix for when parent directory does not get to be updated self._refresh() def _on_trash_instead(self, button, popup, selection): self._clean_popups() # clean history entries from deleted paths directory = self._history[self._index] self._history = [ path for path in self._history if not path.startswith(directory) or path == directory ] self._worker = PortfolioSendTrashWorker(selection) self._worker.connect("started", self._on_delete_started) self._worker.connect("pre-update", self._on_delete_pre_updated) self._worker.connect("updated", self._on_delete_updated) self._worker.connect("finished", self._on_delete_finished) self._worker.connect("failed", self._on_delete_failed) self._worker.connect("stopped", self._on_delete_stopped) self._worker.start() def _on_delete_confirmed(self, button, popup, selection): self._clean_popups() # clean history entries from deleted paths directory = self._history[self._index] self._history = [ path for path in self._history if not path.startswith(directory) or path == directory ] self._worker = PortfolioDeleteWorker(selection) self._worker.connect("started", self._on_delete_started) self._worker.connect("pre-update", self._on_delete_pre_updated) self._worker.connect("updated", self._on_delete_updated) self._worker.connect("finished", self._on_delete_finished) self._worker.connect("failed", self._on_delete_failed) self._worker.connect("stopped", self._on_delete_stopped) self._worker.start() def _on_delete_started(self, worker): self._busy = True self.loading.update(_("Deleting"), 0.0, "", "") self.content_stack.set_visible_child(self.loading_box) self._update_all() self.action_stack.set_visible_child(self.stop_box) self.tools_stack.set_visible_child(self.stop_tools) def _on_delete_pre_updated(self, worker, path): name = os.path.basename(path) self.loading.update(description=name) def _on_delete_updated(self, worker, path, row, index, total): self.files.remove_row(row) self.loading.update(progress=(index + 1) / total) def _on_delete_finished(self, worker, total): self._delete_finish() def _on_delete_failed(self, worker, path): self._busy = False self._clean_workers() self.loading.clean() name = os.path.basename(path) self.loading.update(description=_("Could not delete %s") % name) self.action_stack.set_visible_child(self.close_box) self.tools_stack.set_visible_child(self.close_tools) def _on_delete_stopped(self, worker): self._delete_finish() def _on_popup_closed(self, button, popup, data): self._clean_popups() def _on_button_closed(self, button): self.loading.clean() self.files.unselect_all() self._update_all() if self._force_go_home is False: return self._force_go_home = False self._go_back_to_homepage() def _on_stop_clicked(self, button): self.loading.update(title=_("Stopping")) self._worker.stop() def _on_select_all(self, button): self.files.select_all() def _on_select_none(self, button): self.files.unselect_all() def _on_new_folder(self, button): directory = self._history[self._index] self.files.add_new_folder_row(directory) def _on_restore_trash_clicked(self, button): selection = self.files.get_selection() paths = [default_trash.get_orig_path(path) for path, ref in selection] overwrites = any([os.path.lexists(path) for path in paths if path]) duplicates = len(set(paths)) != len(paths) if not overwrites and not duplicates: self._on_restore_trash_confirmed(None, None, selection) return self._notify( _("Files will be overwritten, proceed?"), self._on_restore_trash_confirmed, self._on_popup_closed, None, False, selection, ) def _on_restore_trash_confirmed(self, button, popup, selection): self._clean_popups() self._worker = PortfolioRestoreTrashWorker(selection) self._worker.connect("started", self._on_restore_trash_started) self._worker.connect("pre-update", self._on_restore_trash_pre_updated) self._worker.connect("updated", self._on_restore_trash_updated) self._worker.connect("finished", self._on_restore_trash_finished) self._worker.connect("failed", self._on_restore_trash_failed) self._worker.connect("stopped", self._on_restore_trash_stopped) self._worker.start() def _on_restore_trash_started(self, worker): self._busy = True self.loading.update(_("Restoring"), 0.0, "", "") self.content_stack.set_visible_child(self.loading_box) self._update_all() self.action_stack.set_visible_child(self.stop_box) self.tools_stack.set_visible_child(self.stop_tools) def _on_restore_trash_pre_updated(self, worker, path): name = os.path.basename(path) self.loading.update(description=name) def _on_restore_trash_updated(self, worker, path, row, index, total): self.files.remove_row(row) self.loading.update(progress=(index + 1) / total) def _on_restore_trash_finished(self, worker, total): self._busy = False self._clean_workers() self.loading.clean() self.files.unselect_all() self._update_all() def _on_restore_trash_failed(self, worker, path): self._busy = False self._clean_workers() self.loading.clean() self.loading.update(description=_("Could not restore %s") % path) self.action_stack.set_visible_child(self.close_box) self.tools_stack.set_visible_child(self.close_tools) def _on_restore_trash_stopped(self, worker): self._delete_finish() def _on_delete_trash_clicked(self, button): selection = self.files.get_selection() count = len(selection) if count == 1: name = os.path.basename(selection[0][0]) else: name = _("these %d files") % count description = _("Delete %s permanently?") % name self._notify( description, self._on_delete_trash_confirmed, self._on_popup_closed, None, False, selection, ) def _on_delete_trash_confirmed(self, button, popup, selection): self._clean_popups() self._worker = PortfolioDeleteTrashWorker(selection) self._worker.connect("started", self._on_delete_trash_started) self._worker.connect("pre-update", self._on_delete_trash_pre_updated) self._worker.connect("updated", self._on_delete_trash_updated) self._worker.connect("finished", self._on_delete_trash_finished) self._worker.connect("failed", self._on_delete_trash_failed) self._worker.connect("stopped", self._on_delete_trash_stopped) self._worker.start() def _on_delete_trash_started(self, worker): self._busy = True self.loading.update(_("Deleting"), 0.0, "", "") self.content_stack.set_visible_child(self.loading_box) self._update_all() self.action_stack.set_visible_child(self.stop_box) self.tools_stack.set_visible_child(self.stop_tools) def _on_delete_trash_pre_updated(self, worker, path): name = os.path.basename(path) self.loading.update(description=name) def _on_delete_trash_updated(self, worker, path, row, index, total): self.files.remove_row(row) self.loading.update(progress=(index + 1) / total) def _on_delete_trash_finished(self, worker, total): self._delete_finish() def _on_delete_trash_failed(self, worker, path): self._busy = False self._clean_workers() self.loading.clean() self.loading.update(description=_("Could not delete %s") % path) self.action_stack.set_visible_child(self.close_box) self.tools_stack.set_visible_child(self.close_tools) def _on_delete_trash_stopped(self, worker): self._delete_finish() def _on_places_updated(self, button, path): self._reset_to_path(path) self._go_to_files_view(duration=0) def _on_places_removing(self, button, path): self._places_notify(_("Removing device, please wait")) def _on_places_removed(self, button, path, safely): if safely is True: self._places_notify(_("Device can be removed")) if self._index == -1: return directory = self._history[self._index] if not path or not directory.startswith(path): return self._go_back_to_homepage() def _on_places_failed(self, button, path): self._places_notify(_("Device is busy, can't be removed")) def _on_places_unlock(self, button, encrypted): self.passphrase.unlock(encrypted) self.passphrase_title.props.title = encrypted.get_friendly_label() self.content_deck.set_visible_child(self.files_stack) self.files_stack.set_visible_child(self.passphrase_box) def _on_places_unlocked(self, widget, path): self._reset_to_path(path) self._go_to_files_view(duration=200) def _on_passphrase_back_clicked(self, button): self.passphrase.clean() self.content_deck.set_visible_child(self.places_box) def _on_about_clicked(self, button): self.about_deck.set_visible_child(self.about_box) def _on_about_back_clicked(self, button): self.about_deck.set_visible_child(self.content_deck) def _on_properties_back_clicked(self, button): self.content_deck.set_visible_child(self.files_stack) def _on_show_hidden_changed(self, settings, data): self._refresh() def _on_sort_order_changed(self, settings, data): self.files.sort_order = self._settings.sort_order self._refresh() def _on_menu_button_clicked(self, button): button.props.popover.popup() def _on_content_folded(self, deck, data=None): child = self.content_deck.get_visible_child() if child == self.places_box and self._worker is not None: self._worker.stop() self._clean_workers() elif child == self.files_box: self._properties_worker.stop() def _on_close_request(self, window): if self._worker is not None: self._worker.stop() self._properties_worker.stop() def open(self, path=PortfolioPlaces.PORTFOLIO_HOME_DIR, force_page_switch=False): path = utils.get_uri_path(path) # make sure it exists though ! if not os.path.exists(path): logger.warning(_("Could not open %s") % path) return # if it's a file then use its parent folder if not os.path.isdir(path): self.files.to_select_path = path path = os.path.dirname(path) # XXX no support for background workers yet if self._busy and not isinstance(self._worker, PortfolioLoadWorker): logger.warning(_("Could not open %s") % path) return if isinstance(self._worker, PortfolioLoadWorker): self._worker.stop() self._clean_workers() self._clean_loading_delay() self._reset_to_path(path) if force_page_switch is False: return self.about_deck.set_visible_child(self.content_deck) self.content_deck.set_visible_child(self.files_stack) def show_properties(self, path, force_page_switch=False): self._properties_worker.props.path = path if force_page_switch is True: self.open(path, force_page_switch) self.content_deck.set_visible_child(self.properties_box) Portfolio-1.0.2/src/window.ui000066400000000000000000001233731476103320100161320ustar00rootroot00000000000000 Portfolio-1.0.2/src/worker.py000066400000000000000000000635471476103320100161550ustar00rootroot00000000000000# worker.py # # Copyright 2020 Martin Abente Lahaye # # 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 . import os import sys import stat import time import shutil import locale import datetime import threading from pwd import getpwuid from grp import getgrgid from gi.repository import Gio, GObject, GLib from . import utils from . import logger from .cache import default_cache from .translation import gettext as _ from .trash import default_trash class WorkerStoppedException(Exception): pass class CachedWorker(object): def __init__(self): default_cache.activate() def __del__(self): default_cache.deactivate() class PortfolioWorker(threading.Thread, GObject.GObject): __gtype_name__ = "PortfolioWorker" def __init__(self): threading.Thread.__init__(self) GObject.GObject.__init__(self) self._cancellable = Gio.Cancellable() def _progress(self, current, total): logger.debug(current, total) def _stop_check(self): if self._cancellable.is_cancelled(): raise WorkerStoppedException() def stop(self): if not self._cancellable.is_cancelled(): self._cancellable.cancel() def emit(self, *args): GLib.idle_add(GObject.GObject.emit, self, *args) class PortfolioCopyWorker(PortfolioWorker): __gtype_name__ = "PortfolioCopyWorker" __gsignals__ = { "started": (GObject.SignalFlags.RUN_LAST, None, (int,)), "updated": (GObject.SignalFlags.RUN_LAST, None, (str, int, int, float, float)), "post-update": (GObject.SignalFlags.RUN_LAST, None, (str, str, object, bool)), "finished": (GObject.SignalFlags.RUN_LAST, None, (int,)), "failed": (GObject.SignalFlags.RUN_LAST, None, (str,)), "stopped": (GObject.SignalFlags.RUN_LAST, None, ()), } def __init__(self, selection, directory=None): super().__init__() self._selection = selection self._directory = directory self._copy_was_called = False def _report_status(self, path, current_bytes, total_bytes): self.emit( "updated", path, self._count, self._total, current_bytes, total_bytes, ) def _do_copy(self, source, destination): current_bytes = 0 total_bytes = os.stat(source.name).st_size arch_capped = sys.maxsize < 2**32 # start with an arbitrary block size block_min_bytes = 2**23 block_bytes = block_min_bytes last_sync = time.monotonic() outfd = destination.fileno() infd = source.fileno() while True: self._stop_check() # assume kernel >= 2.6.33 sent_bytes = os.sendfile(outfd, infd, current_bytes, block_bytes) if sent_bytes == 0: break # fsync frequency is indirectly controlled by the size of the block os.fsync(outfd) # now dynamically adjust the block size so that it # will keep fsync frequency between 750 and 1250ms now = time.monotonic() if now - last_sync > 1.25: block_bytes //= 2 elif now - last_sync < 0.75: block_bytes *= 2 # on 32 bit arch block size must be capped if arch_capped: block_bytes = min(block_bytes, 2**30) # some lower and upper bounds block_bytes = max(block_bytes, block_min_bytes) block_bytes = min(block_bytes, total_bytes) last_sync = now current_bytes += sent_bytes self._report_status(destination.name, current_bytes, total_bytes) logger.debug(f"sendfile block size converged to {block_bytes}") def _copy(self, source_path, destination_path): if os.path.islink(source_path): os.symlink(os.readlink(source_path), destination_path) return with open(source_path, "rb") as source: with open(destination_path, "wb") as destination: self._do_copy(source, destination) shutil.copymode(source_path, destination_path) self._count += 1 self._copy_was_called = True def run(self): self._count = 0 self._total = sum([utils.count(path) for path, ref in self._selection]) self.emit("started", self._total) for path, ref in self._selection: name = os.path.basename(path) destination = os.path.join(self._directory, name) overwritten = os.path.lexists(destination) if path == destination: name = utils.find_new_name(self._directory, name) destination = os.path.join(self._directory, name) overwritten = False try: self._stop_check() if os.path.isdir(path): if overwritten and os.path.isdir(path): shutil.rmtree(destination) shutil.copytree(path, destination, copy_function=self._copy) else: self._copy(path, destination) except WorkerStoppedException: self.emit("stopped") return except Exception as e: logger.debug(e) self.emit("failed", destination) return else: utils.sync_folder(os.path.dirname(destination)) self.emit( "post-update", os.path.basename(destination), destination, utils.get_file_icon(destination), overwritten, ) self.emit("finished", self._total) class PortfolioCutWorker(PortfolioCopyWorker): __gtype_name__ = "PortfolioCutWorker" def run(self): self._count = 0 self._total = sum([utils.count(path) for path, ref in self._selection]) self.emit("started", self._total) for path, ref in self._selection: name = os.path.basename(path) destination = os.path.join(self._directory, name) overwritten = os.path.lexists(destination) try: self._stop_check() if destination == path: continue if overwritten and os.path.isdir(path): shutil.rmtree(destination) if overwritten and os.path.islink(destination): os.unlink(destination) shutil.move(path, destination, copy_function=self._copy) except WorkerStoppedException: self.emit("stopped") return except Exception as e: logger.debug(e) self.emit("failed", path) return else: utils.sync_folder(os.path.dirname(destination)) # XXX force report even if count is not precise if self._copy_was_called is False: self._count += 1 total_bytes = os.lstat(destination).st_size self._report_status(destination, total_bytes, total_bytes) self.emit( "post-update", os.path.basename(destination), destination, utils.get_file_icon(destination), overwritten, ) self.emit("finished", self._total) class PortfolioDeleteWorker(GObject.GObject, CachedWorker): __gtype_name__ = "PortfolioDeleteWorker" __gsignals__ = { "started": (GObject.SignalFlags.RUN_LAST, None, ()), "pre-update": (GObject.SignalFlags.RUN_LAST, None, (str,)), "updated": (GObject.SignalFlags.RUN_LAST, None, (str, object, int, int)), "finished": (GObject.SignalFlags.RUN_LAST, None, (int,)), "failed": (GObject.SignalFlags.RUN_LAST, None, (str,)), "stopped": (GObject.SignalFlags.RUN_LAST, None, ()), } def __init__(self, selection): super().__init__() CachedWorker.__init__(self) self._selection = selection self._timeout_handler_id = None def start(self): self.emit("started") try: paths = [] for path, ref in self._selection: paths += utils.flatten_walk(path) except Exception as e: logger.debug(e) self.emit("failed", path) return self._paths = paths self._refs = dict(self._selection) self._total = len(paths) self._index = 0 self._timeout_handler_id = GLib.idle_add( self.step, priority=GLib.PRIORITY_HIGH_IDLE + 20 ) def step(self): if self._index >= self._total: self.emit("finished", self._total) return path = self._paths[self._index] try: self.emit("pre-update", path) if os.path.isdir(path): os.rmdir(path) else: os.unlink(path) except Exception as e: logger.debug(e) self.emit("failed", path) return self._index += 1 self.emit("updated", path, self._refs.get(path), self._index, self._total) self._timeout_handler_id = GLib.idle_add( self.step, priority=GLib.PRIORITY_HIGH_IDLE + 20 ) def stop(self): if self._timeout_handler_id is not None: GLib.Source.remove(self._timeout_handler_id) self._timeout_handler_id = None self.emit("stopped") class PortfolioLoadWorker(GObject.GObject, CachedWorker): __gtype_name__ = "PortfolioLoadWorker" __gsignals__ = { "started": (GObject.SignalFlags.RUN_LAST, None, (str,)), "updated": (GObject.SignalFlags.RUN_LAST, None, (str, object, int, int)), "finished": (GObject.SignalFlags.RUN_LAST, None, (str,)), "failed": (GObject.SignalFlags.RUN_LAST, None, (str,)), } BUFFER = 75 def __init__(self, directory, hidden=False): super().__init__() CachedWorker.__init__(self) self._directory = directory self._hidden = hidden self._timeout_handler_id = None def start(self): self.emit("started", self._directory) try: self._paths = os.listdir(self._directory) except Exception as e: logger.debug(e) self.emit("failed", self._directory) return self._total = len(self._paths) self._index = 0 self._timeout_handler_id = GLib.idle_add( self.step, priority=GLib.PRIORITY_HIGH_IDLE + 20 ) def step(self): if self._index >= self._total: self.emit("finished", self._directory) return found = [] for index in range(0, self.BUFFER): if self._index + index < self._total: name = self._paths[self._index + index] if not self._hidden and name.startswith("."): continue path = os.path.join(self._directory, name) found.append((name, path, utils.get_file_icon(path))) self._index += self.BUFFER self.emit("updated", self._directory, found, self._index, self._total) self._timeout_handler_id = GLib.idle_add( self.step, priority=GLib.PRIORITY_HIGH_IDLE + 20 ) def stop(self): if self._timeout_handler_id is None: return GLib.Source.remove(self._timeout_handler_id) self._timeout_handler_id = None class PortfolioOpenWorker(GObject.GObject): __gtype_name__ = "PortfolioOpenWorker" __gsignals__ = { "started": (GObject.SignalFlags.RUN_LAST, None, ()), "updated": (GObject.SignalFlags.RUN_LAST, None, ()), "finished": (GObject.SignalFlags.RUN_LAST, None, ()), "failed": (GObject.SignalFlags.RUN_LAST, None, (str,)), } def __init__(self, path): super().__init__() self._path = path self._timeout_handler_id = None self._is_flatpak = utils.is_flatpak() def start(self): self.emit("started") uri = GLib.Uri.build( GLib.UriFlags.NONE, "file", None, None, -1, self._path, None, None ).to_string() Gio.AppInfo.launch_default_for_uri_async( uri, None, None, self._on_launch_finished, None ) self._timeout_handler_id = GLib.timeout_add(100, self._on_step) def stop(self): pass def _on_step(self): self.emit("updated") return True def _on_launch_finished(self, worker, result, data=None): if self._timeout_handler_id is not None: GLib.Source.remove(self._timeout_handler_id) self._timeout_handler_id = None if result.had_error(): self.emit("failed", self._path) else: self.emit("finished") class PortfolioPropertiesWorker(GObject.GObject): class InnerWorker(threading.Thread): def __init__(self, worker): super().__init__() self._stop = False self._worker = worker def stop(self): self._stop = True def run(self): size = 0 for directory, folders, files in os.walk(self._worker._path): for filename in files: if self._stop: return try: size += os.lstat(os.path.join(directory, filename)).st_size except: pass self._worker._size = utils.get_size_for_humans(size) GLib.idle_add(self._worker.notify, "size") def __init__(self): super().__init__() self._path = "" self._name = "" self._location = "" self._type = "" self._size = "" self._created = "" self._modified = "" self._accessed = "" self._permissions_owner = "" self._permissions_group = "" self._permissions_others = "" self._owner = "" self._group = "" self._inner_worker = self.InnerWorker(self) def _get_file_size(self): return os.lstat(self._path).st_size def _update_size(self): if os.path.isdir(self._path): self._inner_worker = self.InnerWorker(self) self._inner_worker.start() else: self._size = utils.get_size_for_humans(self._get_file_size()) self.notify("size") def _get_human_time(self, timestamp): fmt = locale.nl_langinfo(locale.D_T_FMT) return datetime.datetime.fromtimestamp(timestamp).strftime(fmt) def _get_human_permissions(self, path, target="owner"): permissions = [] mode = os.lstat(path).st_mode if target == "owner": can_read = stat.S_IRUSR can_write = stat.S_IWUSR can_exec = stat.S_IXUSR elif target == "group": can_read = stat.S_IRGRP can_write = stat.S_IWGRP can_exec = stat.S_IXGRP else: can_read = stat.S_IROTH can_write = stat.S_IWOTH can_exec = stat.S_IXOTH if mode & can_read: permissions.append(_("read")) if mode & can_write: permissions.append(_("write")) if mode & can_exec: permissions.append(_("execute")) if permissions: return ", ".join(permissions) return _("do nothing") def _get_human_owner(self, path): owner_id = os.lstat(path).st_uid owner = getpwuid(owner_id).pw_name return owner def _get_human_group(self, path): group_id = os.lstat(path).st_gid group = getgrgid(group_id).gr_name return group @GObject.Property(type=str) def name(self): return self._name @GObject.Property(type=str) def location(self): return self._location @GObject.Property(type=str) def type(self): return self._type @GObject.Property(type=str) def size(self): return self._size @GObject.Property(type=str) def created(self): return self._created @GObject.Property(type=str) def modified(self): return self._modified @GObject.Property(type=str) def accessed(self): return self._accessed @GObject.Property(type=str) def permissions_owner(self): return self._permissions_owner @GObject.Property(type=str) def permissions_group(self): return self._permissions_group @GObject.Property(type=str) def permissions_others(self): return self._permissions_others @GObject.Property(type=str) def owner(self): return self._owner @GObject.Property(type=str) def group(self): return self._group @GObject.Property(type=str) def path(self): return self._path @path.setter def path(self, path): self._path = path self._inner_worker.stop() file = Gio.File.new_for_path(path) info = file.query_info( Gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, None, ) self._name = os.path.basename(self._path) self._location = os.path.dirname(self._path) self._type = info.get_content_type() self._size = _("Calculating...") self._created = self._get_human_time(os.lstat(self._path).st_ctime) self._modified = self._get_human_time(os.lstat(self._path).st_mtime) self._accessed = self._get_human_time(os.lstat(self._path).st_atime) self._permissions_owner = self._get_human_permissions(self._path, "owner") self._permissions_group = self._get_human_permissions(self._path, "group") self._permissions_others = self._get_human_permissions(self._path, "others") self._owner = self._get_human_owner(self._path) self._group = self._get_human_group(self._path) self.notify("name") self.notify("location") self.notify("type") self.notify("size") self.notify("created") self.notify("modified") self.notify("accessed") self.notify("permissions_owner") self.notify("permissions_group") self.notify("permissions_others") self.notify("owner") self.notify("group") self._update_size() def stop(self): self._inner_worker.stop() class PortfolioSendTrashWorker(GObject.GObject, CachedWorker): __gtype_name__ = "PortfolioSendTrashWorker" __gsignals__ = { "started": (GObject.SignalFlags.RUN_LAST, None, ()), "pre-update": (GObject.SignalFlags.RUN_LAST, None, (str,)), "updated": (GObject.SignalFlags.RUN_LAST, None, (str, object, int, int)), "finished": (GObject.SignalFlags.RUN_LAST, None, (int,)), "failed": (GObject.SignalFlags.RUN_LAST, None, (str,)), "stopped": (GObject.SignalFlags.RUN_LAST, None, ()), } def __init__(self, selection): super().__init__() CachedWorker.__init__(self) self._selection = selection self._is_flatpak = utils.is_flatpak() def start(self): self.emit("started") self._paths = [path for path, ref in self._selection] self._refs = dict(self._selection) self._total = len(self._paths) self._index = 0 self._timeout_handler_id = GLib.idle_add( self.step, priority=GLib.PRIORITY_HIGH_IDLE + 20 ) def step(self): if self._index >= self._total: self.emit("finished", self._total) return path = self._paths[self._index] try: self.emit("pre-update", path) default_trash.trash(path) except Exception as e: logger.debug(e) self.emit("failed", path) return self._index += 1 self.emit("updated", path, self._refs.get(path), self._index, self._total) self._timeout_handler_id = GLib.idle_add( self.step, priority=GLib.PRIORITY_HIGH_IDLE + 20 ) def stop(self): if self._timeout_handler_id is not None: GLib.Source.remove(self._timeout_handler_id) self._timeout_handler_id = None self.emit("stopped") class PortfolioRestoreTrashWorker(GObject.GObject, CachedWorker): __gtype_name__ = "PortfolioRestoreTrashWorker" __gsignals__ = { "started": (GObject.SignalFlags.RUN_LAST, None, ()), "pre-update": (GObject.SignalFlags.RUN_LAST, None, (str,)), "updated": (GObject.SignalFlags.RUN_LAST, None, (str, object, int, int)), "finished": (GObject.SignalFlags.RUN_LAST, None, (int,)), "failed": (GObject.SignalFlags.RUN_LAST, None, (str,)), "stopped": (GObject.SignalFlags.RUN_LAST, None, ()), } def __init__(self, selection): super().__init__() CachedWorker.__init__(self) self._selection = selection def start(self): self.emit("started") self._paths = [path for path, ref in self._selection] self._refs = dict(self._selection) self._total = len(self._paths) self._index = 0 self._timeout_handler_id = GLib.idle_add( self.step, priority=GLib.PRIORITY_HIGH_IDLE + 20 ) def step(self): if self._index >= self._total: self.emit("finished", self._total) return path = self._paths[self._index] try: self.emit("pre-update", path) default_trash.restore(path) except Exception as e: logger.debug(e) self.emit("failed", path) return self._index += 1 self.emit("updated", path, self._refs.get(path), self._index, self._total) self._timeout_handler_id = GLib.idle_add( self.step, priority=GLib.PRIORITY_HIGH_IDLE + 20 ) def stop(self): if self._timeout_handler_id is not None: GLib.Source.remove(self._timeout_handler_id) self._timeout_handler_id = None self.emit("stopped") class PortfolioDeleteTrashWorker(GObject.GObject): __gtype_name__ = "PortfolioDeleteTrashWorker" __gsignals__ = { "started": (GObject.SignalFlags.RUN_LAST, None, ()), "pre-update": (GObject.SignalFlags.RUN_LAST, None, (str,)), "updated": (GObject.SignalFlags.RUN_LAST, None, (str, object, int, int)), "finished": (GObject.SignalFlags.RUN_LAST, None, (int,)), "failed": (GObject.SignalFlags.RUN_LAST, None, (str,)), "stopped": (GObject.SignalFlags.RUN_LAST, None, ()), } def __init__(self, selection): super().__init__() self._selection = selection def start(self): self.emit("started") self._paths = [path for path, ref in self._selection] self._refs = dict(self._selection) self._total = len(self._paths) self._index = 0 self._timeout_handler_id = GLib.idle_add( self.step, priority=GLib.PRIORITY_HIGH_IDLE + 20 ) def step(self): if self._index >= self._total: self.emit("finished", self._total) return path = self._paths[self._index] try: self.emit("pre-update", path) default_trash.remove(path) except Exception as e: logger.debug(e) self.emit("failed", path) return self._index += 1 self.emit("updated", path, self._refs.get(path), self._index, self._total) self._timeout_handler_id = GLib.idle_add( self.step, priority=GLib.PRIORITY_HIGH_IDLE + 20 ) def stop(self): if self._timeout_handler_id is not None: GLib.Source.remove(self._timeout_handler_id) self._timeout_handler_id = None self.emit("stopped") class PortfolioLoadTrashWorker(GObject.GObject, CachedWorker): __gtype_name__ = "PortfolioLoadTrashWorker" __gsignals__ = { "started": (GObject.SignalFlags.RUN_LAST, None, (str,)), "updated": (GObject.SignalFlags.RUN_LAST, None, (str, object, int, int)), "finished": (GObject.SignalFlags.RUN_LAST, None, (str,)), "failed": (GObject.SignalFlags.RUN_LAST, None, (str,)), } def __init__(self, directory=None, hidden=False): super().__init__() CachedWorker.__init__(self) self._timeout_handler_id = None def start(self): self.emit("started", "") try: self._paths = default_trash.list() except Exception as e: logger.debug(e) self.emit("failed", "") return self._total = len(self._paths) self._index = 0 self._timeout_handler_id = GLib.idle_add( self.step, priority=GLib.PRIORITY_HIGH_IDLE + 20 ) def step(self): if self._index >= self._total: self.emit("finished", "") return path = self._paths[self._index] name = os.path.basename(path) icon = utils.get_file_icon(path) self._index += 1 self.emit("updated", "", [(name, path, icon)], self._index, self._total) self._timeout_handler_id = GLib.idle_add( self.step, priority=GLib.PRIORITY_HIGH_IDLE + 20 ) def stop(self): if self._timeout_handler_id is None: return GLib.Source.remove(self._timeout_handler_id) self._timeout_handler_id = None Portfolio-1.0.2/tests/000077500000000000000000000000001476103320100146265ustar00rootroot00000000000000Portfolio-1.0.2/tests/meson.build000066400000000000000000000021651476103320100167740ustar00rootroot00000000000000conf = configuration_data() conf.set('resource_dir', join_paths(meson.build_root(), 'src')) conf.set('source_dir', meson.source_root()) test_window_file = configure_file( input: 'window.py.in', output: 'window.py', configuration: conf, ) test_worker_file = configure_file( input: 'worker.py.in', output: 'worker.py', configuration: conf, ) test_service_file = configure_file( input: 'service.py.in', output: 'service.py', configuration: conf, ) src_dir = join_paths(meson.source_root(), 'src') tests_dir = join_paths(meson.source_root(), 'tests') pyflakes = find_program('pyflakes', required: false) if pyflakes.found() test('pyflakes', pyflakes, args: [src_dir, test_window_file, test_worker_file]) endif black = find_program('black', required: false) if black.found() test('black', black, args: ['--check', src_dir, test_window_file, test_worker_file]) endif tests = [ test_window_file, test_worker_file, ] if get_option('run_service_tests') tests += test_service_file endif pytest = find_program('pytest', required: false) if pytest.found() test('pytest', pytest, args: tests) endif Portfolio-1.0.2/tests/requirements.json000066400000000000000000000251041476103320100202460ustar00rootroot00000000000000{ "name": "requirements", "buildsystem": "simple", "build-commands": [], "modules": [ { "name": "python3-pyflakes", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"pyflakes\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", "sha256": "84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a" } ], "cleanup": [ "*" ] }, { "name": "python3-black", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"black\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/04/b0/46fb0d4e00372f4a86a6f8efa3cb193c9f64863615e39010b1477e010578/black-24.8.0.tar.gz", "sha256": "2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", "sha256": "4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", "sha256": "73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/6b/be/0f2f4a5e8adc114a02b63d92bf8edbfa24db6fc602fca83c885af2479e0e/editables-0.5-py3-none-any.whl", "sha256": "61e5ffa82629e0d8bfe09bc44a07db3c1ab8ed1ce78a6980732870f19b5e7d4c" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/0c/8b/90e80904fdc24ce33f6fc6f35ebd2232fe731a8528a22008458cf197bc4d/hatchling-1.25.0-py3-none-any.whl", "sha256": "b47948e45d4d973034584dd4cb39c14b6a70227cf287ab7ec0ad7983408a882c" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", "sha256": "5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", "sha256": "a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", "sha256": "44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/b6/7a/e0edec9c8905e851d52076bbc41890603e2ba97cf64966bc1498f2244fd2/trove_classifiers-2024.9.12-py3-none-any.whl", "sha256": "f88a27a892891c87c5f8bbdf110710ae9e0a4725ea8e0fb45f1bcadf088a491f" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/82/0f/6cbd9976160bc334add63bc2e7a58b1433a31b34b7cda6c5de6dd983d9a7/hatch_vcs-0.4.0-py3-none-any.whl", "sha256": "b8a2b6bee54cf6f9fc93762db73890017ae59c9081d1038a41f16235ceaf8b2c" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/a0/b9/1906bfeb30f2fc13bb39bf7ddb8749784c05faadbd18a21cf141ba37bff2/setuptools_scm-8.1.0-py3-none-any.whl", "sha256": "897a3226a6fd4a6eb2f068745e49733261a21f70b1bb28fce0339feb978d9af3" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", "sha256": "04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/5e/a0/9a7ec6559225e96aa2efb5ab7ef4bf17c42940122a21828c3dce5f62b595/hatch_fancy_pypi_readme-24.1.0-py3-none-any.whl", "sha256": "26ec5c7cfd9f604eff0ae6c927d7b197b220706dca203f0aad1928abc81f3a46" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/27/b8/f21073fde99492b33ca357876430822e4800cdf522011f18041351dfa74b/setuptools-75.1.0.tar.gz", "sha256": "d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/b7/a0/95e9e962c5fd9da11c1e28aa4c0d8210ab277b1ada951d2aee336b505813/wheel-0.44.0.tar.gz", "sha256": "a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/38/45/618e84e49a6c51e5dd15565ec2fcd82ab273434f236b8f108f065ded517a/flit_core-3.9.0-py3-none-any.whl", "sha256": "7aada352fb0c7f5538c4fafeddf314d3a6a92ee8e2b1de70482329e42de70301" } ], "cleanup": [ "*" ] }, { "name": "python3-pytest", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"pytest\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", "sha256": "b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", "sha256": "5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", "sha256": "44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", "sha256": "a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" } ], "cleanup": [ "*" ] }, { "name": "python3-pytest-timeout", "buildsystem": "simple", "build-commands": [ "pip3 install --verbose --exists-action=i --no-index --find-links=\"file://${PWD}\" --prefix=${FLATPAK_DEST} \"pytest-timeout\"" ], "sources": [ { "type": "file", "url": "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", "sha256": "b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", "sha256": "5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", "sha256": "44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", "sha256": "a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" }, { "type": "file", "url": "https://files.pythonhosted.org/packages/03/27/14af9ef8321f5edc7527e47def2a21d8118c6f329a9342cc61387a0c0599/pytest_timeout-2.3.1-py3-none-any.whl", "sha256": "68188cb703edfc6a18fad98dc25a3c61e9f24d644b0b70f33af545219fc7813e" } ], "cleanup": [ "*" ] } ] } Portfolio-1.0.2/tests/root/000077500000000000000000000000001476103320100156115ustar00rootroot00000000000000Portfolio-1.0.2/tests/root/home/000077500000000000000000000000001476103320100165415ustar00rootroot00000000000000Portfolio-1.0.2/tests/root/home/tchx84/000077500000000000000000000000001476103320100176635ustar00rootroot00000000000000Portfolio-1.0.2/tests/root/home/tchx84/.hidden000066400000000000000000000000001476103320100211050ustar00rootroot00000000000000Portfolio-1.0.2/tests/root/home/tchx84/file000066400000000000000000000000101476103320100205140ustar00rootroot00000000000000nonzero Portfolio-1.0.2/tests/root/home/tchx84/folder/000077500000000000000000000000001476103320100211365ustar00rootroot00000000000000Portfolio-1.0.2/tests/root/home/tchx84/folder/file000066400000000000000000000000001476103320100217660ustar00rootroot00000000000000Portfolio-1.0.2/tests/service.py.in000066400000000000000000000060111476103320100172430ustar00rootroot00000000000000import os import sys import pytest ROOT_DIR = "@source_dir@" sys.path.append(ROOT_DIR) RESOURCE_DIR = os.path.join("@resource_dir@") TEST_HOME_DIR = os.path.join(ROOT_DIR, "tests/root/home/tchx84") TEST_HOME_DIR_URL = f"file://{TEST_HOME_DIR}" TEST_HOME_FILE_URL = f"file://{os.path.join(TEST_HOME_DIR, 'file')}" resource = None service = None app = None class MockApp: def __init__(self): self.called = False def show_properties(self, path): self.called = True def open_path(self, path): self.called = True def update_gtk(): from gi.repository import GLib context = GLib.MainContext.default() while context.pending(): context.iteration(True) def call_service(method, url): from gi.repository import Gio, GLib bus = None finished = False def callback(_gio, _task, _data): nonlocal finished, bus bus = Gio.bus_get_finish(_task) finished = True Gio.bus_get(Gio.BusType.SESSION, None, callback, None) # wait until we get the bus while not finished: update_gtk() proxy = None finished = False def callback(_proxy, _task, _data): nonlocal finished, proxy proxy = _proxy finished = True proxy = Gio.DBusProxy.new( bus, Gio.DBusProxyFlags.NONE, None, os.environ.get("PORTFOLIO_SERVICE_NAME"), "/org/freedesktop/FileManager1", "org.freedesktop.FileManager1", None, callback, None, ) # wait until proxy is set while not finished: update_gtk() finished = False def callback(_proxy, _task, _data): nonlocal finished finished = True proxy.call( method, GLib.Variant("(ass)", ([url], "")), Gio.DBusCallFlags.NO_AUTO_START, 500, None, callback, None, ) # wait until the callback finished while not finished: update_gtk() def setup_module(): global resource, service, app app = MockApp() import gi gi.require_version("Gio", "2.0") gi.require_version("Gtk", "4.0") from gi.repository import Gio, GLib resource = Gio.Resource.load(os.path.join(RESOURCE_DIR, "portfolio.gresource")) resource._register() from gi.repository import Gtk Gtk.init() os.environ["PORTFOLIO_SERVICE_NAME"] = "dev.tch84.Portfolio.test" from src.service import PortfolioService service = PortfolioService(app) def teardown_module(module): global service update_gtk() service.shutdown() @pytest.mark.timeout(5) def test_show_folders(): app.called = False call_service("ShowFolders", TEST_HOME_DIR_URL) assert app.called is True @pytest.mark.timeout(5) def test_show_items(): app.called = False call_service("ShowItems", TEST_HOME_FILE_URL) assert app.called is True @pytest.mark.timeout(5) def test_show_item_properties(): app.called = False call_service("ShowItemProperties", TEST_HOME_FILE_URL) assert app.called is True Portfolio-1.0.2/tests/window.py.in000066400000000000000000000203511476103320100171150ustar00rootroot00000000000000import os import sys import shutil import pytest ROOT_DIR = "@source_dir@" sys.path.append(ROOT_DIR) RESOURCE_DIR = os.path.join("@resource_dir@") TEST_HOME_DIR = os.path.join(ROOT_DIR, "tests/root/home/tchx84") TEST_XDG_DATA_DIRS = os.path.join(ROOT_DIR, "tests/root/home/share") TEST_HOME_FILE = os.path.join(TEST_HOME_DIR, "file") TEST_HOME_FOLDER = os.path.join(TEST_HOME_DIR, "folder") TEST_HOME_SUB_FILE = os.path.join(TEST_HOME_DIR, "folder", "file") TEST_NEW_FILE = "file" TEST_NEW_FOLDER = "New Folder" TEST_NEW_FOLDER_RENAMED = "Renamed" resource = None window = None def update_gtk(): from gi.repository import GLib context = GLib.MainContext.default() while context.pending(): context.iteration(True) def setup_module(): global resource import gi gi.require_version("Gtk", "4.0") gi.require_version("Gio", "2.0") gi.require_version("Adw", "1") from gi.repository import Gio resource = Gio.Resource.load(os.path.join(RESOURCE_DIR, "portfolio.gresource")) resource._register() from gi.repository import Gtk Gtk.init() from gi.repository import Adw Adw.init() os.environ["PORTFOLIO_HOME_DIR"] = TEST_HOME_DIR os.environ["PORTFOLIO_XDG_DATA_DIRS"] = TEST_XDG_DATA_DIRS def teardown_module(): shutil.rmtree(os.path.join(TEST_HOME_DIR, TEST_NEW_FOLDER), ignore_errors=True) shutil.rmtree(os.path.join(TEST_HOME_FOLDER, TEST_NEW_FOLDER), ignore_errors=True) def test_create_window(): global window from src.window import PortfolioWindow window = PortfolioWindow() window.open() update_gtk() assert window is not None def test_load_all(): # should list only "folder" and "file" assert len(window.files.sorted) == 2 assert window.files.sorted[0][window.files.PATH_COLUMN] == TEST_HOME_FOLDER assert window.files.sorted[1][window.files.PATH_COLUMN] == TEST_HOME_FILE def test_default_selection(): # nothing should be selected by default _, treepaths = window.files.selection.get_selected_rows() assert len(treepaths) == 0 def test_select_all(): # "folder" and "file" should be selected window.files.switch_to_selection_mode() window.files.select_all() update_gtk() model, treepaths = window.files.selection.get_selected_rows() assert len(treepaths) == 2 assert model[treepaths[0]][window.files.PATH_COLUMN] == TEST_HOME_FOLDER assert model[treepaths[1]][window.files.PATH_COLUMN] == TEST_HOME_FILE def test_unselect_all(): # nothing should be selected window.files.switch_to_selection_mode() window.files.unselect_all() update_gtk() _, treepaths = window.files.selection.get_selected_rows() assert len(treepaths) == 0 def test_new_folder(): # "New Folder" should be created and selected window.files.unselect_all() update_gtk() window._on_new_folder(None) model, treepaths = window.files.selection.get_selected_rows() assert len(window.files.sorted) == 3 assert model[0][window.files.PATH_COLUMN] == TEST_HOME_FOLDER assert model[1][window.files.PATH_COLUMN] == os.path.join( TEST_HOME_DIR, TEST_NEW_FOLDER ) assert model[2][window.files.PATH_COLUMN] == TEST_HOME_FILE assert len(treepaths) == 1 assert model[treepaths[0]][window.files.PATH_COLUMN] == os.path.join( TEST_HOME_DIR, TEST_NEW_FOLDER ) def test_open_folder(): # "New Folder" should be opened and must be empty window.files.unselect_all() window.files._on_row_activated(None, 1, None, None) update_gtk() assert len(window.files.sorted) == 0 def test_navigation_previous(): # go back to "folder" window._on_go_previous(None) update_gtk() assert len(window.files.sorted) == 3 assert window.files.sorted[0][window.files.PATH_COLUMN] == TEST_HOME_FOLDER assert window.files.sorted[1][window.files.PATH_COLUMN] == os.path.join( TEST_HOME_DIR, TEST_NEW_FOLDER ) assert window.files.sorted[2][window.files.PATH_COLUMN] == TEST_HOME_FILE def test_copy(): # "file" should be selected and copied window.files.switch_to_selection_mode() window.files._select_row(window.files.sorted[2].iter) window._on_copy_clicked(None) assert len(window._to_copy) == 1 assert window._to_copy[0][0] == TEST_HOME_FILE def test_navigation_next(): # go back to the "New Folder" and must still be empty window._on_go_next(None) update_gtk() assert len(window.files.sorted) == 0 def test_copy_paste(): # paste "file" and it should be listed window._on_paste_clicked(None) window._worker.join() update_gtk() assert len(window.files.sorted) == 1 assert window.files.sorted[0][window.files.PATH_COLUMN] == os.path.join( TEST_HOME_DIR, TEST_NEW_FOLDER, TEST_NEW_FILE ) @pytest.mark.timeout(5) def test_delete_all(): # select all and delete, nothing should remain window.files.switch_to_selection_mode() window.files.select_all() window._on_delete_clicked(None) update_gtk() window._popup.confirm_button.emit("clicked") finished = False def _callback(worker, total): nonlocal finished finished = True window._worker.connect("finished", _callback) while not finished: update_gtk() update_gtk() assert len(window.files.sorted) == 0 def test_original_file_untouched(): # go to "folder" and the original "file" is intact window._on_go_previous(None) update_gtk() assert len(window.files.sorted) == 3 assert window.files.sorted[0][window.files.PATH_COLUMN] == TEST_HOME_FOLDER assert window.files.sorted[1][window.files.PATH_COLUMN] == os.path.join( TEST_HOME_DIR, TEST_NEW_FOLDER ) assert window.files.sorted[2][window.files.PATH_COLUMN] == TEST_HOME_FILE def test_cut(): # "New Folder" is selected and cut window.files.switch_to_selection_mode() window.files._select_row(window.files.sorted[1].iter) window._on_cut_clicked(None) assert len(window._to_cut) == 1 assert window._to_cut[0][0] == os.path.join(TEST_HOME_DIR, TEST_NEW_FOLDER) def test_cut_paste(): # go to "folder" and paste "New Folder" window.files.unselect_all() window.files._on_row_activated(None, 0, None, None) update_gtk() window._on_paste_clicked(None) window._worker.join() update_gtk() assert len(window.files.sorted) == 2 assert window.files.sorted[0][window.files.PATH_COLUMN] == os.path.join( TEST_HOME_FOLDER, TEST_NEW_FOLDER ) assert window.files.sorted[1][window.files.PATH_COLUMN] == TEST_HOME_SUB_FILE def test_rename(): window.files.switch_to_selection_mode() window.files._select_row(window.files.sorted[0].iter) window.rename.emit("clicked") update_gtk() window.files.name_cell.emit("edited", 0, TEST_NEW_FOLDER_RENAMED) update_gtk() model, treepaths = window.files.selection.get_selected_rows() assert model[treepaths[0]][window.files.PATH_COLUMN] == os.path.join( TEST_HOME_FOLDER, TEST_NEW_FOLDER_RENAMED ) @pytest.mark.timeout(5) def test_delete_one(): # select "Renamed" folder and delete it window.files.switch_to_selection_mode() window.files._select_row(window.files.sorted[0].iter) window._on_delete_clicked(None) update_gtk() window._popup.confirm_button.emit("clicked") finished = False def _callback(worker, total): nonlocal finished finished = True window._worker.connect("finished", _callback) while not finished: update_gtk() update_gtk() assert len(window.files.sorted) == 1 assert window.files.sorted[0][window.files.PATH_COLUMN] == TEST_HOME_SUB_FILE def test_new_folder_gone(): # "New Folder" must be gone window._on_go_previous(None) update_gtk() assert len(window.files.sorted) == 2 assert window.files.sorted[0][window.files.PATH_COLUMN] == TEST_HOME_FOLDER assert window.files.sorted[1][window.files.PATH_COLUMN] == TEST_HOME_FILE def test_open_file(): window.open(TEST_HOME_SUB_FILE) update_gtk() assert len(window.files.sorted) == 1 assert window.files.sorted[0][window.files.PATH_COLUMN] == TEST_HOME_SUB_FILE model, treepaths = window.files.selection.get_selected_rows() assert model[treepaths[0]][window.files.PATH_COLUMN] == TEST_HOME_SUB_FILE Portfolio-1.0.2/tests/worker.py.in000066400000000000000000000373431476103320100171300ustar00rootroot00000000000000# worker.py # # Copyright 2021 Martin Abente Lahaye # # 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 . import os import sys import shutil import pathlib import pytest ROOT_DIR = "@source_dir@" sys.path.append(ROOT_DIR) TEST_HOME_DIR = os.path.join(ROOT_DIR, "tests/root/home/tchx84") TEST_COPY_DIR = os.path.join(TEST_HOME_DIR, "copy") TEST_CUT_DIR = os.path.join(TEST_HOME_DIR, "cut") TEST_TRASH_TMP_DIR = os.path.join(ROOT_DIR, "tests/root/home/Trashable") TEST_XDG_DATA_DIRS = os.path.join(ROOT_DIR, "tests/root/home/share") def update_gtk(): from gi.repository import GLib context = GLib.MainContext.default() while context.pending(): context.iteration(True) def setup_module(module): import gi gi.require_version("Gtk", "4.0") from gi.repository import Gtk Gtk.init() # XXX eventually port everything to pathlib? pathlib.Path(os.path.join(TEST_HOME_DIR, "folder")).mkdir(exist_ok=True) pathlib.Path(os.path.join(TEST_HOME_DIR, "folder", "file")).touch() pathlib.Path(os.path.join(TEST_HOME_DIR, "file")).touch() pathlib.Path(os.path.join(TEST_HOME_DIR, ".hidden")).touch() # for Trash test folder pathlib.Path(TEST_XDG_DATA_DIRS).mkdir(exist_ok=True) os.environ["PORTFOLIO_XDG_DATA_DIRS"] = TEST_XDG_DATA_DIRS # for Trash test files pathlib.Path(TEST_TRASH_TMP_DIR).mkdir(exist_ok=True) pathlib.Path(os.path.join(TEST_TRASH_TMP_DIR, "file")).touch() pathlib.Path(os.path.join(TEST_TRASH_TMP_DIR, "folder")).mkdir(exist_ok=True) pathlib.Path(os.path.join(TEST_TRASH_TMP_DIR, "folder", "file")).touch() def teardown_module(module): pathlib.Path(os.path.join(TEST_HOME_DIR, "folder", "file")).touch() shutil.rmtree(TEST_COPY_DIR, ignore_errors=True) shutil.rmtree(TEST_CUT_DIR, ignore_errors=True) shutil.rmtree(TEST_TRASH_TMP_DIR, ignore_errors=True) shutil.rmtree(TEST_XDG_DATA_DIRS, ignore_errors=True) def test_init_setup(): assert len(os.listdir(TEST_HOME_DIR)) == 3 assert os.path.exists(os.path.join(TEST_HOME_DIR, "folder", "file")) assert os.path.exists(os.path.join(TEST_HOME_DIR, "folder")) assert os.path.exists(os.path.join(TEST_HOME_DIR, "file")) assert os.path.exists(os.path.join(TEST_HOME_DIR, ".hidden")) def test_load_worker_default(): from src.worker import PortfolioLoadWorker paths = [] def _callback(worker, directory, found, index, total): nonlocal paths paths += [path for name, path, icon in found] worker = PortfolioLoadWorker(TEST_HOME_DIR) worker.connect("updated", _callback) worker.start() update_gtk() assert len(paths) == 2 assert set(paths) == set( [ os.path.join(TEST_HOME_DIR, "folder"), os.path.join(TEST_HOME_DIR, "file"), ] ) def test_load_worker_hidden(): from src.worker import PortfolioLoadWorker paths = [] def _callback(worker, directory, found, index, total): nonlocal paths paths += [path for name, path, icon in found] worker = PortfolioLoadWorker(TEST_HOME_DIR, True) worker.connect("updated", _callback) worker.start() update_gtk() assert len(paths) == 3 assert set(paths) == set( [ os.path.join(TEST_HOME_DIR, "folder"), os.path.join(TEST_HOME_DIR, "file"), os.path.join(TEST_HOME_DIR, ".hidden"), ] ) def test_copy_worker_default(): from src.worker import PortfolioCopyWorker source = TEST_HOME_DIR target = TEST_COPY_DIR assert not os.path.exists(target) os.makedirs(target) selection = [ (os.path.join(source, "folder"), None), (os.path.join(source, "file"), None), ] worker = PortfolioCopyWorker(selection, target) worker.start() worker.join() assert len(os.listdir(source)) == 4 assert len(os.listdir(target)) == 2 assert os.path.exists(os.path.join(target, "folder", "file")) assert os.path.exists(os.path.join(target, "folder")) assert os.path.exists(os.path.join(target, "file")) def test_copy_worker_nonidempotent(): from src.worker import PortfolioCopyWorker source = TEST_COPY_DIR target = TEST_COPY_DIR selection = [ (os.path.join(source, "folder"), None), (os.path.join(source, "file"), None), ] worker = PortfolioCopyWorker(selection, target) worker.start() worker.join() assert len(os.listdir(source)) == 4 assert len(os.listdir(target)) == 4 assert os.path.exists(os.path.join(target, "folder", "file")) assert os.path.exists(os.path.join(target, "folder")) assert os.path.exists(os.path.join(target, "file")) assert os.path.exists(os.path.join(target, "folder(1)", "file")) assert os.path.exists(os.path.join(target, "folder(1)")) assert os.path.exists(os.path.join(target, "file(1)")) def test_copy_worker_overwrite(): from src.worker import PortfolioCopyWorker source = TEST_COPY_DIR target = TEST_HOME_DIR # to differenciate from the source content pathlib.Path(os.path.join(target, "folder", "different")).touch() assert os.path.exists(os.path.join(target, "folder", "different")) selection = [ (os.path.join(source, "folder"), None), (os.path.join(source, "file"), None), ] worker = PortfolioCopyWorker(selection, target) worker.start() worker.join() assert len(os.listdir(source)) == 4 assert len(os.listdir(target)) == 4 assert os.path.exists(os.path.join(source, "folder", "file")) assert os.path.exists(os.path.join(source, "folder")) assert os.path.exists(os.path.join(source, "file")) assert not os.path.exists(os.path.join(target, "folder", "different")) assert os.path.exists(os.path.join(target, "folder", "file")) assert os.path.exists(os.path.join(target, "folder")) assert os.path.exists(os.path.join(target, "file")) @pytest.mark.timeout(5) def test_delete_worker_default(): from src.worker import PortfolioDeleteWorker source = TEST_COPY_DIR selection = [ (os.path.join(source, "folder(1)"), None), (os.path.join(source, "file(1)"), None), ] finished = False def _callback(worker, total): nonlocal finished finished = True worker = PortfolioDeleteWorker(selection) worker.connect("finished", _callback) worker.start() while not finished: update_gtk() assert len(os.listdir(source)) == 2 assert os.path.exists(os.path.join(source, "folder", "file")) assert os.path.exists(os.path.join(source, "folder")) assert os.path.exists(os.path.join(source, "file")) def test_cut_worker_default(): from src.worker import PortfolioCutWorker source = TEST_COPY_DIR target = TEST_CUT_DIR assert not os.path.exists(target) os.makedirs(target) selection = [ (os.path.join(source, "folder"), None), (os.path.join(source, "file"), None), ] worker = PortfolioCutWorker(selection, target) worker.start() worker.join() assert len(os.listdir(source)) == 0 assert len(os.listdir(target)) == 2 assert os.path.exists(os.path.join(target, "folder", "file")) assert os.path.exists(os.path.join(target, "folder")) assert os.path.exists(os.path.join(target, "file")) def test_cut_worker_idempotent(): from src.worker import PortfolioCutWorker source = TEST_CUT_DIR target = TEST_CUT_DIR selection = [ (os.path.join(source, "folder"), None), (os.path.join(source, "file"), None), ] worker = PortfolioCutWorker(selection, target) worker.start() worker.join() assert len(os.listdir(source)) == 2 assert len(os.listdir(target)) == 2 assert os.path.exists(os.path.join(target, "folder", "file")) assert os.path.exists(os.path.join(target, "folder")) assert os.path.exists(os.path.join(target, "file")) def test_cut_worker_overwrite(): from src.worker import PortfolioCutWorker source = TEST_CUT_DIR target = TEST_HOME_DIR # to differenciate from the source content pathlib.Path(os.path.join(target, "folder", "different")).touch() assert os.path.exists(os.path.join(target, "folder", "different")) selection = [ (os.path.join(source, "folder"), None), (os.path.join(source, "file"), None), ] worker = PortfolioCutWorker(selection, target) worker.start() worker.join() assert len(os.listdir(source)) == 0 assert len(os.listdir(target)) == 5 assert os.path.exists(os.path.join(target, "folder")) assert not os.path.exists(os.path.join(target, "folder", "different")) assert os.path.exists(os.path.join(target, "file")) assert os.path.exists(os.path.join(target, ".hidden")) assert os.path.exists(os.path.join(target, "copy")) assert os.path.exists(os.path.join(target, "cut")) @pytest.mark.timeout(5) @pytest.mark.skip(reason="no editor available") def test_open_worker_default(): from src.worker import PortfolioOpenWorker path_to_file = os.path.join(TEST_HOME_DIR, "file") finished = False def _callback(worker): nonlocal finished finished = True worker = PortfolioOpenWorker(path_to_file) worker.connect("finished", _callback) worker.start() while not finished: update_gtk() assert finished is True @pytest.mark.timeout(5) def test_open_worker_error(): from src.worker import PortfolioOpenWorker path_to_nowhere = "/path/to/no/where" path = None finished = False def _callback(worker, _path): nonlocal path, finished path = _path finished = True worker = PortfolioOpenWorker(path_to_nowhere) worker.connect("failed", _callback) worker.start() while not finished: update_gtk() assert path == path_to_nowhere @pytest.mark.timeout(5) def test_copy_worker_stopped(): from src.worker import PortfolioCopyWorker source = TEST_HOME_DIR target = TEST_COPY_DIR assert len(os.listdir(source)) == 5 assert len(os.listdir(target)) == 0 selection = [ (os.path.join(source, "folder"), None), (os.path.join(source, "file"), None), ] stopped = False def _callback(worker): nonlocal stopped stopped = True worker = PortfolioCopyWorker(selection, target) worker.connect("stopped", _callback) worker.stop() worker.start() worker.join() while not stopped: update_gtk() assert stopped is True assert len(os.listdir(source)) == 5 assert len(os.listdir(target)) == 0 @pytest.mark.timeout(5) def test_cut_worker_stopped(): from src.worker import PortfolioCutWorker source = TEST_HOME_DIR target = TEST_COPY_DIR assert len(os.listdir(source)) == 5 assert len(os.listdir(target)) == 0 selection = [ (os.path.join(source, "folder"), None), (os.path.join(source, "file"), None), ] stopped = False def _callback(worker): nonlocal stopped stopped = True worker = PortfolioCutWorker(selection, target) worker.connect("stopped", _callback) worker.stop() worker.start() worker.join() while not stopped: update_gtk() assert stopped is True assert len(os.listdir(source)) == 5 assert len(os.listdir(target)) == 0 @pytest.mark.timeout(5) def test_delete_worker_stopped(): from src.worker import PortfolioDeleteWorker source = TEST_HOME_DIR assert len(os.listdir(source)) == 5 selection = [ (os.path.join(source, "copy"), None), (os.path.join(source, "cut"), None), ] stopped = False def _callback(worker): nonlocal stopped stopped = True worker = PortfolioDeleteWorker(selection) worker.connect("stopped", _callback) worker.start() worker.stop() while not stopped: update_gtk() assert stopped is True assert len(os.listdir(source)) == 5 def test_properties_worker_default(): from src.worker import PortfolioPropertiesWorker worker = PortfolioPropertiesWorker() worker.props.path = os.path.join(TEST_HOME_DIR, "file") assert worker.props.name == "file" assert worker.props.size == "8.0 B" @pytest.mark.timeout(5) def test_send_trash_worker_default(): from src.worker import PortfolioSendTrashWorker assert len(os.listdir(TEST_TRASH_TMP_DIR)) == 2 selection = [ (os.path.join(TEST_TRASH_TMP_DIR, "file"), None), (os.path.join(TEST_TRASH_TMP_DIR, "folder"), None), ] finished = False def _callback(worker, total): nonlocal finished finished = True worker = PortfolioSendTrashWorker(selection) worker.connect("finished", _callback) worker.start() while not finished: update_gtk() assert len(os.listdir(TEST_TRASH_TMP_DIR)) == 0 assert os.path.exists(os.path.join(TEST_XDG_DATA_DIRS, "Trash", "files", "file")) assert os.path.exists(os.path.join(TEST_XDG_DATA_DIRS, "Trash", "files", "folder")) @pytest.mark.timeout(5) def test_load_trash_worker_default(): from src.worker import PortfolioLoadTrashWorker paths = [] def _update_callback(worker, name, tuples, index, total): nonlocal paths paths += [path for name, path, icon in tuples] finished = False def _finished_callback(worker, path): nonlocal finished finished = True worker = PortfolioLoadTrashWorker() worker.connect("updated", _update_callback) worker.connect("finished", _finished_callback) worker.start() while not finished: update_gtk() assert len(paths) == 2 assert set(paths) == set( [ os.path.join(TEST_XDG_DATA_DIRS, "Trash", "files", "file"), os.path.join(TEST_XDG_DATA_DIRS, "Trash", "files", "folder"), ] ) @pytest.mark.timeout(5) def test_restore_trash_worker_default(): from src.worker import PortfolioRestoreTrashWorker assert len(os.listdir(TEST_TRASH_TMP_DIR)) == 0 selection = [ (os.path.join(TEST_XDG_DATA_DIRS, "Trash", "files", "file"), None), (os.path.join(TEST_XDG_DATA_DIRS, "Trash", "files", "folder"), None), ] finished = False def _callback(worker, total): nonlocal finished finished = True worker = PortfolioRestoreTrashWorker(selection) worker.connect("finished", _callback) worker.start() while not finished: update_gtk() assert len(os.listdir(TEST_TRASH_TMP_DIR)) == 2 assert os.path.exists(os.path.join(TEST_TRASH_TMP_DIR, "file")) assert os.path.exists(os.path.join(TEST_TRASH_TMP_DIR, "folder")) @pytest.mark.timeout(5) def test_delete_trash_worker_default(): from src.worker import PortfolioDeleteTrashWorker test_send_trash_worker_default() selection = [ (os.path.join(TEST_XDG_DATA_DIRS, "Trash", "files", "file"), None), (os.path.join(TEST_XDG_DATA_DIRS, "Trash", "files", "folder"), None), ] finished = False def _callback(worker, total): nonlocal finished finished = True worker = PortfolioDeleteTrashWorker(selection) worker.connect("finished", _callback) worker.start() while not finished: update_gtk() assert len(os.listdir(TEST_TRASH_TMP_DIR)) == 0 assert len(os.listdir(os.path.join(TEST_XDG_DATA_DIRS, "Trash", "files"))) == 0