pax_global_header00006660000000000000000000000064117547171770014533gustar00rootroot0000000000000052 comment=8bf699819c9d30e2d34e14e76917f94daea4c67f etk.docking-0.2/000077500000000000000000000000001175471717700135745ustar00rootroot00000000000000etk.docking-0.2/.gitignore000066400000000000000000000001541175471717700155640ustar00rootroot00000000000000.coverage .project .pydevproject .settings *.pyc *.pyo build dist doc/reference/build/* lib/*.egg-info *.savetk.docking-0.2/AUTHORS000066400000000000000000000004401175471717700146420ustar00rootroot00000000000000The primary authors are (and/or have been): - Dieter Verfaillie - Arjan Molenaar People who have submitted patches, reported bugs, added translations, helped answer newbie questions, and generally made etk.docking that much better: etk.docking-0.2/COPYING000066400000000000000000001045131175471717700146330ustar00rootroot00000000000000 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 . etk.docking-0.2/COPYING.LESSER000066400000000000000000000167251175471717700156360ustar00rootroot00000000000000 GNU LESSER 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. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. etk.docking-0.2/MANIFEST.IN000066400000000000000000000001661175471717700152350ustar00rootroot00000000000000include AUTHORS COPYING COPYING.LESSER README TODO include ez_setup.py recursive-include lib/etk/docking/icons *.png etk.docking-0.2/README000066400000000000000000000000011175471717700144430ustar00rootroot00000000000000 etk.docking-0.2/TODO000066400000000000000000000033511175471717700142660ustar00rootroot00000000000000Release 0.1: "must have" ======================== v take a deep breath and a long look at the GTK+ api, follow it as much as possible, ie change DockPaned.insert_item(item, ...) to DockPaned.insert_item(child, ...), etc v fix DockPaned once and for all v fix the DockGroup tab rendering bug > implies reviewing the _tabs/_visible_tabs system and it's interaction with tab ordering... > right moment to implement "tab order" property (alphabetical, opening order, last used, more?) v implement the group-id system, can be used to configure: - auto remove empty dockgroups - can float - expand (so you can have a group-id=documents with expand=True and a group-id=tools with expand=False). This should control the expand child property of DockPaned - ??? v implement full signal set on DockGroup, DockPaned, ???: v item-added v item-removed x item-reordered --> maybe in DockLayout v item-selected > reemit by DockLayout so the end user only needs to concern himself with DockLayout and DockItem widgets (beside designing an initial layout) v emit 'item-closed'/'item-removed' when closing a floating window Release 0.2: "no rush" ====================== - use native gtk themeing to render all widgets, make the eclipse based "compact" rendering optional. - use PlaceHolderWindow to consistently hightlight _all_ drop zones - minimize DockGroup to toolbar - autoshow/hide minimized DockGroups Release x: "when we have time" ============================== - the gdk_draw_* functions have been deprecated for GTK+3, migrate to cairo - fix DnD on win32 for gtk+ > 2.16.6: depends on gdk-win32 csw/grabs work :( - keyboard navigation - translations - handle RTL - accessibility etk.docking-0.2/doc/000077500000000000000000000000001175471717700143415ustar00rootroot00000000000000etk.docking-0.2/doc/examples/000077500000000000000000000000001175471717700161575ustar00rootroot00000000000000etk.docking-0.2/doc/examples/demo.py000066400000000000000000000210631175471717700174570ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . from __future__ import absolute_import import logging import random import pygtk pygtk.require('2.0') import gobject import gtk import gtk.gdk as gdk import pango try: import etk.docking except ImportError: # The lib directory is most likely not on PYTHONPATH, so add it here. import os, sys sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'lib'))) del os, sys finally: from etk.docking import DockLayout, DockFrame, DockPaned, \ DockGroup, DockItem, dockstore, settings class MainWindow(gtk.Window): def __init__(self, docklayout=None, dockframe=None): gtk.Window.__init__(self) self.set_default_size(500, 150) self.set_title('etk.docking demo') self.set_border_width(4) self.file_counter = 1 self.subwindows = [] vbox = gtk.VBox() vbox.set_spacing(4) self.add(vbox) ######################################################################## # Docking ######################################################################## if docklayout and dockframe: self.dockframe = dockframe self.docklayout = docklayout else: self.dockframe = DockFrame() self.dockframe.set_border_width(8) g = DockGroup() g.set_name('main') self.dockframe.add(g) self.docklayout = DockLayout() self.docklayout.add(self.dockframe) settings['main'].auto_remove = False settings['main'].can_float = True settings['main'].inherit_settings = False settings['main'].expand = False # To change default group behaviour: #self.docklayout.settings[None].inherit_settings = False vbox.pack_start(self.dockframe) def on_item_closed(layout, group, item): item.destroy() print 'closed item:', item.title self.docklayout.connect('item-closed', on_item_closed) def on_item_selected(layout, group, item): print 'Selected item:', item.title self.docklayout.connect('item-selected', on_item_selected) ######################################################################## # Testing Tools ######################################################################## adddibutton = gtk.Button('Create docked items') adddibutton.child.set_ellipsize(pango.ELLIPSIZE_MIDDLE) adddibutton.connect('clicked', self._on_add_di_button_clicked) vbox.pack_start(adddibutton, False, False) orientationbutton = gtk.Button('Switch Orientation') orientationbutton.child.set_ellipsize(pango.ELLIPSIZE_MIDDLE) orientationbutton.connect('clicked', self._on_orientation_button_clicked) vbox.pack_start(orientationbutton, False, False) hbox = gtk.HBox() savebutton = gtk.Button('Save layout') savebutton.child.set_ellipsize(pango.ELLIPSIZE_MIDDLE) savebutton.connect('clicked', self._on_save_button_clicked) hbox.pack_start(savebutton, True, True) loadbutton = gtk.Button('Load layout') loadbutton.child.set_ellipsize(pango.ELLIPSIZE_MIDDLE) loadbutton.connect('clicked', self._on_load_button_clicked) hbox.pack_start(loadbutton, True, True) vbox.pack_start(hbox, False, False) self.show_all() #def on_has_toplevel_focus(window, pspec): # print 'Has toplevel focus', window, pspec # print 'Focus widget is', window.get_focus() #self.connect('notify::has-toplevel-focus', on_has_toplevel_focus) def _on_add_di_button_clicked(self, button): def add_dockitems(child): if isinstance(child, DockGroup): self._add_dockitems(child) elif isinstance(child, DockPaned): for child in child: add_dockitems(child) for child in self.dockframe: add_dockitems(child) def _on_orientation_button_clicked(self, button): def switch_orientation(paned): if isinstance(paned, DockPaned): if paned.get_orientation() == gtk.ORIENTATION_HORIZONTAL: paned.set_orientation(gtk.ORIENTATION_VERTICAL) else: paned.set_orientation(gtk.ORIENTATION_HORIZONTAL) for child in paned.get_children(): switch_orientation(child) paned = self.dockframe.get_children()[0] switch_orientation(paned) def _on_save_button_clicked(self, button): file = 'demo.sav' s = dockstore.serialize(self.docklayout) with open(file, 'w') as f: f.write(s) def _on_load_button_clicked(self, button): file = 'demo.sav' with open(file) as f: s = f.read() newlayout = dockstore.deserialize(s, self._create_content) main_frames = list(dockstore.get_main_frames(newlayout)) assert len(main_frames) == 1, main_frames subwindow = MainWindow(newlayout, main_frames[0]) self.subwindows.append(subwindow) dockstore.finish(newlayout, main_frames[0]) for f in newlayout.frames: f.get_toplevel().show_all() def _create_content(self, text=None): # Create a TextView and set some example text scrolledwindow = gtk.ScrolledWindow() scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) textview = gtk.TextView() textview.get_buffer().set_text(text) scrolledwindow.add(textview) return scrolledwindow def _add_dockitems(self, dockgroup): examples = [(gtk.STOCK_EXECUTE, 'calculator', '#!/usr/bin/env python\n\nprint \'Hello!\''), (gtk.STOCK_OPEN, 'Hi!', 'Hello!'), (gtk.STOCK_FILE, 'ABC', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'), (gtk.STOCK_FIND, 'abc', 'abcdefghijklmnopqrstuvwxyz'), (gtk.STOCK_HARDDISK, 'browser', '0123456789'), (gtk.STOCK_HOME, 'today', '9876543210'), gtk.Notebook] for i in [1]: #range(random.randrange(1, 10, 1)): example = random.choice(examples) if example is gtk.Notebook: child = gtk.Notebook() child.append_page(gtk.Button('Click me'), gtk.Label('New %s' % self.file_counter)) stock_id = '' tooltip_text = 'notebook' else: stock_id, tooltip_text, text = example child = self._create_content(text) child.set_name(stock_id) # Create a DockItem and add our TextView di = DockItem(title='New %s' % self.file_counter, title_tooltip_text=tooltip_text, stock_id=stock_id) def on_close(item): print 'close:', item di.connect('close', on_close) di.add(child) di.show_all() # Add out DockItem to the DockGroup dockgroup.add(di) # Increment file counter self.file_counter += 1 def quit(widget, event, mainloop): mainloop.quit() def main(): # Initialize logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s\t%(levelname)s\t%(name)s\t%(funcName)s\t%(message)s') # Uncomment to enable log filtering #for handler in logging.getLogger('').handlers: # handler.addFilter(logging.Filter('EtkDockPaned')) # Initialize mainloop gobject.threads_init() mainloop = gobject.MainLoop() # Initialize mainwindow mainwindow = MainWindow() mainwindow.connect('delete-event', quit, mainloop) mainwindow.show() # Run mainloop mainloop.run() if __name__ == '__main__': main() etk.docking-0.2/doc/examples/dockgroupdemo.py000066400000000000000000000102701175471717700213730ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . from __future__ import absolute_import import logging import random import gobject import gtk import gtk.gdk as gdk import pango try: from etk.docking import DockLayout, DockPaned, DockGroup, DockItem except ImportError: # The lib directory is most likely not on PYTHONPATH, so add it here. import os, sys sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'lib'))) from etk.docking import DockLayout, DockPaned, DockGroup, DockItem del os, sys class MainWindow(gtk.Window): def __init__(self): gtk.Window.__init__(self) self.set_default_size(500, 150) self.set_title('DockGroup Demo') self.set_border_width(4) self.file_counter = 1 vbox = gtk.VBox() vbox.set_spacing(4) self.add(vbox) ######################################################################## # Docking ######################################################################## self.dg = DockGroup() vbox.pack_start(self.dg) ######################################################################## # Testing Tools ######################################################################## adddibutton = gtk.Button('Create docked items') adddibutton.child.set_ellipsize(pango.ELLIPSIZE_MIDDLE) adddibutton.connect('clicked', self._on_add_di_button_clicked) vbox.pack_start(adddibutton, False, False) self.show_all() def _on_add_di_button_clicked(self, button): examples = [('calc', 'calculator', '#!/usr/bin/env python\n\nprint \'Hello!\''), ('file-manager', 'Hi!', 'Hello!'), ('fonts', 'ABC', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'), ('style', 'abc', 'abcdefghijklmnopqrstuvwxyz'), ('web-browser', 'browser', '0123456789'), ('date', 'today', '9876543210')] for i in range(random.randrange(1, 10, 1)): icon_name, tooltip_text, text = random.choice(examples) # Create a TextView and set some example text scrolledwindow = gtk.ScrolledWindow() scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) textview = gtk.TextView() textview.get_buffer().set_text(text) scrolledwindow.add(textview) # Create a DockItem and add our TextView di = DockItem(icon_name=icon_name, title='New %s' % self.file_counter, title_tooltip_text=tooltip_text) di.add(scrolledwindow) di.show_all() # Add out DockItem to the DockGroup self.dg.add(di) # Increment file counter self.file_counter += 1 def quit(widget, event, mainloop): mainloop.quit() def main(): # Initialize logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s\t%(levelname)s\t%(name)s\t%(funcName)s\t%(message)s') # Uncomment to enable log filtering #for handler in logging.getLogger('').handlers: # handler.addFilter(logging.Filter('EtkDockGroup')) # Initialize mainloop gobject.threads_init() mainloop = gobject.MainLoop() # Initialize mainwindow mainwindow = MainWindow() mainwindow.connect('delete-event', quit, mainloop) mainwindow.show() # Run mainloop mainloop.run() if __name__ == '__main__': main() etk.docking-0.2/doc/features/000077500000000000000000000000001175471717700161575ustar00rootroot00000000000000etk.docking-0.2/doc/features/dnd.feature000066400000000000000000000040641175471717700203050ustar00rootroot00000000000000Feature: Drag and drop tabs Background: Given a window with 2 dockgroups And one containing 3 items And another containing 2 items And define dockgroup 1 as "some-group" And define dockgroup 2 as "to-group" And define item 1 from dockgroup 1 as "drag-me" And define item 1 from dockgroup 2 as "sometab" And start a main loop Scenario: Drag an item over a new group When I drag item "drag-me" And I drop it on the content section in group "to-group" Then item "drag-me" is part of "to-group" And it has the focus Scenario: Drag an item over the tabs of a new group. When I drag item "drag-me" And I drop it on tab "sometab" in group "to-group" Then item "drag-me" is part of "to-group" And it has the focus #And it has been placed in just before "sometab" Scenario: Drag an item and drop it between two existing groups. When I drag item "drag-me" And I drop it between groups "some-group" and "to-group" Then a new group should have been created And it should contain the item Scenario: Drag an item to the end of a group of DockGroups. When I drag item "drag-me" And I drop it before the first group Then a new group should have been created And it should contain the item Scenario: Drag all items. When I drag all items in group "some-group" And I drop it on the content section in group "to-group" Then the group "some-group" has been removed Scenario: If an item is dragged ourside the scope of the dock frame. When I drag item "drag-me" And I drop it outside of the frame Then a floating window is created And it contains a new group with the item #Scenario: Drag an item at the begin/end of the intersection between groups The paned widget should get a new parent with opposite orientation, containing the items in a new group. #Scenario: Remove last item from DockGroup and containing DockPaned is also empty If there is a parent paned that contains only one item after the removal, it should also be removed (recursively) etk.docking-0.2/doc/features/steps.py000066400000000000000000000173121175471717700176730ustar00rootroot00000000000000 import pygtk pygtk.require('2.0') from freshen import Before, After, AfterStep, Given, When, Then, scc as world import gtk from etk.docking import DockPaned, DockGroup, DockLayout, DockFrame, DockItem from etk.docking.dnd import DRAG_TARGET_ITEM_LIST, DockDragContext from etk.docking.docklayout import drag_motion, drag_end, drag_failed class StubContext(object): def __init__(self, source_widget, items): self.targets = [ DRAG_TARGET_ITEM_LIST[0] ] self.source_widget = source_widget # Set up dragcontext (nornally done in motion_notify event) if items: self.source_widget.dragcontext = dragcontext = DockDragContext() dragcontext.dragged_object = items def get_source_widget(self): return self.source_widget def finish(self, success, delete, timestamp): self.finished = (success, delete) docklayout = property(lambda s: world.layout) @After def tear_down(_): world.window.destroy() @Given('a window with (\d+) dockgroups?') def default_window(n_groups): world.window = gtk.Window(gtk.WINDOW_TOPLEVEL) world.window.set_default_size(800, 150) world.frame = DockFrame() world.window.add(world.frame) world.layout = DockLayout() world.layout.add(world.frame) paned = DockPaned() world.frame.add(paned) world.window.show() world.frame.show() paned.show() world.groups = [] for i in range(int(n_groups)): group = DockGroup() paned.add(group) group.show() world.groups.append(group) @Given('(one|another) containing (\d+) items') def setup_items(one_other, n_items): if one_other == 'one': index = 0 else: index = world.item_index + 1 for n in range(int(n_items)): button = gtk.Button() item = DockItem(icon_name='file', title='Item %s' % n, title_tooltip_text='') item.add(button) item.show() world.groups[index].add(item) world.item_index = index @Given('start a main loop') def start_a_main_loop(): world.window.show_all() # simulate gtk.main() while gtk.events_pending(): gtk.main_iteration() @Given('define dockgroup (\d+) as "([^"]+)"') def define_group_by_name(nth_group, name): group = world.groups[int(nth_group) - 1] #print 'Define group', group, 'as', name setattr(world, name, group) @Given('define item (\d+) from dockgroup (\d+) as "([^"]+)"') def define_item_by_name(nth_item, nth_group, name): group = world.groups[int(nth_group) - 1] item = group.get_children()[int(nth_item) - 1] #print 'Define item', item, 'as', name setattr(world, name, (group, item)) @When('I drag item "([^"]+)"') def drag_item(name): group, item = getattr(world, name) assert item in group.items group.dragcontext.source_x = 1 group.dragcontext.source_y = 1 group.dragcontext.source_button = 1 group.dragcontext.dragged_object = [ item ] world.dragged_items = group, [ item ] group.do_drag_begin(context=None) assert item.get_parent() is None #import time #time.sleep(1000) @When('I drag all items in group "([^"]+)"') def drag_all_items_in_group(name): group = getattr(world, name) group.dragcontext.source_x = 1 group.dragcontext.source_y = 1 group.dragcontext.source_button = 1 group.dragcontext.dragged_object = [ item for item in group.items ] world.dragged_items = group, group.dragcontext.dragged_object group.do_drag_begin(context=None) def drop_item(dest, x, y): source_group, items = world.dragged_items world.drop_pos = x, y context = StubContext(source_group, items) drag_data = drag_motion(dest, context, x, y, 0) assert drag_data, "No data from motion over %s at (%d, %d)" % (dest, x, y) drag_data.received(selection_data=None, info=None) del world.dragged_items world.dropped_items = items world.dropped_on_dest = dest drag_end(source_group, StubContext(source_group, items)) groups = world.layout.get_widgets('EtkDockGroup') world.new_groups = list(set(groups).difference(world.groups)) world.groups = groups @When('I drop it on the content section in group "([^"]+)"') def drop_item_on_content(name): dest_group = getattr(world, name) a = dest_group.allocation x, y = a.x + a.width / 2, a.y + a.height / 2 drop_item(dest_group, x, y) @When('I drop it on tab "([^"]+)" in group "([^"]+)"') def drop_item_on_tab(tabname, groupname): dest_group = getattr(world, groupname) dg2, item = getattr(world, tabname) assert dg2 is dest_group assert item in dest_group.items tab = [tab for tab in dest_group._tabs if tab.item is item][0] a = tab.area x, y = a.x + a.width - 2, a.y + a.height - 2 drop_item(dest_group, x, y) @When('I drop it between groups "([^"]+)" and "([^"]+)"') def drop_between_groups(group1name, group2name): group1 = getattr(world, group1name) group2 = getattr(world, group2name) paned = group1.get_parent() # Test is restricted to two groups having the same DockPaned as parent assert paned is group2.get_parent() index = [i.child for i in paned._items].index(group1) assert index == [i.child for i in paned._items].index(group2) - 1 handle = paned._handles[index] x, y = handle.area.x + handle.area.width / 2, handle.area.y + handle.area.height / 2 drop_item(paned, x, y) @When('I drop it before the first group') def drop_before_first_group(): first_group = world.groups[0] a = first_group.allocation x, y = a.x, a.y + a.height / 2 drop_item(first_group, x, y) @When('I drop it outside of the frame') def drop_it_outside_of_the_frame(): source_widget, items = world.dragged_items drag_failed(source_widget, StubContext(source_widget, items), 1) world.new_frame = world.layout.get_floating_frames().next() @Then('item "([^"]+)" is part of "([^"]+)"') #@Then('item "(drag-me)" is part of "(to-group)"') def then_tab_on_group(item_name, group_name): _, item = getattr(world, item_name) group = getattr(world, group_name) assert item in group.items @Then('item "([^"]+)" is not part of "([^"]+)"') def then_tab_not_on_group(item_name, group_name): _, item = getattr(world, item_name) group = getattr(world, group_name) assert item not in group.items @Then('it has the focus') def then_it_has_the_focus(): assert len(world.dropped_items) == 1 assert world.dropped_on_dest._current_tab.item is world.dropped_items[0] @Then('it has been placed in just before "([^"]+)"') def placed_before_tab(name): newgroup, item = getattr(world, name) start_a_main_loop() assert len(world.dropped_items) == 1 items = newgroup.visible_items print 'it has been placed in just before', items.index(world.dropped_items[0]), print items.index(item) assert items assert items.index(world.dropped_items[0]) == items.index(item) - 1 @Then('a new group should have been created') def then_new_group(): assert len(world.new_groups) == 1 @Then('it should contain the item') def then_contains_item(): items = world.new_groups[0].items assert set(world.dropped_items).issubset(items), (world.dropped_items, items) @Then('the group "([^"]+)" has been removed') def the_group_has_been_removed(group): group = getattr(world, group) assert group.get_parent() is None, group.get_parent() @Then('a floating window is created') def new_window_is_created(): assert len(list(world.layout.get_floating_frames())) == 1 @Then('it contains a new group with the item') def contains_a_new_group_with_the_item(): group, items = world.dragged_items assert len(items) == 1 assert items[0].get_parent() is not group assert items[0].get_ancestor(DockFrame) is world.new_frame # vim:sw=4:et:ai etk.docking-0.2/doc/platform/000077500000000000000000000000001175471717700161655ustar00rootroot00000000000000etk.docking-0.2/doc/platform/colors.html000066400000000000000000000557641175471717700203750ustar00rootroot00000000000000 GTK+ Color Schemes

Ubuntu 10.04

Ambiance

  STATE_INSENSITIVE STATE_NORMAL STATE_PRELIGHT STATE_ACTIVE STATE_SELECTED
fg          
bg          
base          
light          
mid          
dark          
text          
text_aa          
white  
black  

Clearlooks

  STATE_INSENSITIVE STATE_NORMAL STATE_PRELIGHT STATE_ACTIVE STATE_SELECTED
fg          
bg          
base          
light          
mid          
dark          
text          
text_aa          
white  
black  

Dust

  STATE_INSENSITIVE STATE_NORMAL STATE_PRELIGHT STATE_ACTIVE STATE_SELECTED
fg          
bg          
base          
light          
mid          
dark          
text          
text_aa          
white  
black  

Mac OS X

X11, default theme

  STATE_INSENSITIVE STATE_NORMAL STATE_PRELIGHT STATE_ACTIVE STATE_SELECTED
fg          
bg          
base          
light          
mid          
dark          
text          
text_aa          
white  
black  

Quartz

  STATE_INSENSITIVE STATE_NORMAL STATE_PRELIGHT STATE_ACTIVE STATE_SELECTED
fg          
bg          
base          
light          
mid          
dark          
text          
text_aa          
white  
black  

Windows XP

Windows Classic, Windows Standard

  STATE_INSENSITIVE STATE_NORMAL STATE_PRELIGHT STATE_ACTIVE STATE_SELECTED
fg          
bg          
base          
light          
mid          
dark          
text          
text_aa          
white  
black  

Window XP, Blue

  STATE_INSENSITIVE STATE_NORMAL STATE_PRELIGHT STATE_ACTIVE STATE_SELECTED
fg          
bg          
base          
light          
mid          
dark          
text          
text_aa          
white  
black  

Window XP, Olive Green

  STATE_INSENSITIVE STATE_NORMAL STATE_PRELIGHT STATE_ACTIVE STATE_SELECTED
fg          
bg          
base          
light          
mid          
dark          
text          
text_aa          
white  
black  

Window XP, Silver

  STATE_INSENSITIVE STATE_NORMAL STATE_PRELIGHT STATE_ACTIVE STATE_SELECTED
fg          
bg          
base          
light          
mid          
dark          
text          
text_aa          
white  
black  
etk.docking-0.2/doc/platform/colors.py000066400000000000000000000060711175471717700200440ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . ''' Prints a colored html table containing all themeable GTK+ colors in each state to stdout. ''' from __future__ import absolute_import import pygtk pygtk.require('2.0') import gtk def main(): w = gtk.Window() w.show() colors1 = ['fg', 'bg', 'base', 'light', 'mid', 'dark', 'text', 'text_aa'] colors2 = ['white', 'black'] print ' ' print ' ' print ' ' print ' ' print ' ' print ' ' print ' ' print ' ' print ' ' for attribute in colors1: print ' ' print ' ' % attribute color = w.style.__getattribute__(attribute)[gtk.STATE_INSENSITIVE] print ' ' % (int(color.red / 256.00), int(color.green / 256.00), int(color.blue / 256.00)) color = w.style.__getattribute__(attribute)[gtk.STATE_NORMAL] print ' ' % (int(color.red / 256.00), int(color.green / 256.00), int(color.blue / 256.00)) color = w.style.__getattribute__(attribute)[gtk.STATE_PRELIGHT] print ' ' % (int(color.red / 256.00), int(color.green / 256.00), int(color.blue / 256.00)) color = w.style.__getattribute__(attribute)[gtk.STATE_ACTIVE] print ' ' % (int(color.red / 256.00), int(color.green / 256.00), int(color.blue / 256.00)) color = w.style.__getattribute__(attribute)[gtk.STATE_SELECTED] print ' ' % (int(color.red / 256.00), int(color.green / 256.00), int(color.blue / 256.00)) print ' ' for attribute in colors2: print ' ' print ' ' % attribute color = w.style.__getattribute__(attribute) print ' ' % (int(color.red / 256.00), int(color.green / 256.00), int(color.blue / 256.00)) print ' ' print '
 STATE_INSENSITIVESTATE_NORMALSTATE_PRELIGHTSTATE_ACTIVESTATE_SELECTED
%s     
%s 
' if __name__ == '__main__': main() etk.docking-0.2/doc/reference/000077500000000000000000000000001175471717700162775ustar00rootroot00000000000000etk.docking-0.2/doc/reference/Makefile000066400000000000000000000110021175471717700177310ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/etkdocking.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/etkdocking.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/etkdocking" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/etkdocking" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." etk.docking-0.2/doc/reference/build/000077500000000000000000000000001175471717700173765ustar00rootroot00000000000000etk.docking-0.2/doc/reference/build/.keep000066400000000000000000000000001175471717700203110ustar00rootroot00000000000000etk.docking-0.2/doc/reference/make.bat000066400000000000000000000102611175471717700177040ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\etkdocking.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\etkdocking.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end etk.docking-0.2/doc/reference/source/000077500000000000000000000000001175471717700175775ustar00rootroot00000000000000etk.docking-0.2/doc/reference/source/_static/000077500000000000000000000000001175471717700212255ustar00rootroot00000000000000etk.docking-0.2/doc/reference/source/_static/.keep000066400000000000000000000000001175471717700221400ustar00rootroot00000000000000etk.docking-0.2/doc/reference/source/_templates/000077500000000000000000000000001175471717700217345ustar00rootroot00000000000000etk.docking-0.2/doc/reference/source/_templates/.keep000066400000000000000000000000001175471717700226470ustar00rootroot00000000000000etk.docking-0.2/doc/reference/source/api/000077500000000000000000000000001175471717700203505ustar00rootroot00000000000000etk.docking-0.2/doc/reference/source/api/dockframe.rst000066400000000000000000000001711175471717700230340ustar00rootroot00000000000000:class:`etk.DockFrame` ====================== .. autoclass:: etk.docking.DockFrame :show-inheritance: :members: etk.docking-0.2/doc/reference/source/api/dockgroup.rst000066400000000000000000000004661175471717700231050ustar00rootroot00000000000000:class:`etk.DockGroup` ====================== .. autoclass:: etk.docking.DockGroup :show-inheritance: :members: append_item, prepend_item, insert_item, remove_item, item_num, get_n_items, get_nth_item, get_current_item, set_current_item, next_item, prev_item, reorder_item etk.docking-0.2/doc/reference/source/api/dockitem.rst000066400000000000000000000001661175471717700227040ustar00rootroot00000000000000:class:`etk.DockItem` ===================== .. autoclass:: etk.docking.DockItem :show-inheritance: :members: etk.docking-0.2/doc/reference/source/api/docklayout.rst000066400000000000000000000005451175471717700232640ustar00rootroot00000000000000:class:`etk.DockLayout` ======================= .. autoclass:: etk.docking.DockLayout :show-inheritance: :members: Signals ------- item-closed ( group, item ): event forwarded from the DockGroup on which the item was removed. This makes for easy central maintenance of how to deal with closed items (e.g. if the items should be destroyed or not). etk.docking-0.2/doc/reference/source/api/dockpaned.rst000066400000000000000000000002321175471717700230270ustar00rootroot00000000000000:class:`etk.DockPaned` ====================== .. autoclass:: etk.docking.DockPaned :show-inheritance: True :member-order: bysource :members: etk.docking-0.2/doc/reference/source/conf.py000066400000000000000000000172461175471717700211100ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # etk.docking documentation build configuration file, created by # sphinx-quickstart on Thu Sep 23 17:46:41 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os import sys import re def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() def get_version(): file = os.path.join(os.path.dirname(__file__), '..', '..', '..', 'lib', 'etk', 'docking', '__init__.py') return re.compile(r".*__version__ = '(.*?)'", re.S).match(read(file)).group(1) def autodoc_skip_member(app, what, name, obj, skip, options): # Ignore gobject virtual function implementations (signal handlers) if name.startswith('do_') or '.do_' in name: return True def setup(app): app.connect('autodoc-skip-member', autodoc_skip_member) # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.append(os.path.abspath('../../../lib')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'etk.docking' copyright = u'2010, etk.docking Contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = get_version() # The full version, including alpha/beta/rc tags. release = get_version() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'etkdockingdoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'etkdocking.tex', u'etk.docking Documentation', u'etk.docing Contributors', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'etkdocking', u'etk.docking Documentation', [u'etk.docing Contributors'], 1) ] # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'http://docs.python.org/': None} etk.docking-0.2/doc/reference/source/dev/000077500000000000000000000000001175471717700203555ustar00rootroot00000000000000etk.docking-0.2/doc/reference/source/dev/serialization.rst000066400000000000000000000023741175471717700237720ustar00rootroot00000000000000Serialization ============= One of the required features is to be able to persist and load a layout. This can be done by means of the DockStore. A DockStore is able to load and save layouts. For loading and saving, the most simple xml serializer is used: xml.parser or etree:: ... The layout can be set up automatically as far as DockFrame, DockPaned, DockGroup and DockItem is concerned. The contents of a single DockItem is harder to construct. For this the DockStore needs to consult a delegate object that implements the DockStore protocol:: class DockStoreDelegate(object): def load(self, id): pass Open items: * It's interesting to see to what extend the current GUI builder code can suite us. etk.docking-0.2/doc/reference/source/index.rst000066400000000000000000000006461175471717700214460ustar00rootroot00000000000000:mod:`etk.docking` - PyGTK Docking Widgets ========================================== API documentation ----------------- .. toctree:: :maxdepth: 2 api/dockframe api/dockpaned api/dockgroup api/dockitem api/docklayout Developer documentation ----------------------- .. toctree:: :maxdepth: 2 dev/serialization Indices and tables ================== * :ref:`genindex` * :ref:`search` etk.docking-0.2/ez_setup.py000066400000000000000000000240551175471717700160120ustar00rootroot00000000000000#!python """Bootstrap setuptools installation If you want to use setuptools in your package's setup.py, just include this file in the same directory with it, and add this to the top of your setup.py:: from ez_setup import use_setuptools use_setuptools() If you want to require a specific version of setuptools, set a download mirror, or use an alternate download directory, you can do so by supplying the appropriate options to ``use_setuptools()``. This file can also be run as a script to install or upgrade setuptools. """ import sys DEFAULT_VERSION = "0.6c11" DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] md5_data = { 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', 'setuptools-0.6c10-py2.3.egg': 'ce1e2ab5d3a0256456d9fc13800a7090', 'setuptools-0.6c10-py2.4.egg': '57d6d9d6e9b80772c59a53a8433a5dd4', 'setuptools-0.6c10-py2.5.egg': 'de46ac8b1c97c895572e5e8596aeb8c7', 'setuptools-0.6c10-py2.6.egg': '58ea40aef06da02ce641495523a0b7f5', 'setuptools-0.6c11-py2.3.egg': '2baeac6e13d414a9d28e7ba5b5a596de', 'setuptools-0.6c11-py2.4.egg': 'bd639f9b0eac4c42497034dec2ec0c2b', 'setuptools-0.6c11-py2.5.egg': '64c94f3bf7a72a13ec83e0b24f2749b2', 'setuptools-0.6c11-py2.6.egg': 'bfa92100bd772d5a213eedd356d64086', 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f', 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2', 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc', 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20', 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab', 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53', 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2', 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e', 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372', 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902', 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de', 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b', 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03', 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a', 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6', 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a', } import sys, os try: from hashlib import md5 except ImportError: from md5 import md5 def _validate_md5(egg_name, data): if egg_name in md5_data: digest = md5(data).hexdigest() if digest != md5_data[egg_name]: print >>sys.stderr, ( "md5 validation of %s failed! (Possible download problem?)" % egg_name ) sys.exit(2) return data def use_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, download_delay=15 ): """Automatically find/download setuptools and make it available on sys.path `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where setuptools will be downloaded, if it is not already available. If `download_delay` is specified, it should be the number of seconds that will be paused before initiating a download, should one be required. If an older version of setuptools is installed, this routine will print a message to ``sys.stderr`` and raise SystemExit in an attempt to abort the calling script. """ was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules def do_download(): egg = download_setuptools(version, download_base, to_dir, download_delay) sys.path.insert(0, egg) import setuptools; setuptools.bootstrap_install_from = egg try: import pkg_resources except ImportError: return do_download() try: pkg_resources.require("setuptools>="+version); return except pkg_resources.VersionConflict, e: if was_imported: print >>sys.stderr, ( "The required version of setuptools (>=%s) is not available, and\n" "can't be installed while this script is running. Please install\n" " a more recent version first, using 'easy_install -U setuptools'." "\n\n(Currently using %r)" ) % (version, e.args[0]) sys.exit(2) else: del pkg_resources, sys.modules['pkg_resources'] # reload ok return do_download() except pkg_resources.DistributionNotFound: return do_download() def download_setuptools( version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, delay = 15 ): """Download setuptools from a specified location and return its filename `version` should be a valid setuptools version number that is available as an egg for download under the `download_base` URL (which should end with a '/'). `to_dir` is the directory where the egg will be downloaded. `delay` is the number of seconds to pause before an actual download attempt. """ import urllib2, shutil egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) url = download_base + egg_name saveto = os.path.join(to_dir, egg_name) src = dst = None if not os.path.exists(saveto): # Avoid repeated downloads try: from distutils import log if delay: log.warn(""" --------------------------------------------------------------------------- This script requires setuptools version %s to run (even to display help). I will attempt to download it for you (from %s), but you may need to enable firewall access for this script first. I will start the download in %d seconds. (Note: if this machine does not have network access, please obtain the file %s and place it in this directory before rerunning this script.) ---------------------------------------------------------------------------""", version, download_base, delay, url ); from time import sleep; sleep(delay) log.warn("Downloading %s", url) src = urllib2.urlopen(url) # Read/write all in one block, so we don't create a corrupt file # if the download is interrupted. data = _validate_md5(egg_name, src.read()) dst = open(saveto,"wb"); dst.write(data) finally: if src: src.close() if dst: dst.close() return os.path.realpath(saveto) def main(argv, version=DEFAULT_VERSION): """Install or upgrade setuptools and EasyInstall""" try: import setuptools except ImportError: egg = None try: egg = download_setuptools(version, delay=0) sys.path.insert(0,egg) from setuptools.command.easy_install import main return main(list(argv)+[egg]) # we're done here finally: if egg and os.path.exists(egg): os.unlink(egg) else: if setuptools.__version__ == '0.0.1': print >>sys.stderr, ( "You have an obsolete version of setuptools installed. Please\n" "remove it from your system entirely before rerunning this script." ) sys.exit(2) req = "setuptools>="+version import pkg_resources try: pkg_resources.require(req) except pkg_resources.VersionConflict: try: from setuptools.command.easy_install import main except ImportError: from easy_install import main main(list(argv)+[download_setuptools(delay=0)]) sys.exit(0) # try to force an exit else: if argv: from setuptools.command.easy_install import main main(argv) else: print "Setuptools version",version,"or greater has been installed." print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' def update_md5(filenames): """Update our built-in md5 registry""" import re for name in filenames: base = os.path.basename(name) f = open(name,'rb') md5_data[base] = md5(f.read()).hexdigest() f.close() data = [" %r: %r,\n" % it for it in md5_data.items()] data.sort() repl = "".join(data) import inspect srcfile = inspect.getsourcefile(sys.modules[__name__]) f = open(srcfile, 'rb'); src = f.read(); f.close() match = re.search("\nmd5_data = {\n([^}]+)}", src) if not match: print >>sys.stderr, "Internal error!" sys.exit(2) src = src[:match.start(1)] + repl + src[match.end(1):] f = open(srcfile,'w') f.write(src) f.close() if __name__=='__main__': if len(sys.argv)>2 and sys.argv[1]=='--md5update': update_md5(sys.argv[2:]) else: main(sys.argv[1:]) etk.docking-0.2/lib/000077500000000000000000000000001175471717700143425ustar00rootroot00000000000000etk.docking-0.2/lib/etk/000077500000000000000000000000001175471717700151255ustar00rootroot00000000000000etk.docking-0.2/lib/etk/__init__.py000066400000000000000000000000701175471717700172330ustar00rootroot00000000000000__import__('pkg_resources').declare_namespace(__name__) etk.docking-0.2/lib/etk/docking/000077500000000000000000000000001175471717700165435ustar00rootroot00000000000000etk.docking-0.2/lib/etk/docking/__init__.py000066400000000000000000000063411175471717700206600ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . from __future__ import absolute_import __all__ = ['DockLayout', 'DockFrame', 'DockPaned', 'DockGroup', 'DockItem', 'settings'] __version__ = '0.2' __docformat__ = 'restructuredtext' ############################################################################ # Initialization ############################################################################ import os, gtk # Register some custom icons into the default icon theme path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'icons', '16x16')) gtk.icon_theme_add_builtin_icon('compact-close', 16, gtk.gdk.pixbuf_new_from_file(os.path.join(path, 'compact-close.png'))) gtk.icon_theme_add_builtin_icon('compact-close-prelight', 16, gtk.gdk.pixbuf_new_from_file(os.path.join(path, 'compact-close-prelight.png'))) gtk.icon_theme_add_builtin_icon('compact-list', 16, gtk.gdk.pixbuf_new_from_file(os.path.join(path, 'compact-list.png'))) gtk.icon_theme_add_builtin_icon('compact-minimize', 16, gtk.gdk.pixbuf_new_from_file(os.path.join(path, 'compact-minimize.png'))) gtk.icon_theme_add_builtin_icon('compact-maximize', 16, gtk.gdk.pixbuf_new_from_file(os.path.join(path, 'compact-maximize.png'))) gtk.icon_theme_add_builtin_icon('compact-restore', 16, gtk.gdk.pixbuf_new_from_file(os.path.join(path, 'compact-restore.png'))) # Check for elib, not required. try: from elib.intl import install_module except ImportError: def _(message): return message else: localedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'share', 'locale')) _ = install_module('etk.docking', localedir) del localedir, install_module # Keep our namespace nice and tidy del os, gtk, path ############################################################################ # GtkBuilder and Glade create GObject instances (and thus GTK+ widgets) using # gobject.new(). For this to work, we have to be sure our subclasses have been # registered with the GObject type system when etk.docking is imported. # This also defines the widgets that can be considered public. ############################################################################ from .docklayout import DockLayout from .docklayout import add_new_group_left, add_new_group_right, \ add_new_group_above, add_new_group_below, add_new_group_floating from .dockframe import DockFrame from .dockpaned import DockPaned from .dockgroup import DockGroup from .dockitem import DockItem from .docksettings import settings etk.docking-0.2/lib/etk/docking/compactbutton.py000066400000000000000000000230701175471717700220010ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . from __future__ import absolute_import from logging import getLogger import gobject import gtk import gtk.gdk as gdk from .util import load_icon class CompactButton(gtk.Widget): __gtype_name__ = 'EtkCompactButton' __gsignals__ = {'clicked': (gobject.SIGNAL_RUN_FIRST | gobject.SIGNAL_ACTION, gobject.TYPE_NONE, tuple())} __gproperties__ = {'icon-name-normal': (gobject.TYPE_STRING, 'icon name normal', 'icon name normal', '', gobject.PARAM_READWRITE), 'icon-name-prelight': (gobject.TYPE_STRING, 'icon name prelight', 'icon name prelight', '', gobject.PARAM_READWRITE), 'icon-name-active': (gobject.TYPE_STRING, 'icon name active', 'icon name active', '', gobject.PARAM_READWRITE), 'size': (gobject.TYPE_UINT, 'size', 'size', 0, gobject.G_MAXUINT, 16, gobject.PARAM_READWRITE), 'has-frame': (gobject.TYPE_BOOLEAN, 'has frame', 'has frame', True, gobject.PARAM_READWRITE)} def __init__(self, icon_name_normal='', size=16, has_frame=True): gtk.Widget.__init__(self) self.set_flags(self.flags() | gtk.NO_WINDOW) # Initialize logging self.log = getLogger('%s.%s' % (self.__gtype_name__, hex(id(self)))) # Internal housekeeping self._entered = False self._icon_normal = None self._icon_prelight = None self._icon_active = None self.set_size(size) self.set_has_frame(has_frame) self.set_icon_name_normal(icon_name_normal) ############################################################################ # Convenience ############################################################################ def _refresh_icons(self): self._icon_normal = load_icon(self._icon_name_normal, self._size) if self._icon_name_prelight == self._icon_name_normal: self._icon_prelight = self._icon_normal else: self._icon_prelight = load_icon(self._icon_name_prelight, self._size) if self._icon_name_active == self._icon_name_prelight: self._icon_active = self._icon_prelight else: self._icon_active = load_icon(self._icon_name_active, self._size) ############################################################################ # GObject ############################################################################ def do_get_property(self, pspec): if pspec.name == 'icon-name-normal': return self.get_icon_name_normal() elif pspec.name == 'icon-name-prelight': return self.get_icon_name_prelight() elif pspec.name == 'icon-name-active': return self.get_icon_name_active() elif pspec.name == 'size': return self.get_size() elif pspec.name == 'has-frame': return self.get_has_frame() def do_set_property(self, pspec, value): if pspec.name == 'icon-name-normal': self.set_icon_name_normal(value) elif pspec.name == 'icon-name-prelight': self.set_icon_name_prelight(value) elif pspec.name == 'icon-name-active': self.set_icon_name_active(value) elif pspec.name == 'size': self.set_size(value) elif pspec.name == 'has-frame': self.set_has_frame(value) def get_icon_name_normal(self): return self._icon_name_normal def set_icon_name_normal(self, value): self._icon_name_normal = value self._icon_name_prelight = value self._icon_name_active = value if self.flags() & gtk.REALIZED: self._refresh_icons() self.queue_resize() def get_icon_name_prelight(self): return self._icon_name_prelight def set_icon_name_prelight(self, value): self._icon_name_prelight = value self._icon_name_active = value if self.flags() & gtk.REALIZED: self._refresh_icons() self.queue_resize() def get_icon_name_active(self): return self._icon_name_active def set_icon_name_active(self, value): self._icon_name_active = value if self.flags() & gtk.REALIZED: self._refresh_icons() self.queue_resize() def get_size(self): return self._size def set_size(self, value): self._size = value def get_has_frame(self): return self._has_frame def set_has_frame(self, value): self._has_frame = value ############################################################################ # GtkWidget ############################################################################ def do_realize(self): gtk.Widget.do_realize(self) self._input_window = gdk.Window(self.get_parent_window(), x = self.allocation.x, y = self.allocation.y, width = self.allocation.width, height = self.allocation.height, window_type = gdk.WINDOW_CHILD, wclass = gdk.INPUT_ONLY, visual = self.get_visual(), colormap = self.get_colormap(), event_mask = (gdk.ENTER_NOTIFY_MASK | gdk.LEAVE_NOTIFY_MASK | gdk.BUTTON_PRESS_MASK | gdk.BUTTON_RELEASE_MASK)) self._input_window.set_user_data(self) self._refresh_icons() def do_unrealize(self): self._input_window.set_user_data(None) self._input_window.destroy() gtk.Widget.do_unrealize(self) def do_map(self): self._input_window.show() gtk.Widget.do_map(self) def do_unmap(self): self._input_window.hide() gtk.Widget.do_unmap(self) def do_size_request(self, requisition): requisition.width = self._size requisition.height = self._size def do_size_allocate(self, allocation): self.allocation = allocation if self.flags() & gtk.REALIZED: self._input_window.move_resize(*self.allocation) def do_expose_event(self, event): # Draw icon if self.state == gtk.STATE_NORMAL: pixbuf = self._icon_normal x = self.allocation.x y = self.allocation.y elif self.state == gtk.STATE_PRELIGHT: pixbuf = self._icon_prelight x = self.allocation.x y = self.allocation.y elif self.state == gtk.STATE_ACTIVE: pixbuf = self._icon_active x = self.allocation.x + 1 y = self.allocation.y + 1 event.window.draw_pixbuf(self.style.base_gc[self.state], pixbuf, 0, 0, x, y) # Draw frame if self._has_frame and self.state != gtk.STATE_NORMAL: event.window.draw_rectangle(self.style.dark_gc[self.state], False, self.allocation.x, self.allocation.y, self.allocation.width - 1, self.allocation.height - 1) return False def do_enter_notify_event(self, event): self._entered = True self.set_state(gtk.STATE_PRELIGHT) self.queue_draw() return True def do_leave_notify_event(self, event): self._entered = False self.set_state(gtk.STATE_NORMAL) self.queue_draw() return True def do_button_press_event(self, event): if event.button == 1: self.set_state(gtk.STATE_ACTIVE) self.queue_draw() return True def do_button_release_event(self, event): if event.button == 1 and self._entered == True: self.set_state(gtk.STATE_PRELIGHT) self.emit('clicked') self.queue_draw() return True etk.docking-0.2/lib/etk/docking/dnd.py000066400000000000000000000140251175471717700176640ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . from __future__ import absolute_import from logging import getLogger import gtk import gtk.gdk as gdk DRAG_TARGET_ITEM_LIST = ('x-etk-docking/item-list', gtk.TARGET_SAME_APP, 0) class DockDragContext(object): ''' As we can't reliably use drag_source_set to initiate a drag operation (there's just to much information locked away in C structs - GtkDragSourceSite, GtkDragSourceInfo, GtkDragDestSite, GtkDragDestInfo, ... - that are not exposed to Python), we are sadly forced to mimic some of that default behavior. This class is also used to store extra information about a drag operation in progress not available in the C structs mentioned above. ''' __slots__ = ['dragging', # are we dragging or not (bool) 'dragged_object', # object being dragged 'source_x', # x coordinate starting a potential drag 'source_y', # y coordinate starting a potential drag 'source_button', # the button the user pressed to start the drag 'offset_x', # cursor x offset relative to dragged object source_x 'offset_y'] # cursor y offset relative to dragged object source_y def __init__(self): self.reset() def reset(self): self.dragging = False self.dragged_object = None self.source_x = None self.source_y = None self.source_button = None self.offset_x = None self.offset_y = None class Placeholder(gtk.DrawingArea): __gtype_name__ = 'EtkDockPlaceholder' def do_expose_event(self, expose): a = self.allocation c = self.window.cairo_create() c.set_source_rgb(0, 0, 0) c.set_line_width(1.0) c.rectangle(0.5, 0.5, a.width - 1, a.height - 1) #c.set_source_rgba(0, 0, 0, 0) #c.fill() c.stroke() class PlaceHolderWindow(gtk.Window): ''' The etk.dnd.PlaceHolderWindow is a gtk.Window that can highlight an area on screen. When a PlaceHolderWindow has no child widget an undecorated utility popup is shown drawing a transparent highlighting rectangle around the desired area. The location and size of the highlight rectangle can easily be updated with the move_resize method. The show and hide methods do as they suggest. When you add a child widget to the PlaceHolderWindow the utility popup is automatically decorated (get's a title bar) and removes it's transparency. This is used by the drag and drop implementation to mark a valid destination for the drag and drop operation while dragging and as the container window for teared off floating items. ''' __gtype_name__ = 'EtkDockPlaceHolderWindow' def __init__(self): gtk.Window.__init__(self, gtk.WINDOW_POPUP) self.set_decorated(False) self.set_skip_taskbar_hint(True) self.set_type_hint(gdk.WINDOW_TYPE_HINT_UTILITY) #TODO: self.set_transient_for(???.get_toplevel()) # Initialize logging self.log = getLogger('%s.%s' % (self.__gtype_name__, hex(id(self)))) # Internal housekeeping self._gc = None def _create_shape(self, width, height): black = gdk.Color(red=0, green=0, blue=0, pixel=1) white = gdk.Color(red=255, green=255, blue=255, pixel=0) pm = gdk.Pixmap(self.window, width, height, 1) gc = gdk.GC(pm) gc.set_background(white) gc.set_foreground(white) pm.draw_rectangle(gc, True, 0, 0, width, height) gc.set_foreground(black) pm.draw_rectangle(gc, False, 0, 0, width - 1, height - 1) pm.draw_rectangle(gc, False, 1, 1, width - 3, height - 3) self.shape_combine_mask(pm, 0, 0) ############################################################################ # GtkWidget ############################################################################ def do_realize(self): gtk.Window.do_realize(self) self._gc = self.style.bg_gc[gtk.STATE_SELECTED] def do_unrealize(self): self._gc = None gtk.Window.do_unrealize(self) def do_size_allocate(self, allocation): self.log.debug('%s' % allocation) gtk.Window.do_size_allocate(self, allocation) self._create_shape(allocation.width, allocation.height) def do_expose_event(self, event): self.log.debug('%s' % event) gtk.Window.do_expose_event(self, event) width, height = self.get_size() self.window.draw_rectangle(self._gc, False, 0, 0, width-1, height-1) self.window.draw_rectangle(self._gc, False, 1, 1, width-3, height-3) return True ############################################################################ # GtkContainer ############################################################################ def do_add(self, widget): self.set_decorated(True) self.reset_shapes() gtk.Window.add(self, widget) ############################################################################ # EtkPlaceHolderWindow ############################################################################ def move_resize(self, x, y, width, height): self.log.debug('%s, %s, %s, %s' % (x, y, width, height)) self.move(x, y) self.resize(width, height) etk.docking-0.2/lib/etk/docking/dockframe.py000066400000000000000000000060031175471717700210470ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . from __future__ import absolute_import from logging import getLogger import gtk import gtk.gdk as gdk class DockFrame(gtk.Bin): ''' The etk.DockFrame widget is a gtk.Bin that acts as the toplevel widget for a dock layout hierarchy. ''' __gtype_name__ = 'EtkDockFrame' def __init__(self): gtk.Bin.__init__(self) # Initialize logging self.log = getLogger('%s.%s' % (self.__gtype_name__, hex(id(self)))) # Internal housekeeping self._placeholder = None ############################################################################ # GtkWidget ############################################################################ def do_size_request(self, requisition): requisition.width = 0 requisition.height = 0 if self.child and self.child.flags() & gtk.VISIBLE: (requisition.width, requisition.height) = self.child.size_request() requisition.width += self.border_width * 2 requisition.height += self.border_width * 2 def do_size_allocate(self, allocation): self.allocation = allocation if self.child and self.child.flags() & gtk.VISIBLE: child_allocation = gdk.Rectangle() child_allocation.x = allocation.x + self.border_width child_allocation.y = allocation.y + self.border_width child_allocation.width = allocation.width - (2 * self.border_width) child_allocation.height = allocation.height - (2 * self.border_width) self.child.size_allocate(child_allocation) ############################################################################ # EtkDockFrame ############################################################################ def set_placeholder(self, placeholder): """ Set a new placeholder widget on the frame. The placeholder is drawn on top of the dock items. If a new placeholder is set, an existing placeholder is destroyed. """ if self._placeholder: self._placeholder.unparent() self._placeholder.destroy() self._placeholder = None if placeholder: self._placeholder = placeholder self._placeholder.set_parent(self) etk.docking-0.2/lib/etk/docking/dockgroup.py000066400000000000000000001257211175471717700211220ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . from __future__ import absolute_import from logging import getLogger from math import pi from operator import attrgetter from time import time import cairo import gobject import gtk import gtk.gdk as gdk from . import _ from .compactbutton import CompactButton from .dockitem import DockItem from .dnd import DockDragContext, DRAG_TARGET_ITEM_LIST from .hslcolor import HslColor from .util import rect_contains class _DockGroupTab(object): ''' Convenience class storing information about a tab. ''' __slots__ = ['item', # DockItem associated with this tab 'item_title_handler', # item title property notification signal handler id 'item_title_tooltip_text_handler', # item title-tooltip-text property notification signal handler id 'image', # icon (gtk.Image) 'label', # title (gtk.Label) 'button', # close button (etk.docking.CompactButton) 'menu_item', # menu item (gtk.ImageMenuItem) 'area', # area, used for hit testing (gdk.Rectangle) 'last_focused'] # timestamp set last time a tab was focused def __contains__(self, pos): return rect_contains(self.area, *pos) def __str__(self): return "<%s object at 0x%s with label '%s' on %s>" % (self.__class__.__name__, hex(id(self)), self.item.get_title(), self.area) class DockGroup(gtk.Container): ''' The etk.DockGroup widget is a gtk.Container that groups its children in a tabbed interface. Tabs can be reorder by dragging them to the desired location within the same or another etk.DockGroup having the same group-id. You can also drag a complete etk.DockGroup onto another etk.DockGroup having the same group-id to merge all etk.DockItems from the source into the destination etk.DockGroup. ''' __gtype_name__ = 'EtkDockGroup' __gsignals__ = {'item-added': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,)), 'item-removed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,)), 'item-selected': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,))} def __init__(self): gtk.Container.__init__(self) # Initialize logging self.log = getLogger('%s.%s' % (self.__gtype_name__, hex(id(self)))) # Internal housekeeping self.set_border_width(2) self._frame_width = 1 self._spacing = 3 self._available_width = 0 self._decoration_area = gdk.Rectangle() self._tabs = [] self._visible_tabs = [] self._current_tab = None self._tab_state = gtk.STATE_SELECTED self.dragcontext = DockDragContext() gtk.widget_push_composite_child() self._list_button = CompactButton('compact-list') self._list_button.set_tooltip_text(_('Show list')) self._list_button.connect('clicked', self._on_list_button_clicked) self._list_button.set_parent(self) self._min_button = CompactButton('compact-minimize') self._min_button.set_tooltip_text(_('Minimize')) self._min_button.connect('clicked', self._on_min_button_clicked) self._min_button.set_parent(self) self._max_button = CompactButton('compact-maximize') self._max_button.set_tooltip_text(_('Maximize')) self._max_button.connect('clicked', self._on_max_button_clicked) self._max_button.set_parent(self) self._tab_menu = gtk.Menu() self._tab_menu.attach_to_widget(self, None) self._list_menu = gtk.Menu() self._list_menu.attach_to_widget(self._list_button, None) gtk.widget_pop_composite_child() def __len__(self): return len(self._tabs) ############################################################################ # GtkWidget ############################################################################ def do_realize(self): # Internal housekeeping self.set_flags(self.flags() | gtk.REALIZED) self.window = gdk.Window(self.get_parent_window(), x = self.allocation.x, y = self.allocation.y, width = self.allocation.width, height = self.allocation.height, window_type = gdk.WINDOW_CHILD, wclass = gdk.INPUT_OUTPUT, event_mask = (gdk.EXPOSURE_MASK | gdk.POINTER_MOTION_MASK | gdk.BUTTON_PRESS_MASK | gdk.BUTTON_RELEASE_MASK)) self.window.set_user_data(self) self.style.attach(self.window) self.style.set_background(self.window, gtk.STATE_NORMAL) # Set parent window on all child widgets for tab in self._tabs: tab.image.set_parent_window(self.window) tab.label.set_parent_window(self.window) tab.button.set_parent_window(self.window) tab.item.set_parent_window(self.window) self._list_button.set_parent_window(self.window) self._min_button.set_parent_window(self.window) self._max_button.set_parent_window(self.window) def do_unrealize(self): self.window.set_user_data(None) self.window.destroy() gtk.Container.do_unrealize(self) def do_map(self): gtk.Container.do_map(self) self._list_button.show() self._min_button.show() self._max_button.show() self.window.show() def do_unmap(self): self._list_button.hide() self._min_button.hide() self._max_button.hide() self.window.hide() gtk.Container.do_unmap(self) def do_size_request(self, requisition): gtk.Container.do_size_request(self, requisition) # Start with a zero sized decoration area dw = dh = 0 # Compute width and height for each tab, but only add # current item tab size to the decoration area requisition as # the other tabs can be hidden when we don't get enough room # in the allocation fase. for tab in self._tabs: (iw, ih) = tab.image.size_request() (lw, lh) = tab.label.size_request() (bw, bh) = tab.button.size_request() tab.area.width = (self._frame_width + self._spacing + iw + self._spacing + lw + self._spacing + bw + self._spacing + self._frame_width) tab.area.height = (self._frame_width + self._spacing + max(ih, lh, bh) + self._spacing + self._frame_width) if tab == self._current_tab: dw = tab.area.width - lw dh = max(dh, tab.area.height) # Add decoration button sizes to the decoration area (list_w, list_h) = self._list_button.size_request() (min_w, min_h) = self._min_button.size_request() (max_w, max_h) = self._max_button.size_request() dw += (self._spacing + list_w + min_w + max_w + self._spacing + self._frame_width) dh = max(dh, (self._spacing + list_h + self._spacing), (self._spacing + min_h + self._spacing), (self._spacing + max_h + self._spacing)) # Store decoration area size for later usage self._decoration_area.width = dw self._decoration_area.height = dh # Current item: we only honor the height request if self._current_tab: ih = self._current_tab.item.size_request()[1] else: ih = 0 ih += self._frame_width + 2 * self.border_width # Compute total size requisition requisition.width = dw requisition.height = dh + ih def do_size_allocate(self, allocation): self.allocation = allocation if self.flags() & gtk.REALIZED: self.window.move_resize(*allocation) # Allocate space for decoration buttons max_w, max_h = self._max_button.get_child_requisition() min_w, min_h = self._min_button.get_child_requisition() list_w, list_h = self._list_button.get_child_requisition() bh = max(list_h, min_h, max_h) by = self._frame_width + self._spacing self._max_button.size_allocate(gdk.Rectangle(allocation.width - self._frame_width - self._spacing - max_w, by, max_w, bh)) self._min_button.size_allocate(gdk.Rectangle(allocation.width - self._frame_width - self._spacing - max_w - min_w, by, min_w, bh)) self._list_button.size_allocate(gdk.Rectangle(allocation.width - self._frame_width - self._spacing - max_w - min_w - list_w, by, list_w, bh)) # Compute available tab area width self._available_width = (allocation.width - self._frame_width - self._spacing - max_w - min_w - list_w - self._spacing) # Update visible tabs self._update_visible_tabs() # Update visibility on dockitems and composite children used by tabs. for tab in self._tabs: if tab is self._current_tab: tab.item.show() tab.image.show() tab.label.show() tab.button.show() elif tab in self._visible_tabs: tab.item.hide() tab.image.show() tab.label.show() elif tab.item.flags() & gtk.VISIBLE: tab.item.hide() tab.image.hide() tab.label.hide() tab.button.hide() # Only show the list button when needed if len(self._tabs) > len(self._visible_tabs): self._list_button.show() else: self._list_button.hide() # Compute x an y for each visible tab and allocate space for # the tab's composite children. cx = cy = 0 for tab in self._visible_tabs: tab.area.x = cx tab.area.y = cy (iw, ih) = tab.image.get_child_requisition() (lw, lh) = tab.label.get_child_requisition() (bh, bw) = tab.button.get_child_requisition() ix = cx + self._frame_width + self._spacing iy = (tab.area.height - ih) / 2 + 1 tab.image.size_allocate(gdk.Rectangle(ix, iy, iw, ih)) if len(self._visible_tabs) == 1: lw = tab.area.width - (self._frame_width + self._spacing + iw + self._spacing + self._spacing + bw + self._spacing + self._frame_width) lw = max(lw, 0) # Prevent negative width lx = cx + self._frame_width + self._spacing + iw + self._spacing ly = (tab.area.height - lh) / 2 + 1 tab.label.size_allocate(gdk.Rectangle(lx, ly, lw, lh)) bx = (cx + self._frame_width + self._spacing + iw + self._spacing + lw + self._spacing) by = (tab.area.height - bh) / 2 + 1 tab.button.size_allocate(gdk.Rectangle(bx, by, bw, bh)) cx += tab.area.width # Allocate space for the current *item* if self._current_tab: ix = self._frame_width + self.border_width iy = self._decoration_area.height + self.border_width iw = max(allocation.width - (2 * self._frame_width) - (2 * self.border_width), 0) ih = max(allocation.height - (2 * self._frame_width) - (2 * self.border_width) - 23, 0) self._current_tab.item.size_allocate(gdk.Rectangle(ix, iy, iw, ih)) #assert not self._current_tab or self._current_tab in self._visible_tabs self.queue_draw_area(0, 0, self.allocation.width, self.allocation.height) def do_expose_event(self, event): # Prepare colors bg = self.style.bg[self.state] bg = (bg.red_float, bg.green_float, bg.blue_float) dark = self.style.dark[self.state] dark = (dark.red_float, dark.green_float, dark.blue_float) tab_light = self.style.text_aa[self._tab_state] tab_light = (tab_light.red_float, tab_light.green_float, tab_light.blue_float) tab_dark = HslColor(self.style.text_aa[gtk.STATE_SELECTED]) tab_dark.set_l(0.9) tab_dark = tab_dark.get_rgb_float() # Create cairo context c = self.window.cairo_create() # Restrict context to the exposed area, avoid extra work c.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) c.clip_preserve() # Draw background c.set_source_rgb(*bg) c.fill() # Draw frame a = self.allocation c.set_line_width(self._frame_width) c.rectangle(0.5, 0.5, a.width - 1, a.height - 1) c.set_source_rgb(*dark) c.stroke() c.move_to(0.5, self._decoration_area.height - 0.5) c.line_to(a.width + 0.5, self._decoration_area.height - 0.5) c.set_source_rgb(*dark) c.stroke() if self._visible_tabs: # Draw border c.set_line_width(self.border_width) c.rectangle(self._frame_width + self.border_width / 2, self._decoration_area.height + self.border_width / 2, a.width - (2 * self._frame_width) - self.border_width, a.height - self._decoration_area.height - self._frame_width - self.border_width) c.set_source_rgb(*tab_light) c.stroke() # Draw tabs c.set_line_width(self._frame_width) try: # Fails if expose event happens before size request/allocate when a new # current_tab has been selected. visible_index = self._visible_tabs.index(self._current_tab) except ValueError: visible_index = -1 for index, tab in enumerate(self._visible_tabs): tx = tab.area.x ty = tab.area.y tw = tab.area.width th = tab.area.height if index < visible_index and index != 0: c.move_to(tx + 0.5, ty + th) c.line_to(tx + 0.5, ty + 8.5) c.arc(tx + 8.5, 8.5, 8, 180 * (pi / 180), 270 * (pi / 180)) c.set_source_rgb(*dark) c.stroke() elif index > visible_index: c.arc(tx + tw - 8.5, 8.5, 8, 270 * (pi / 180), 360 * (pi / 180)) c.line_to(tx + tw - 0.5, ty + th) c.set_source_rgb(*dark) c.stroke() elif index == visible_index: c.move_to(tx + 0.5, ty + th) if visible_index == 0: c.line_to(tx + 0.5, ty + 0.5) c.line_to(tx + tw - 8.5, ty + 0.5) else: c.line_to(tx + 0.5, ty + 8.5) c.arc(tx + 8.5, 8.5, 8, 180 * (pi / 180), 270 * (pi / 180)) c.line_to(tx + tw - 8.5, ty + 0.5) c.arc(tx + tw - 8.5, 8.5, 8, 270 * (pi / 180), 360 * (pi / 180)) c.line_to(tx + tw - 0.5, ty + th) linear = cairo.LinearGradient(0.5, 0.5, 0.5, th) linear.add_color_stop_rgb(1, *tab_light) linear.add_color_stop_rgb(0, *tab_dark) c.set_source(linear) c.fill_preserve() c.set_source_rgb(*dark) c.stroke() self.propagate_expose(self._current_tab.item, event) self.propagate_expose(tab.image, event) self.propagate_expose(tab.label, event) self.propagate_expose(tab.button, event) self.propagate_expose(self._list_button, event) self.propagate_expose(self._min_button, event) self.propagate_expose(self._max_button, event) return False def do_button_press_event(self, event): ''' :param event: the event that triggered the signal :returns: True to stop other handlers from being invoked for the event. False to propagate the event further. The do_button_press_event() signal handler is executed when a mouse button is pressed. ''' # We might start a DnD operation, or we could simply be starting # a click on a tab. Store information from this event in self.dragcontext # and decide in do_motion_notify_event if we're actually starting a # dnd operation. if event.window is self.window and event.button == 1: self.dragcontext.source_x = event.x self.dragcontext.source_y = event.y self.dragcontext.source_button = event.button return True def do_button_release_event(self, event): ''' :param event: the event that triggered the signal :returns: True to stop other handlers from being invoked for the event. False to propagate the event further. The do_button_release_event() signal handler is executed when a mouse button is released. ''' # Did we click a tab? clicked_tab = self.get_tab_at_pos(event.x, event.y) if clicked_tab: # Set the current item on left click if event.button == 1: self.set_current_item(self._tabs.index(clicked_tab)) # Show context menu on right click elif event.button == 3: #TODO: implement tab context menu def _menu_position(menu): wx, wy = self.window.get_origin() x = int(wx + event.x) y = int(wy + event.y) return (x, y, True) self._tab_menu.show_all() self._tab_menu.popup(parent_menu_shell=None, parent_menu_item=None, func=_menu_position, button=event.button, activate_time=event.time) return True def do_motion_notify_event(self, event): ''' :param event: the event that triggered the signal :returns: True to stop other handlers from being invoked for the event. False to propagate the event further. The do_motion-notify-event() signal handler is executed when the mouse pointer moves while over this widget. ''' # Reset tooltip text self.set_tooltip_text(None) # We should not react to motion_notify_events originating from the # current tab's child widget if event.window is self.window: # Check if we are actually starting a DnD operation if event.state & gdk.BUTTON1_MASK and \ self.dragcontext.source_button == 1 and \ self.drag_check_threshold(int(self.dragcontext.source_x), int(self.dragcontext.source_y), int(event.x), int(event.y)): self.log.debug('drag_begin') self.dragcontext.dragging = True # What are we dragging? tab = self.get_tab_at_pos(self.dragcontext.source_x, self.dragcontext.source_y) if tab: self.dragcontext.dragged_object = [tab.item] else: self.dragcontext.dragged_object = [t.item for t in self._tabs] if self.dragcontext.dragged_object: self.drag_begin([DRAG_TARGET_ITEM_LIST], gdk.ACTION_MOVE, self.dragcontext.source_button, event) # Update tab button visibility for tab in self._visible_tabs: if (event.x, event.y) in tab: # Update tooltip for tab under the cursor self.set_tooltip_text(tab.item.get_title_tooltip_text()) tab.button.show() else: tab.button.hide() return True ############################################################################ # GtkWidget drag source ############################################################################ def do_drag_begin(self, context): ''' :param context: the gdk.DragContext The do_drag_begin() signal handler is executed on the drag source when the user initiates a drag operation. A typical reason to use this signal handler is to set up a custom drag icon with the drag_source_set_icon() method. ''' # Free the item for transport. for item in self.dragcontext.dragged_object: self._dragged_tab_index = [t.item for t in self._tabs].index(item) self.remove_item(self._dragged_tab_index) #TODO: Set drag icon to be empty #TODO: Set drag cursor -> will most likely not (only) happen here... # Can be any of the following, depending on the selected drag destination: # - gdk.DIAMOND_CROSS "stacking" a dockitem into a dockgroup # - gdk.SB_UP_ARROW "splitting" a dockitem into a new dockgroup above the source dockgroup (needs docklayout) # - gdk.SB_DOWN_ARROW "splitting" a dockitem into a new dockgroup below the source dockgroup (needs docklayout) # - gdk.SB_LEFT_ARROW "splitting" a dockitem into a new dockgroup on the left of the source dockgroup (needs docklayout) # - gdk.SB_RIGHT_ARROW "splitting" a dockitem into a new dockgroup on the right of the source dockgroup (needs docklayout) #dnd_window = gtk.Window(gtk.WINDOW_POPUP) #dnd_window.set_screen(self.get_screen()) #dnd_window.add(tab.item) #dnd_window.set_size_request(tab.item.allocation.width, # tab.item.allocation.height) #dnd_window.show_all() #context.set_icon_widget(dnd_window, -2, -2) #context.set_icon_pixmap(tab.image.get_pixmap(), -2, -2) def do_drag_data_get(self, context, selection_data, info, timestamp): ''' :param context: the gdk.DragContext :param selection_data: a gtk.SelectionData object :param info: an integer ID for the drag :param timestamp: the time of the drag event The do_drag_data_get() signal handler is executed when a drag operation completes that copies data or when a drag drop occurs using the gdk.DRAG_PROTO_ROOTWIN protocol. The drag source executes this handler when the drag destination requests the data using the drag_get_data() method. This handler needs to fill selection_data with the data in the format specified by the target associated with info. For tab movement, here the tab is removed from the group. If the drop fails, the tab is restored in do_drag_failed(). For group movement, no special action is taken. ''' # Set some data so the DnD process continues selection_data.set(gdk.atom_intern(DRAG_TARGET_ITEM_LIST[0]), 8, '%d tabs' % len(self.dragcontext.dragged_object)) def do_drag_data_delete(self, context): ''' :param context: the gdk.DragContext The do_drag_data_delete() signal handler is executed when the drag completes a move operation and requires the source data to be deleted. The handler is responsible for deleting the data that has been dropped. For groups, the group is deleted, for tabs the group is destroyed of there are no more tabs left (see do_drag_data_get()). ''' # Let this be handled by the DockLayout pass def do_drag_end(self, context): ''' :param context: the gdk.DragContext The do_drag_end() signal handler is executed when the drag operation is completed. A typical reason to use this signal handler is to undo things done in the do_drag_begin() handler. In this case, items distached on drag-begin are connected to the group again, if not attached to some other widget already. ''' if self.dragcontext.dragging and self.dragcontext.dragged_object: for item in self.dragcontext.dragged_object: if not item.get_parent(): self.insert_item(item) self.dragcontext.reset() self.queue_resize() ############################################################################ # GtkContainer ############################################################################ def do_forall(self, internals, callback, data): # Internal widgets if internals: for tab in self._tabs: callback(tab.image, data) callback(tab.label, data) callback(tab.button, data) callback(self._list_button, data) callback(self._min_button, data) callback(self._max_button, data) # Docked items for tab in self._tabs: callback(tab.item, data) def do_add(self, widget): if widget not in (tab.item for tab in self._tabs): self._insert_item(widget) def do_remove(self, widget): self._remove_item(widget) def _remove_item(self, child): assert child in (tab.item for tab in self._tabs) item_num = self.item_num(child) tab = self._tabs[item_num] # We need this to reset the current item below old_tab_index = self._tabs.index(self._current_tab) # Remove tab item tab.item.disconnect(tab.item_title_handler) tab.item.disconnect(tab.item_title_tooltip_text_handler) tab.item.unparent() # Remove child widgets tab.image.unparent() tab.image.destroy() tab.label.unparent() tab.label.destroy() tab.button.unparent() tab.button.destroy() self._list_menu.remove(tab.menu_item) tab.menu_item.destroy() self._tabs.remove(tab) # Refresh ourselves current_tab_index = old_tab_index if item_num < current_tab_index: item_num = current_tab_index - 1 self.set_current_item(item_num) self.emit('item-removed', child) ############################################################################ # EtkDockGroup ############################################################################ items = property(lambda s: [t.item for t in s._tabs]) visible_items = property(lambda s: [t.item for t in s._visible_tabs]) def __contains__(self, item): return item in self.items def _update_visible_tabs(self): # Check what tabs we can show with the space we have been allocated. # Tabs on the far right of the current item tab get hidden first, # then tabs on the far left. if not self._tabs: del self._visible_tabs[:] else: # TODO: get previous tab position, use that to insert _current_tab if self._current_tab and self._current_tab not in self._visible_tabs: self._visible_tabs.append(self._current_tab) #if not set(self._visible_tabs) <= set(self._tabs): for tab in self._visible_tabs: if tab not in self._tabs: self._visible_tabs.remove(tab) # TODO: There are other places where something like this happens, # notably do_motion_notify_event. Consider some cleanup... if tab is self._current_tab: tab.button.show() else: tab.button.hide() available_width = self._available_width calculated_width = 0 for tab in self._visible_tabs: calculated_width += tab.area.width if calculated_width < available_width and len(self._visible_tabs) < len(self._tabs): tab_age = sorted(self._tabs, key=attrgetter('last_focused'), reverse=True) while tab_age and calculated_width < available_width: if tab_age[0] not in self._visible_tabs: calculated_width += tab_age[0].area.width self._visible_tabs.append(tab_age[0]) del tab_age[0] if calculated_width > available_width: tab_age = sorted(self._visible_tabs, key=attrgetter('last_focused')) while len(tab_age) > 1 and calculated_width > available_width: calculated_width -= tab_age[0].area.width self._visible_tabs.remove(tab_age[0]) del tab_age[0] # If the current item's tab is the only visible tab, # we need to recalculate its tab.area.width if len(self._visible_tabs) == 1: (iw, ih) = self._current_tab.image.get_child_requisition() (lw, lh) = self._current_tab.label.get_child_requisition() (bh, bw) = self._current_tab.button.get_child_requisition() normal = (self._frame_width + self._spacing + iw + self._spacing + lw + self._spacing + bw + self._spacing + self._frame_width) if available_width <= normal: self._current_tab.area.width = available_width else: self._current_tab.area.width = normal def set_tab_state(self, tab_state): ''' Define the tab state. Normally that will be ``gtk.STATE_SELECTED``, but a different state can be set if required. ''' self._tab_state = tab_state if self.allocation: self.queue_draw_area(0, 0, self.allocation.width, self.allocation.height) def get_tab_state(self): ''' Current state for drawing tabs. ''' return self._tab_state def get_tab_at_pos(self, x, y): ''' :param x: the x coordinate of the position :param y: the y coordinate of the position :returns: the item tab at the position specified by x and y or None The get_tab_at_pos() method returns the _DockGroupTab whose area contains the position specified by x and y or None if no _DockGroupTab area contains position. ''' for tab in self._visible_tabs: if (x, y) in tab: return tab else: return None def append_item(self, item): ''' :param item: a DockItem :returns: the index number of the item tab in the DockGroup The append_item() method appends a DockItem to the DockGroup using the DockItem specified by item. ''' return self.insert_item(item) def prepend_item(self, item): ''' :param item: a DockItem :returns: the index number of the item tab in the DockGroup The prepend_item() method prepends a DockItem to the DockGroup using the DockItem specified by item. ''' return self.insert_item(item, position=0) def insert_item(self, item, position=None, visible_position=None): ''' :param item: a DockItem :param position: the index (starting at 0) at which to insert the item, or None to append the item after all other item tabs. :param visible_position: the index at which the newly inserted item should be displayed. This index is usually different from the position and is normally only used when dropping items on the group. :returns: the index number of the item tab in the DockGroup The insert_item() method inserts a DockItem into the DockGroup at the location specified by position (0 is the first item). item is the DockItem to insert. If position is None the item is appended to the DockGroup. ''' index = self._insert_item(item, position, visible_position) return index def _insert_item(self, item, position=None, visible_position=None): assert isinstance(item, DockItem) assert self.item_num(item) is None if position is None or position < 0: position = len(self) # Create composite children for tab gtk.widget_push_composite_child() tab = _DockGroupTab() tab.image = item.get_image() tab.label = gtk.Label() tab.button = CompactButton(has_frame=False) tab.menu_item = gtk.ImageMenuItem() gtk.widget_pop_composite_child() # Configure child widgets for tab tab.item = item tab.item.set_parent(self) tab.item_title_handler = tab.item.connect('notify::title', self._on_item_title_changed, tab) tab.item_title_tooltip_text_handler = tab.item.connect('notify::title-tooltip-text', self._on_item_title_tooltip_text_changed, tab) tab.image.set_parent(self) tab.label.set_text(item.get_title()) tab.label.set_parent(self) tab.button.set_icon_name_normal('compact-close') tab.button.set_icon_name_prelight('compact-close-prelight') tab.button.set_parent(self) tab.button.connect('clicked', self._on_tab_button_clicked, item) tab.menu_item.set_image(item.get_image()) tab.menu_item.set_label(item.get_title()) tab.menu_item.connect('activate', self._on_list_menu_item_activated, tab) self._list_menu.append(tab.menu_item) tab.area = gdk.Rectangle() tab.last_focused = time() if self.flags() & gtk.REALIZED: tab.item.set_parent_window(self.window) tab.image.set_parent_window(self.window) tab.label.set_parent_window(self.window) tab.button.set_parent_window(self.window) self._tabs.insert(position, tab) self.emit('item-added', item) #TODO: get rid of this pronto! if visible_position is not None: self._visible_tabs.insert(visible_position, tab) item_num = self.item_num(item) self.set_current_item(item_num) return item_num def remove_item(self, item_num): ''' :param item_num: the index of an item tab, starting from 0. If None, the last item will be removed. The remove_item() method removes the item at the location specified by item_num. The value of item_num starts from 0. ''' if item_num is None: tab = self._tabs[-1] else: tab = self._tabs[item_num] item = tab.item self._remove_item(item) def item_num(self, item): ''' :param item: a DockItem :returns: the index of the item tab specified by item, or None if item is not in the DockGroup The item_num() method returns the index of the item tab which contains the DockItem specified by item or None if no item tab contains item. ''' for tab in self._tabs: if tab.item is item: return self._tabs.index(tab) return None def get_n_items(self): ''' :returns: the number of item tabs in the DockGroup. The get_n_items() method returns the number of item tabs in the DockGroup. ''' return len(self) def get_nth_item(self, item_num): ''' :param item_num: the index of an item tab in the DockGroup. :returns: a DockItem, or None if item_num is out of bounds. The get_nth_item() method returns the DockItem contained in the item tab with the index specified by item_num. If item_num is out of bounds for the item range of the DockGroup this method returns None. ''' if item_num >= 0 and item_num <= len(self._tabs) - 1: return self._tabs[item_num].item else: return None def get_current_item(self): ''' :returns: the index (starting from 0) of the current item tab in the DockGroup. If the DockGroup has no item tabs, then None will be returned. The get_current_item() method returns the index of the current item tab numbered from 0, or None if there are no item tabs. ''' if self._current_tab: return self._tabs.index(self._current_tab) else: return None def set_current_item(self, item_num): ''' :param item_num: the index of the item tab to switch to, starting from 0. If negative, the first item tab will be used. If greater than the number of item tabs in the DockGroup, the last item tab will be used. Switches to the item number specified by item_num. If item_num is negative the first item is selected. If greater than the number of items in the DockGroup, the last item is selected. ''' # Store a reference to the old current tab if self._current_tab and self._current_tab in self._tabs: old_tab = self._current_tab else: old_tab = None # Switch to the new current tab if self._tabs: if item_num < 0: current_tab_index = 0 elif item_num > len(self._tabs) - 1: current_tab_index = len(self._tabs) - 1 else: current_tab_index = item_num self._current_tab = self._tabs[current_tab_index] self._current_tab.last_focused = time() # Update properties on new current tab self._item_title_changed(self._current_tab) self._on_item_title_tooltip_text_changed(self._current_tab) self.emit('item-selected', self._current_tab.item) else: self._current_tab = None # Update properties on old current tab if old_tab: self._item_title_changed(old_tab) self._on_item_title_tooltip_text_changed(old_tab) # Refresh ourselves self.queue_resize() def next_item(self): ''' The next_item() method switches to the next item. Nothing happens if the current item is the last item. ''' ci = self.get_current_item() if not ci == len(self) - 1: self.set_current_item(ci + 1) def prev_item(self): ''' The prev_item() method switches to the previous item. Nothing happens if the current item is the first item. ''' ci = self.get_current_item() if not ci == 0: self.set_current_item(ci - 1) def reorder_item(self, item, position): ''' :param item: the DockItem widget to move :param position: the index of the item tab that item is to move to The reorder_item() method reorders the DockGroup items so that item appears in the location specified by position. If position is greater than or equal to the number of children in the list, item will be moved to the end of the list. If position is negative, item will be moved to the beginning of the list. ''' assert self.item_num(item) is not None if position < 0: position = 0 elif position > len(self) - 1: position = len(self) tab = self._tabs[self.item_num(item)] self._tabs.remove(tab) self._tabs.insert(position, tab) ############################################################################ # Property notification signal handlers ############################################################################ def _item_title_changed(self, tab): tab.label.set_text(tab.item.get_title()) self.queue_resize() if tab is self._current_tab: tab.menu_item.child.set_use_markup(True) tab.menu_item.child.set_markup('%s' % tab.item.get_title()) else: tab.menu_item.child.set_use_markup(False) tab.menu_item.child.set_markup(tab.item.get_title()) def _on_item_title_changed(self, item, pspec, tab): self._item_title_changed(tab) def _on_item_title_tooltip_text_changed(self, tab): tab.menu_item.set_tooltip_text(tab.item.get_title_tooltip_text()) ############################################################################ # Decoration area signal handlers ############################################################################ def _on_tab_button_clicked(self, button, item): item.close() def _on_list_button_clicked(self, button): def _menu_position(menu): wx, wy = self.window.get_origin() x = wx + button.allocation.x y = wy + button.allocation.y + button.allocation.height return (x, y, True) self._list_menu.show_all() self._list_menu.popup(parent_menu_shell=None, parent_menu_item=None, func=_menu_position, button=1, activate_time=0) def _on_list_menu_item_activated(self, menuitem, tab): self.set_current_item(self._tabs.index(tab)) def _on_min_button_clicked(self, button): #TODO: Hiding the dockgroup is not a good idea, as it will be 'minimized' # into a toolbar, managed by DockLayout. We'll probably want to emit # a signal instead... #self.hide() pass def _on_max_button_clicked(self, button): if button.get_icon_name_normal() == 'compact-maximize': button.set_icon_name_normal('compact-restore') button.set_tooltip_text(_('Restore')) else: button.set_icon_name_normal('compact-maximize') button.set_tooltip_text(_('Maximize')) etk.docking-0.2/lib/etk/docking/dockitem.py000066400000000000000000000150101175471717700207110ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . from __future__ import absolute_import from logging import getLogger import gobject import gtk import gtk.gdk as gdk class DockItem(gtk.Bin): __gtype_name__ = 'EtkDockItem' __gproperties__ = {'title': (gobject.TYPE_STRING, 'Title', 'The title for the DockItem.', '', gobject.PARAM_READWRITE), 'title-tooltip-text': (gobject.TYPE_STRING, 'Title tooltip text', 'The tooltip text for the title.', '', gobject.PARAM_READWRITE), 'icon-name': (gobject.TYPE_STRING, 'Icon name', 'The name of the icon from the icon theme.', '', gobject.PARAM_READWRITE), 'stock': (gobject.TYPE_STRING, 'Stock', 'Stock ID for a stock image to display.', '', gobject.PARAM_READWRITE), 'image': (gobject.TYPE_PYOBJECT, 'Image', 'The image constructed from the specified stock ID or icon-name. Default value is gtk.STOCK_MISSING_IMAGE.', gobject.PARAM_READABLE)} __gsignals__ = {'close': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())} def __init__(self, title='', title_tooltip_text='', icon_name=None, stock_id=None): gtk.Bin.__init__(self) self.set_flags(self.flags() | gtk.NO_WINDOW) self.set_redraw_on_allocate(False) # Initialize logging self.log = getLogger('%s.%s' % (self.__gtype_name__, hex(id(self)))) # Internal housekeeping self._icon_name = icon_name self._stock_id = stock_id self.set_title(title) self.set_title_tooltip_text(title_tooltip_text) self.set_icon_name(icon_name) self.set_stock(stock_id) ############################################################################ # GObject ############################################################################ def do_get_property(self, pspec): if pspec.name == 'title': return self.get_title() elif pspec.name == 'title-tooltip-text': return self.get_title_tooltip_text() elif pspec.name == 'icon-name': return self.get_icon_name() elif pspec.name == 'stock': return self.get_stock() elif pspec.name == 'image': return self.get_image() def do_set_property(self, pspec, value): if pspec.name == 'title': self.set_title(value) elif pspec.name == 'title-tooltip-text': self.set_title_tooltip_text(value) elif pspec.name == 'icon-name': self.set_icon_name(value) elif pspec.name == 'stock': self.set_stock(value) def get_title(self): return self._title def set_title(self, text): self._title = text self.notify('title') def get_title_tooltip_text(self): return self._title_tooltip_text def set_title_tooltip_text(self, text): self._title_tooltip_text = text self.notify('title-tooltip-text') def get_icon_name(self): return self._icon_name def set_icon_name(self, icon_name): self._icon_name = icon_name self.notify('icon-name') def get_stock(self): return self._stock_id def set_stock(self, stock_id): self._stock_id = stock_id self.notify('stock') def get_image(self): if self._icon_name: return gtk.image_new_from_icon_name(self._icon_name, gtk.ICON_SIZE_MENU) elif self._stock_id: return gtk.image_new_from_stock(self._stock_id, gtk.ICON_SIZE_MENU) else: return gtk.Image() title = property(get_title, set_title) title_tooltip_text = property(get_title_tooltip_text, set_title_tooltip_text) icon_name = property(get_icon_name, set_icon_name) stock = property(get_stock, set_stock) ############################################################################ # GtkWidget ############################################################################ def do_size_request(self, requisition): requisition.width = 0 requisition.height = 0 if self.child and self.child.flags() & gtk.VISIBLE: (requisition.width, requisition.height) = self.child.size_request() requisition.width += self.border_width * 2 requisition.height += self.border_width * 2 def do_size_allocate(self, allocation): self.allocation = allocation if self.child and self.child.flags() & gtk.VISIBLE: child_allocation = gdk.Rectangle() child_allocation.x = allocation.x + self.border_width child_allocation.y = allocation.y + self.border_width child_allocation.width = allocation.width - (2 * self.border_width) child_allocation.height = allocation.height - (2 * self.border_width) self.child.size_allocate(child_allocation) ############################################################################ # DockItem ############################################################################ def do_close(self): group = self.get_parent() if group: group.remove(self) def close(self): self.emit('close') etk.docking-0.2/lib/etk/docking/docklayout.py000066400000000000000000001036551175471717700213050ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . from __future__ import absolute_import import sys from collections import namedtuple from logging import getLogger from simplegeneric import generic import gobject import gtk import gtk.gdk as gdk import itertools from weakref import WeakKeyDictionary from .dnd import DRAG_TARGET_ITEM_LIST, Placeholder from .dockframe import DockFrame from .dockpaned import DockPaned from .dockgroup import DockGroup from .dockitem import DockItem from .docksettings import settings from .util import flatten # On OSX/X11 Utility windows are above all windows, # even if the app is not the active app. PROVIDE_FLOATING_WINDOW_HINTS = sys.platform != 'darwin' MAGIC_BORDER_SIZE = 10 DragData = namedtuple('DragData', 'drop_widget leave received') class DockLayout(gobject.GObject): """ Manage a dock layout. For this to work the toplevel widget in the layout hierarchy should be a DockFrame. The DockFrame is registered with the DockLayout. After that sophisticated drag-and-drop functionality is present. NB. When items are closed, the item-closed signal is emitted. The item is *not* destroyed, though. """ __gtype_name__ = 'EtkDockLayout' __gsignals__ = { 'item-closed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT, gobject.TYPE_OBJECT)), 'item-selected': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT, gobject.TYPE_OBJECT)), } def __init__(self): gobject.GObject.__init__(self) # Initialize logging self.log = getLogger('%s.%s' % (self.__gtype_name__, hex(id(self)))) self.frames = set() self._signal_handlers = {} # Map widget -> set([signals, ...]) self._focused_item = None self._focused_group = None self._focus_data = WeakKeyDictionary() # Map item -> last focused widget self._drag_data = None def add(self, frame): assert isinstance(frame, DockFrame) self.frames.add(frame) self.add_signal_handlers(frame) def remove(self, frame): self.remove_signal_handlers(frame) self.frames.remove(frame) def get_main_frames(self): """ Get the frames that are non-floating (the main frames). """ return (f for f in self.frames \ if not (isinstance(f.get_parent(), gtk.Window) \ and f.get_parent().get_transient_for()) ) def get_floating_frames(self): """ Get the floating frames. Floating frames have a gtk.Window as parent that is transient for some other window. """ return (f for f in self.frames \ if isinstance(f.get_parent(), gtk.Window) \ and f.get_parent().get_transient_for() ) def get_widgets(self, name): """ Get a set of widgets based on their name. """ return filter(lambda w: w.get_name() == name, itertools.chain.from_iterable(flatten(frame) for frame in self.frames)) def _get_signals(self, widget): """ Get a list of signals to be registered for a specific widget. """ if isinstance(widget, DockPaned): signals = (('item-added', self.on_widget_add), ('item-removed', self.on_widget_remove)) elif isinstance(widget, DockGroup): signals = (('item-added', self.on_widget_add), ('item-removed', self.on_widget_remove), ('item-selected', self.on_dockgroup_item_selected)) elif isinstance(widget, DockItem): signals = (('close', self.on_dockitem_close),) elif isinstance(widget, gtk.Container): signals = (('add', self.on_widget_add), ('remove', self.on_widget_remove)) else: signals = () return signals + (('drag-motion', self.on_widget_drag_motion), ('drag-leave', self.on_widget_drag_leave), ('drag-drop', self.on_widget_drag_drop), ('drag-data-received', self.on_widget_drag_data_received), ('drag-end', self.on_widget_drag_end), ('drag-failed', self.on_widget_drag_failed), ('notify::is-focus', self.on_widget_is_focus)) def add_signal_handlers(self, widget): """ Set up signal handlers for layout and child widgets. Also group state is changed from selected to prelight, in order to have one focused widget. """ if self._signal_handlers.get(widget): return signals = set() drag_dest = widget.drag_dest_get_target_list() if not drag_dest: widget.drag_dest_set(gtk.DEST_DEFAULT_MOTION, [DRAG_TARGET_ITEM_LIST], gdk.ACTION_MOVE) elif DRAG_TARGET_ITEM_LIST not in drag_dest: widget.drag_dest_set_target_list(drag_dest + [DRAG_TARGET_ITEM_LIST]) # Use instance methods here, so layout can do additional bookkeeping for name, callback in self._get_signals(widget): try: signals.add(widget.connect(name, callback)) except TypeError, e: self.log.debug(e) self._signal_handlers[widget] = signals if isinstance(widget, gtk.Container): widget.foreach(self.add_signal_handlers) # Ensure SELECTED state is only for the selected item if isinstance(widget, DockGroup): widget.set_tab_state(gtk.STATE_PRELIGHT) def remove_signal_handlers(self, widget): """ Remove signal handlers. """ try: signals = self._signal_handlers[widget] except KeyError: pass # No signals else: for s in signals: widget.disconnect(s) del self._signal_handlers[widget] # TODO: widget.drag_dest_set_target_list(drag_dest - [DRAG_TARGET_ITEM_LIST])?? if isinstance(widget, gtk.Container): widget.foreach(self.remove_signal_handlers) def update_floating_window_title(self, widget): frame = widget.get_ancestor(DockFrame) if frame in self.get_floating_frames(): frame.get_toplevel().set_title( ', '.join( map(lambda w: w.title, filter(lambda w: isinstance(w, DockItem), flatten(frame))))) def do_item_closed(self, group, item): """ If an item is closed, perform maintenance cleanup. """ if settings[item].auto_remove: cleanup(group, self) def do_item_selected(self, group, item): # Use this callback to grey out the selection on all but the active selection? self._focused_item = item if not (group is self._focused_group and group.get_tab_state() != gtk.STATE_PRELIGHT): if self._focused_group: self._focused_group.set_tab_state(gtk.STATE_PRELIGHT) self._focused_group = group group.set_tab_state(gtk.STATE_SELECTED) def on_widget_add(self, container, widget): """ Deal with new elements being added to the layout or it's children. """ if isinstance(widget, gtk.Container): self.add_signal_handlers(widget) self.update_floating_window_title(container) def on_widget_remove(self, container, widget): """ Remove signals from containers and subcontainers. """ if isinstance(widget, gtk.Container): self.remove_signal_handlers(widget) self.update_floating_window_title(container) def on_widget_drag_motion(self, widget, context, x, y, timestamp): if DRAG_TARGET_ITEM_LIST[0] in context.targets: context.docklayout = self drag_data = drag_motion(widget, context, x, y, timestamp) old_drop_widget = self._drag_data and self._drag_data.drop_widget new_drop_widget = drag_data and drag_data.drop_widget if new_drop_widget is not old_drop_widget: self.on_widget_drag_leave(widget, context, timestamp) self._drag_data = drag_data def on_widget_drag_leave(self, widget, context, timestamp): # Note: when dropping, drag-leave is invoked before drag-drop if DRAG_TARGET_ITEM_LIST[0] in context.targets: drag_data = self._drag_data if drag_data and drag_data.leave: self.log.debug('on widget drag leave %s' % drag_data.leave) drag_data.leave(drag_data.drop_widget) def on_widget_drag_drop(self, widget, context, x, y, timestamp): self.log.debug('drag_drop %s %s %s %s', context, x, y, timestamp) if DRAG_TARGET_ITEM_LIST[0] in context.targets: drag_data = self._drag_data if drag_data and drag_data.drop_widget: target = gdk.atom_intern(DRAG_TARGET_ITEM_LIST[0]) drag_data.drop_widget.drag_get_data(context, target, timestamp) return True # act as if drag failed: source = context.get_source_widget() source.emit('drag-failed', context, 1) cleanup(source, self) return False def on_widget_drag_data_received(self, widget, context, x, y, selection_data, info, timestamp): ''' Execute the received handler using the received handler retrieved in the drag_drop event handler. ''' self.log.debug('drag_data_received %s, %s, %s, %s, %s, %s' % (context, x, y, selection_data, info, timestamp)) if DRAG_TARGET_ITEM_LIST[0] in context.targets: drag_data = self._drag_data assert drag_data.received try: drag_data.received(selection_data, info) finally: self._drag_data = None def on_widget_drag_end(self, widget, context): if DRAG_TARGET_ITEM_LIST[0] in context.targets: context.docklayout = self return drag_end(widget, context) def on_widget_drag_failed(self, widget, context, result): if DRAG_TARGET_ITEM_LIST[0] in context.targets: context.docklayout = self return drag_failed(widget, context, result) def on_widget_is_focus(self, widget, pspec): """ The input focus moved to another widget. """ if isinstance(widget, DockItem): item = widget else: item = widget.get_ancestor(DockItem) if item: self._focus_data[item] = widget if item is not self._focused_item: group = item.get_parent() self.emit('item-selected', group, item) def on_dockitem_close(self, item): group = item.get_parent() self.emit('item-closed', group, item) cleanup(group, self) def on_dockgroup_item_selected(self, group, item): """ An item is selected by clicking on a tab. """ focus_child = self._focus_data.get(item) if focus_child: # item-selected is emited by is-focus handler focus_child.set_property('has-focus', True) self.emit('item-selected', group, item) ################################################################################ # Placement ################################################################################ def add_new_group_left(widget, new_group): add_new_group_before(widget, new_group, gtk.ORIENTATION_HORIZONTAL) def add_new_group_right(widget, new_group): add_new_group_after(widget, new_group, gtk.ORIENTATION_HORIZONTAL) def add_new_group_above(widget, new_group): add_new_group_before(widget, new_group, gtk.ORIENTATION_VERTICAL) def add_new_group_below(widget, new_group): add_new_group_after(widget, new_group, gtk.ORIENTATION_VERTICAL) def add_new_group_before(widget, new_group, orientation): """ Create a new DockGroup and place it before `widget`. The DockPaned that will hold both groups will have the defined orientation. """ position = widget.get_parent().get_children().index(widget) add_new_group(widget, new_group, orientation, position) return new_group def add_new_group_after(widget, new_group, orientation): """ Create a new DockGroup and place it after `widget`. The DockPaned that will hold both groups will have the defined orientation. """ position = widget.get_parent().get_children().index(widget) + 1 add_new_group(widget, new_group, orientation, position) return new_group def add_new_group(widget, new_group, orientation, position): """ Place a `new_group` next to `widget` in a """ # Parameters: orientation, position (left/top: None, right/bottom: 1), # current_child, (paned) current_position, (paned) weight parent = widget.get_parent() assert parent try: current_position = parent.item_num(widget) weight = parent.child_get_property(widget, 'weight') except AttributeError: current_position = None if isinstance(parent, DockPaned) and orientation == parent.get_orientation(): new_paned = parent else: new_paned = new(DockPaned) new_paned.set_orientation(orientation) # Current_child will always be a DockGroup or DockPaned with opposite orientation parent.remove(widget) if current_position is not None: # used for DockPaned parent.insert_item(new_paned, position=current_position, weight=weight) else: # Used for DockFrame parent.add(new_paned) new_paned.insert_item(widget, weight=0.5) widget.queue_resize() #new_group = new(DockGroup, source, context.docklayout) new_paned.insert_item(new_group, position, weight=0.5) new_paned.show() new_group.show() return new_group def _window_delete_handler(window, event): map(lambda i: i.close(), filter(lambda i: isinstance(i, DockItem), flatten(window))) return False def add_new_group_floating(new_group, layout, size=None, pos=None): window = gtk.Window(gtk.WINDOW_TOPLEVEL) if pos: window.move(*pos) window.set_resizable(True) window.set_skip_taskbar_hint(True) if PROVIDE_FLOATING_WINDOW_HINTS: window.set_type_hint(gdk.WINDOW_TYPE_HINT_UTILITY) window.set_transient_for(layout.get_main_frames().next().get_toplevel()) if size: window.set_size_request(*size) window.connect('delete-event', _window_delete_handler) frame = new(DockFrame) window.add(frame) frame.add(new_group) window.show() frame.show() new_group.show() layout.add(frame) return frame ################################################################################ # Drag and Drop behaviour ################################################################################ def _propagate_to_parent(func, widget, context, x, y, timestamp): ''' Common function to propagate calls to a parent widget. ''' parent = widget.get_parent() if parent: # Should not use get_pointer as it's not testable. px, py = widget.translate_coordinates(parent, x, y) return func(parent, context, px, py, timestamp) else: return None def with_magic_borders(func): ''' decorator for handlers that have sensitive borders, as items may be dropped on the parent item as well. ''' def func_with_magic_borders(widget, context, x, y, timestamp): # Always ensure we check the parent class: handled = _propagate_to_parent(magic_borders, widget, context, x, y, timestamp) return handled or func(widget, context, x, y, timestamp) func_with_magic_borders.__doc__ = func.__doc__ return func_with_magic_borders @generic def magic_borders(widget, context, x, y, timestamp): ''' :returns: DragData if the parent widget handled the event This method is used to find out if (in case an item is dragged on the border of a widget, the parent is eager to take that event instead. This, for example, can be used to place items above or below each other in not-yet existing paned sections. ''' pass @generic def drag_motion(widget, context, x, y, timestamp): ''' :param context: the gdk.DragContext :param x: the X position of the drop :param y: the Y position of the drop :param timestamp: the time of the drag event :returns: a tuple (widget, callback) to be called when leaving the item (drag_leave event) if the cursor is in a drop zone. The do_drag_motion() signal handler is executed when the drag operation moves over a drop target widget. The handler must determine if the cursor position is in a drop zone or not. If it is not in a drop zone, it should return False and no further processing is necessary. Otherwise, the handler should return True. In this case, the handler is responsible for providing the necessary information for displaying feedback to the user, by calling the gdk.DragContext.drag_status() method. If the decision to accept or reject the drop can't be made based solely on the cursor position and the type of the data, the handler may inspect the dragged data by calling the drag_get_data() method and defer the gdk.DragContext.drag_status() method call to the do_drag_data_received() signal handler. Note:: There is no do_drag_enter() signal handler. The drag receiver has to keep track of any do_drag_motion() signals received since the last do_drag_leave() signal. The first do_drag_motion() signal received after a do_drag_leave() signal should be treated as an "enter" signal. Upon an "enter", the handler will typically highlight the drop site with the drag_highlight() method. drag_data_received(widget, context, x, y, selection_data, info, timestamp): The do_drag_data_received() signal handler is executed when the drag destination receives the data from the drag operation. If the data was received in order to determine whether the drop will be accepted, the handler is expected to call the gdk.DragContext.drag_status() method and not finish the drag. If the data was received in response to a do_drag_drop() signal (and this is the last target to be received), the handler for this signal is expected to process the received data and then call the gdk.DragContext.finish() method, setting the success parameter to True if the data was processed successfully. ''' return _propagate_to_parent(drag_motion, widget, context, x, y, timestamp) @generic def cleanup(widget, layout): ''' :param widget: widget that may require cleanup. Perform cleanup action for a widget. For example after a drag-and-drop operation or when a dock-item is closed. ''' parent = widget.get_parent() return parent and cleanup(parent, layout) @generic def drag_end(widget, context): ''' :param context: the gdk.DragContext The do_drag_end() signal handler is executed when the drag operation is completed. A typical reason to use this signal handler is to undo things done in the do_drag_begin() handler. ''' parent = widget.get_parent() return parent and drag_end(parent, context) @generic def drag_failed(widget, context, result): ''' :param context: the gdk.DragContext :param result: the result of the drag operation :returns: True if the failed drag operation has been already handled. The do_drag_failed() signal handler is executed on the drag source when a drag has failed. The handler may hook custom code to handle a failed DND operation based on the type of error. It returns True if the failure has been already handled (not showing the default "drag operation failed" animation), otherwise it returns False. ''' parent = widget.get_parent() return parent and drag_failed(parent, context, result) ################################################################################ # DockGroup ################################################################################ def new(class_, old=None, layout=None): ''' Create a new Widget. Set the group name if required. ''' new = class_() if old and settings[old].inherit_settings: new.set_name(old.get_name()) return new def dock_group_expose_highlight(self, event): try: tab = self._visible_tabs[self._drop_tab_index] except TypeError: a = event.area else: if tab is self._current_tab: a = event.area else: a = tab.area cr = self.window.cairo_create() cr.set_source_rgb(0, 0, 0) cr.set_line_width(1.0) cr.rectangle(a.x + 0.5, a.y + 0.5, a.width - 1, a.height - 1) cr.stroke() def dock_group_highlight(self): if not hasattr(self, '_expose_event_id'): self.log.debug('attaching expose event') self._expose_event_id = self.connect_after('expose-event', dock_group_expose_highlight) self.queue_draw() def dock_unhighlight(self): self.queue_draw() try: self.disconnect(self._expose_event_id) del self._expose_event_id except AttributeError, e: self.log.debug(e, exc_info=True) @drag_motion.when_type(DockGroup) @with_magic_borders def dock_group_drag_motion(self, context, x, y, timestamp): self.log.debug('dock_group_drag_motion: %s, %s, %s, %s' % (context, x, y, timestamp)) # Insert the dragged tab before the tab under (x, y) drop_tab = self.get_tab_at_pos(x, y) self.log.debug('drop tab at (%d, %d) is %s', x, y, drop_tab) if drop_tab: self._drop_tab_index = self._visible_tabs.index(drop_tab) elif self._tabs: self._drop_tab_index = self._visible_tabs.index(self._current_tab) else: self._drop_tab_index = None dock_group_highlight(self) def dock_group_drag_data_received(selection_data, info): self.log.debug('%s, %s, %s, %s, %s, %s' % (context, x, y, selection_data, info, timestamp)) source = context.get_source_widget() assert source self.log.debug('Recieving item %s' % source.dragcontext.dragged_object) for item in reversed(source.dragcontext.dragged_object): self.insert_item(item, visible_position=self._drop_tab_index) context.finish(True, True, timestamp) # success, delete, time return DragData(self, dock_unhighlight, dock_group_drag_data_received) @cleanup.when_type(DockGroup) def dock_group_cleanup(self, layout): if not self.items and settings[self].auto_remove: parent = self.get_parent() self.log.debug('removing empty group') self.destroy() return parent and cleanup(parent, layout) # Attached to drag *source* @drag_end.when_type(DockGroup) def dock_group_drag_end(self, context): cleanup(self, context.docklayout) # Attached to drag *source* @drag_failed.when_type(DockGroup) def dock_group_drag_failed(self, context, result): global settings self.log.debug('%s, %s' % (context, result)) if result == 1 and settings[self].can_float: #gtk.DRAG_RESULT_NO_TARGET if reduce(lambda a, b: a or b, map(lambda i: settings[i].float_retain_size, self.dragcontext.dragged_object)): size = (self.allocation.width, self.allocation.height) else: size = None layout = context.docklayout new_group = new(DockGroup, self, layout) add_new_group_floating(new_group, layout, size, self.get_pointer()) for item in self.dragcontext.dragged_object: new_group.append_item(item) else: for item in self.dragcontext.dragged_object: self.insert_item(item, position=self._dragged_tab_index) return True ################################################################################ # DockPaned ################################################################################ def dock_paned_expose_highlight(self, event): try: handle = self._handles[self._drop_handle_index] except (AttributeError, IndexError, TypeError), e: self.log.error(e, exc_info=True) return else: a = handle.area cr = self.window.cairo_create() cr.set_source_rgb(0, 0, 0) cr.set_line_width(1.0) cr.rectangle(a.x + 0.5, a.y + 0.5, a.width - 1, a.height - 1) cr.stroke() def dock_paned_highlight(self): if not hasattr(self, '_expose_event_id'): self.log.debug('attaching expose event') self._expose_event_id = self.connect_after('expose-event', dock_paned_expose_highlight) self.queue_draw() @drag_motion.when_type(DockPaned) @with_magic_borders def dock_paned_drag_motion(self, context, x, y, timestamp): self.log.debug('dock_paned_drag_motion: %s, %s, %s, %s' % (context, x, y, timestamp)) handle = self._get_handle_at_pos(x, y) self.log.debug('handle at pos (%d, %d) is %s', x, y, handle) if handle: self._drop_handle_index = self._handles.index(handle) else: self._drop_handle_index = None dock_paned_highlight(self) def dock_paned_drag_data_received(selection_data, info): self.log.debug('%s, %s, %s, %s, %s, %s' % (context, x, y, selection_data, info, timestamp)) source = context.get_source_widget() self.log.debug('Recieving item %s' % source.dragcontext.dragged_object) # If on handle: create new DockGroup and add items new_group = new(DockGroup, source, context.docklayout) self.insert_item(new_group, self._drop_handle_index + 1) new_group.show() for item in source.dragcontext.dragged_object: new_group.insert_item(item) context.finish(True, True, timestamp) # success, delete, time return DragData(self, dock_unhighlight, dock_paned_drag_data_received) @cleanup.when_type(DockPaned) def dock_paned_cleanup(self, layout): if not len(self): parent = self.get_parent() self.log.debug('removing empty paned') self.destroy() return parent and cleanup(parent, layout) if len(self) == 1: parent = self.get_parent() child = self[0] if isinstance(parent, DockPaned): position = [c for c in parent].index(self) weight = parent.child_get_property(self, 'weight') self.remove(child) parent.remove(self) parent.insert_item(child, position=position, weight=weight) assert child.get_parent() is parent, (child.get_parent(), parent) else: #does not work: child.unparent() self.remove(child) parent.remove(self) parent.add(child) self.log.debug('removing empty paned - moved child to parent first') self.destroy() # Attached to drag *source* @drag_end.when_type(DockPaned) def dock_paned_drag_end(self, context): cleanup(self, context.docklayout) def dock_paned_magic_borders_leave(self): a = self.get_ancestor(DockFrame) if a: a.set_placeholder(None) @magic_borders.when_type(DockPaned) def dock_paned_magic_borders(self, context, x, y, timestamp): a = self.allocation def create_received(create): current_group = self.get_item_at_pos(x, y) assert current_group ca = current_group.allocation if x < MAGIC_BORDER_SIZE: allocation = (ca.x, ca.y, MAGIC_BORDER_SIZE, ca.height) elif a.width - x < MAGIC_BORDER_SIZE: allocation = (a.width - MAGIC_BORDER_SIZE, ca.y, MAGIC_BORDER_SIZE, ca.height) elif y < MAGIC_BORDER_SIZE: allocation = (ca.x, ca.y, ca.width, MAGIC_BORDER_SIZE) elif a.height - y < MAGIC_BORDER_SIZE: allocation = (ca.x, a.height - MAGIC_BORDER_SIZE, ca.width, MAGIC_BORDER_SIZE) frame = self.get_ancestor(DockFrame) fx, fy = self.translate_coordinates(frame, allocation[0], allocation[1]) fa = frame.allocation allocation = (fa.x + fx, fa.y + fy, allocation[2], allocation[3]) placeholder = Placeholder() frame.set_placeholder(placeholder) placeholder.size_allocate(allocation) placeholder.show() if create: if self.get_orientation() == gtk.ORIENTATION_HORIZONTAL: orientation = gtk.ORIENTATION_VERTICAL else: orientation = gtk.ORIENTATION_HORIZONTAL weight = self.child_get_property(current_group, 'weight') if min(x, y) < MAGIC_BORDER_SIZE: position = 0 else: position = None def new_paned_and_group_receiver(selection_data, info): source = context.get_source_widget() assert source new_group = new(DockGroup, source, context.docklayout) add_new_group(current_group, new_group, orientation, position) self.log.debug('Recieving item %s' % source.dragcontext.dragged_object) for item in source.dragcontext.dragged_object: new_group.append_item(item) context.finish(True, True, timestamp) # success, delete, time return new_paned_and_group_receiver else: def add_group_receiver(selection_data, info): source = context.get_source_widget() assert source new_group = new(DockGroup, source, context.docklayout) if min(x, y) < MAGIC_BORDER_SIZE: position = 0 else: position = None self.insert_item(new_group, position) new_group.show() for item in source.dragcontext.dragged_object: new_group.append_item(item) context.finish(True, True, timestamp) # success, delete, time return add_group_receiver if abs(min(y, a.height - y)) < MAGIC_BORDER_SIZE: received = create_received(self.get_orientation() == gtk.ORIENTATION_HORIZONTAL) return DragData(self, dock_paned_magic_borders_leave, received) elif abs(min(x, a.width - x)) < MAGIC_BORDER_SIZE: received = create_received(self.get_orientation() == gtk.ORIENTATION_VERTICAL) return DragData(self, dock_paned_magic_borders_leave, received) return None ################################################################################ # DockFrame ################################################################################ @cleanup.when_type(DockFrame) def dock_frame_cleanup(self, layout): if not self.get_children(): parent = self.get_parent() layout.remove(self) self.destroy() try: if parent.get_transient_for(): parent.destroy() except AttributeError: self.log.error(' Not a transient top level widget') @drag_end.when_type(DockFrame) def dock_frame_drag_end(self, context): cleanup(self, context.docklayout) def dock_frame_magic_borders_leave(self): self.set_placeholder(None) @drag_motion.when_type(DockFrame) @magic_borders.when_type(DockFrame) def dock_frame_magic_borders(self, context, x, y, timestamp): ''' Deal with drop events that are not accepted by any Paned. Provided the outermost n pixels are not used by the item itself, but propagate the event to the parent widget. This means that sometimes the event ends up in the "catch-all", the DockFrame. The Frame should make sure a new DockPaned is created with the proper orientation and whatever's needed. ''' a = self.allocation border = self.border_width if x - border < MAGIC_BORDER_SIZE: orientation = gtk.ORIENTATION_HORIZONTAL allocation = (a.x + border, a.y + border, MAGIC_BORDER_SIZE, a.height - border*2) elif a.width - x - border < MAGIC_BORDER_SIZE: orientation = gtk.ORIENTATION_HORIZONTAL allocation = (a.x + a.width - MAGIC_BORDER_SIZE - border, a.y + border, MAGIC_BORDER_SIZE, a.height - border*2) elif y - border < MAGIC_BORDER_SIZE: orientation = gtk.ORIENTATION_VERTICAL allocation = (a.x + border, a.y + border, a.width - border*2, MAGIC_BORDER_SIZE) elif a.height - y - border < MAGIC_BORDER_SIZE: orientation = gtk.ORIENTATION_VERTICAL allocation = (a.x + border, a.y + a.height - MAGIC_BORDER_SIZE - border, a.width - border*2, MAGIC_BORDER_SIZE) else: return None placeholder = Placeholder() self.set_placeholder(placeholder) placeholder.size_allocate(allocation) placeholder.show() current_child = self.get_children()[0] assert current_child if min(x - border, y - border) < MAGIC_BORDER_SIZE: position = 0 else: position = None def new_paned_and_group_receiver(selection_data, info): source = context.get_source_widget() assert source new_group = new(DockGroup, source, context.docklayout) add_new_group(current_child, new_group, orientation, position) self.log.debug('Recieving item %s' % source.dragcontext.dragged_object) for item in source.dragcontext.dragged_object: new_group.append_item(item) context.finish(True, True, timestamp) # success, delete, time return DragData(self, dock_frame_magic_borders_leave, new_paned_and_group_receiver) etk.docking-0.2/lib/etk/docking/dockpaned.py000066400000000000000000001004321175471717700210450ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . from __future__ import absolute_import from logging import getLogger import gobject import gtk import gtk.gdk as gdk from .dnd import DockDragContext from .util import rect_overlaps from .docksettings import settings # The weight we allocate to a newly added item if we can't come up with anything else FALLBACK_WEIGHT = 0.2 class _DockPanedHandle(object): ''' Private object storing information about a handle. ''' __slots__ = ['area'] # area, used for hit testing (gdk.Rectangle) def __init__(self): self.area = gdk.Rectangle() def __contains__(self, pos): return rect_overlaps(self.area, *pos) class _DockPanedItem(object): ''' Private object storing information about a child widget. ''' __slots__ = ['child', # child widget 'weight', # relative weight [0..1] 'weight_request', # requested weight, processed in size_allocate() 'min_size'] # minimum relative weight def __init__(self): self.child = None self.weight = None self.weight_request = None self.min_size = None def __contains__(self, pos): return rect_overlaps(self.child.allocation, *pos) class DockPaned(gtk.Container): ''' The :class:`etk.DockPaned` widget is a container widget with multiple panes arranged either horizontally or vertically, depending on the value of the orientation property. Child widgets are added to the panes of the widget with the :meth:`append_item`, :meth:`prepend_item` or :meth:`insert_item` methods. A dockpaned widget draws a separator between it's child widgets and a small handle that the user can drag to adjust the division. It does not draw any relief around the children or around the separator. ''' __gtype_name__ = 'EtkDockPaned' __gproperties__ = \ {'handle-size': (gobject.TYPE_UINT, 'handle size', 'handle size', 0, gobject.G_MAXINT, 4, gobject.PARAM_READWRITE), 'orientation': (gobject.TYPE_UINT, 'handle size', 'handle size', 0, 1, 0, gobject.PARAM_READWRITE)} __gchild_properties__ = \ {'weight': (gobject.TYPE_FLOAT, 'item weight', 'item weight', 0, # min 1, # max .2, # default gobject.PARAM_READWRITE)} __gsignals__ = {'item-added': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,)), 'item-removed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_OBJECT,))} def __init__(self): gtk.Container.__init__(self) # Initialize logging self.log = getLogger('%s.%s' % (self.__gtype_name__, hex(id(self)))) # Initialize attributes self._items = [] self._handles = [] self._hcursor = None self._vcursor = None # Initialize handle dragging (not to be confused with DnD...) self._dragcontext = DockDragContext() # Initialize properties self.set_handle_size(4) self.set_orientation(gtk.ORIENTATION_HORIZONTAL) ############################################################################ # Private ############################################################################ def _children(self): ''' :returns: an iterator that returns the items and handles in the dockpaned. The :meth:`_children` method returns an iterator that returns the items and handles in the dockpaned in the order they are drawn. This corresponds to ``[_items[0], _handles[0], _items[1], _handles[1], _items[2], ...]`` ''' index = 0 switch = True for x in range(len(self._items) + len(self._handles)): if switch: yield self._items[index] switch = False else: yield self._handles[index] switch = True index += 1 def _insert_item(self, child, position=None, weight=None): ''' :param child: a :class:`gtk.Widget` to use as the contents of the item. :param position: the index (starting at 0) at which to insert the item, negative or :const:`None` to append the item after all other items. :param weight: The relative amount of space the child should get. No guarantees. :returns: the index number of the item in the dockpaned. The :meth:`_insert_item` method is the private implementation behind the :meth:`add`, :meth:`insert_item`, :meth:`append_item` and :meth:`prepend_item` methods. ''' assert isinstance(child, gtk.Widget) assert self.item_num(child) is None assert not child.get_parent() if position is None or position < 0: position = len(self) # Create new _DockPanedItem item = _DockPanedItem() item.child = child item.child.set_parent(self) if self.flags() & gtk.REALIZED: item.child.set_parent_window(self.window) self._items.insert(position, item) # Create a _DockPanedHandle if needed if len(self) > 1: self._insert_handle(position - 1) assert len(self._items) == len(self._handles) + 1 if weight: assert 0.0 <= weight <= 1.0 item.weight_request = weight elif len(self._items) == 1: # First item always gets 100% allocated item.weight = 1.0 elif self.allocation and child.allocation: size = self._effective_size(self.allocation) - self._handle_size if self._orientation == gtk.ORIENTATION_HORIZONTAL: child_size = child.size_request()[0] else: child_size = child.size_request()[1] if size > 0 and child_size > 0: item.weight_request = float(child_size) / size else: item.weight_request = FALLBACK_WEIGHT else: item.weight_request = FALLBACK_WEIGHT self.queue_resize() self.emit('item-added', child) return self.item_num(child) def _remove_item(self, child): ''' :param child: a :class:`gtk.Widget` to use as the contents of the item. The :meth:`_remove_item` method is the private implementation behind the :meth:`remove` and :meth:`remove_item` methods. ''' item_num = self.item_num(child) assert item_num is not None # Remove the DockPanedItem from the list child.unparent() del self._items[item_num] # If there are still items/handles in the list, we'd like to # remove a handle... if self._items: self._remove_handle(item_num) assert len(self._items) == len(self._handles) + 1 or \ len(self._items) == len(self._handles) == 0 self.queue_resize() self.emit('item-removed', child) def _insert_handle(self, position): ''' :param position: the index (starting at 0) at which to insert a handle. The :meth:`_insert_handle` inserts a handle at the index specified by `position`. ''' handle = _DockPanedHandle() self._handles.insert(position, handle) def _remove_handle(self, position): ''' :param position: the index (starting at 0) at which to remove a handle. The :meth:`_remove_handle` removes a handle at the index specified by `position`. ''' try: # Remove the DockPanedHandle that used to be located after # the DockPanedItem we just removed del self._handles[position] except IndexError: # Well, seems we removed the last DockPanedItem from the # list, so we'll remove the DockPanedHandle that used to # be located before the DockPanedItem we just removed del self._handles[position - 1] def _get_n_handles(self): ''' :returns: the number of handles in the dockpaned. The :meth:`_get_n_handles` method returns the number of handles in the dockpaned. ''' return len(self._handles) def _get_handle_at_pos(self, x, y): ''' :param x: the x coordinate of the position. :param y: the y coordinate of the position. :returns: the handle at the position specified by x and y or :const:`None`. The :meth:`_get_handle_at_pos` method returns the handle whose area contains the position specified by `x` and `y` or :const:`None` if no handle is at that position. ''' for handle in self._handles: if (x, y) in handle: return handle else: return None def _item_for_child(self, child): for item in self._items: if item.child is child: return item raise ValueError('child widget %s not in paned' % child) def _size(self, allocation): ''' Get the size (width or height) required in calculations, depending on the Paned's orientation. ''' if self._orientation == gtk.ORIENTATION_HORIZONTAL: return allocation.width else: return allocation.height def _effective_size(self, allocation): ''' Find the size we can actually spend on items. ''' return self._size(allocation) - self._get_n_handles() * self._handle_size def _redistribute_size(self, delta_size, enlarge, shrink): ''' :param delta_size: the size we want to add the item specified by `enlarge`. :param enlarge: the `_DockPanedItem` we want to add size to. :param shrink: a list of `_DockPanedItem`'s where the size requested by `delta_size` will be removed. The :meth:`_redistribute_size` method subtracts size from the items specified by `shrink` and adds the freed size to the item specified by `enlarge`. This is done until `delta_size` reaches 0, or there's no more items left in `shrink`. ''' # Distribute delta_size amongst the items in shrink size = self._effective_size(self.allocation) enlarge_alloc = enlarge.child.allocation for item in shrink: available_size = self._size(item.child.allocation) - item.min_size # Check if we can shrink (respecting the child's size_request) if available_size > 0: a = item.child.allocation # Can we adjust the whole delta or not? if delta_size > available_size: adjustment = available_size else: adjustment = delta_size enlarge.weight_request = float(self._size(enlarge_alloc) + adjustment) / size item.weight_request = float(self._size(a) - adjustment) / size delta_size -= adjustment if delta_size == 0: break self.queue_resize() def _redistribute_weight(self, size): ''' Divide the space available over the items. Items that have explicitly been assigned a weight should get it assigned, as long as the max weight (1.0) is not exeeded. The general scheme is as follows: * figure out which items requested a new weight * ensure sum(min_sizes) fits in the allocated size * ensure the requested weights do not make items go smaller than min_size * divide remaining space over other items. ''' items = self._items size = float(size) # Scale non-expandable items, so their size does not change effectively if self.allocation: f = self._effective_size(self.allocation) / size for i in self._items: #if i.weight and not i.expand and not i.weight_request: if i.weight and not settings[i.child].expand and not i.weight_request: i.weight_request = i.weight * f requested_items = [ i for i in items if i.weight_request ] other_items = [ i for i in items if not i.weight_request ] # Ensure the min_sizes do not exceed the overall size min_size = sum(i.min_size for i in items) if min_size > size: sf = size / min_size self.log.warn('Size scaling required (factor=%f)' % sf) else: sf = 1.0 # First ensure all remaining items can be placed for i, w in zip(other_items, fair_scale(1.0 - sum(i.weight_request for i in requested_items), \ [(i.weight, sf * i.min_size / size) for i in other_items])): i.weight = w # Divide what's left over the requesting items for i, w in zip(requested_items, fair_scale(1.0 - sum(i.weight for i in other_items), \ [(i.weight_request, sf * i.min_size / size) for i in requested_items])): i.weight = w i.weight_request = None ############################################################################ def __getitem__(self, index): return self._items[index].child def __delitem__(self, index): child = self[index] self._remove_item(child) def __len__(self): return len(self._items) def __contains__(self, child): for i in self._items: if i.child is child: return True return False def __iter__(self): for i in self._items: yield i.child ############################################################################ # GObject ############################################################################ def do_get_property(self, pspec): if pspec.name == 'handle-size': return self.get_handle_size() elif pspec.name == 'orientation': return self.get_orientation() def do_set_property(self, pspec, value): if pspec.name == 'handle-size': self.set_handle_size(value) elif pspec.name == 'orientation': self.set_orientation(value) ############################################################################ # GtkWidget ############################################################################ def do_realize(self): # Internal housekeeping self.set_flags(self.flags() | gtk.REALIZED) self.window = gdk.Window(self.get_parent_window(), x = self.allocation.x, y = self.allocation.y, width = self.allocation.width, height = self.allocation.height, window_type = gdk.WINDOW_CHILD, wclass = gdk.INPUT_OUTPUT, event_mask = (gdk.EXPOSURE_MASK | gdk.LEAVE_NOTIFY_MASK | gdk.BUTTON_PRESS_MASK | gdk.BUTTON_RELEASE_MASK | gdk.POINTER_MOTION_MASK)) self.window.set_user_data(self) self.style.attach(self.window) self.style.set_background(self.window, gtk.STATE_NORMAL) # Set parent window on all child widgets for item in self._items: item.child.set_parent_window(self.window) # Initialize cursors self._hcursor = gdk.Cursor(self.get_display(), gdk.SB_H_DOUBLE_ARROW) self._vcursor = gdk.Cursor(self.get_display(), gdk.SB_V_DOUBLE_ARROW) def do_unrealize(self): self._hcursor = None self._vcursor = None self.window.set_user_data(None) self.window.destroy() gtk.Container.do_unrealize(self) def do_map(self): self.window.show() gtk.Container.do_map(self) def do_unmap(self): self.window.hide() gtk.Container.do_unmap(self) def do_size_request(self, requisition): # Start with nothing width = height = 0 # Add child widgets for item in self._items: w, h = item.child.size_request() if self._orientation == gtk.ORIENTATION_HORIZONTAL: width += w height = max(height, h) # Store the minimum weight for usage in do_size_allocate item.min_size = w else: width = max(width, w) height += h # Store the minimum weight for usage in do_size_allocate item.min_size = h # Add handles if self._orientation == gtk.ORIENTATION_HORIZONTAL: width += self._get_n_handles() * self._handle_size else: height += self._get_n_handles() * self._handle_size # Done requisition.width = width requisition.height = height def do_size_allocate(self, allocation): #################################################################### # DockPaned resizing explained # # When a widget is resized (ie when the parent window is resized by # the user), the do_size_request & do_size_allocate dance is # typically executed multiple times with small changes (1 or 2 pixels, # sometimes more depending on the gdk backend used), instead of one # pass with the complete delta. Distributing those small values # evenly across multiple child widgets simply doesn't work very well. # To overcome this problem, we assign a weight (can be translated to # "factor") to each child. # #################################################################### if self._items: size = self._effective_size(allocation) self._redistribute_weight(size) cx = cy = 0 # current x and y counters handle_size = self._handle_size # Allocate child widgets: both items and handles, so we can simply increment for child in self._children(): rect = gdk.Rectangle() rect.x = cx rect.y = cy if isinstance(child, _DockPanedItem): s = round(child.weight * size) if self._orientation == gtk.ORIENTATION_HORIZONTAL: rect.height = allocation.height rect.width = s cx += s if child is self._items[-1]: rect.width += allocation.width - cx else: rect.height = s rect.width = allocation.width cy += s if child is self._items[-1]: rect.height += allocation.height - cy child.child.size_allocate(rect) elif isinstance(child, _DockPanedHandle): if self._orientation == gtk.ORIENTATION_HORIZONTAL: rect.height = allocation.height rect.width = handle_size cx += handle_size else: rect.height = handle_size rect.width = allocation.width cy += handle_size child.area = rect # Accept new allocation self.allocation = allocation # Move/Resize our GdkWindow if self.flags() & gtk.REALIZED: self.window.move_resize(*allocation) def do_expose_event(self, event): for item in self._items: self.propagate_expose(item.child, event) for handle in self._handles: #TODO: render themed handle if not using compact layout pass return False def do_leave_notify_event(self, event): # Reset cursor self.window.set_cursor(None) def do_button_press_event(self, event): # We might be starting a drag operation, or we could simply be starting # a click somewhere. Store information from this event in self._dragcontext # and decide in do_motion_notify_event if we're actually starting a # drag operation or not. if event.window is self.window and event.button == 1: handle = self._get_handle_at_pos(event.x, event.y) if handle: self._dragcontext.dragging = True self._dragcontext.dragged_object = handle self._dragcontext.source_button = event.button self._dragcontext.offset_x = event.x - handle.area.x self._dragcontext.offset_y = event.y - handle.area.y return True return False def do_button_release_event(self, event): # Reset drag context if event.button == self._dragcontext.source_button: self._dragcontext.reset() self.window.set_cursor(None) return True return False def do_motion_notify_event(self, event): cursor = None # Set an appropriate cursor when the pointer is over a handle if self._get_handle_at_pos(event.x, event.y): if self._orientation == gtk.ORIENTATION_HORIZONTAL: cursor = self._hcursor else: cursor = self._vcursor # Drag a handle if self._dragcontext.dragging: if self._orientation == gtk.ORIENTATION_HORIZONTAL: cursor = self._hcursor delta_size = int(event.x - self._dragcontext.dragged_object.area.x - self._dragcontext.offset_x) else: cursor = self._vcursor delta_size = int(event.y - self._dragcontext.dragged_object.area.y - self._dragcontext.offset_y) handle_index = self._handles.index(self._dragcontext.dragged_object) item_after = self._items[handle_index + 1] if delta_size < 0: # Enlarge the item after and shrink the items before the handle delta_size = abs(delta_size) enlarge = item_after shrink = reversed(self._items[:self._items.index(item_after)]) self._redistribute_size(delta_size, enlarge, shrink) elif delta_size > 0: # Enlarge the item before and shrink the items after the handle enlarge = self._items[handle_index] shrink = self._items[self._items.index(item_after):] self._redistribute_size(delta_size, enlarge, shrink) else: enlarge = None shrink = [] self.queue_resize() # Set the cursor we decided upon above... if cursor: self.window.set_cursor(cursor) ############################################################################ # GtkContainer ############################################################################ def do_add(self, widget): self._insert_item(widget) def do_remove(self, widget): self._remove_item(widget) def do_forall(self, internals, callback, data): try: for item in self._items: callback(item.child, data) except AttributeError: pass def do_get_child_property(self, child, property_id, pspec): item = self._item_for_child(child) if pspec.name == 'weight': return item.weight_request or item.weight def do_set_child_property(self, child, property_id, value, pspec): item = self._item_for_child(child) if pspec.name == 'weight': item.weight_request = value child.child_notify('weight') ############################################################################ # EtkDockPaned ############################################################################ def get_handle_size(self): ''' :return: the size of the handles in the dockpaned. Retrieves the size of the handles in the dockpaned. ''' return self._handle_size def set_handle_size(self, handle_size): ''' :param handle_size: the new size for the handles in the dockpaned. Sets the size for the handles in the dockpaned. ''' self._handle_size = handle_size self.notify('handle-size') self.queue_resize() def get_orientation(self): ''' :return: the orientation of the dockpaned. Retrieves the orientation of the dockpaned. ''' return self._orientation def set_orientation(self, orientation): ''' :param orientation: the dockpaned's new orientation. Sets the orientation of the dockpaned. ''' self._orientation = orientation self.notify('orientation') self.queue_resize() def append_item(self, child): ''' :param child: the :class:`gtk.Widget` to use as the contents of the item. :returns: the index number of the item in the dockpaned. The :meth:`append_item` method prepends an item to the dockpaned. ''' return self._insert_item(child) def prepend_item(self, child): ''' :param child: the :class:`gtk.Widget` to use as the contents of the item. :returns: the index number of the item in the dockpaned. The :meth:`prepend_item` method prepends an item to the dockpaned. ''' return self._insert_item(child, 0) def insert_item(self, child, position=None, weight=None): ''' :param child: a :class:`gtk.Widget`` to use as the contents of the item. :param position: the index (starting at 0) at which to insert the item, negative or :const:`None` to append the item after all other items. :param weight: The relative amount of space the child should get. No guarantees. :returns: the index number of the item in the dockpaned. The :meth:`insert_item` method inserts an item into the dockpaned at the location specified by `position` (0 is the first item). `child` is the widget to use as the contents of the item. If position is negative or :const:`None` the item is appended to the dockpaned. ''' return self._insert_item(child, position, weight) def remove_item(self, item_num): ''' :param item_num: the index (starting from 0) of the item to remove. If :const:`None` or negative, the last item will be removed. The :meth:`remove_item` method removes the item at the location specified by `item_num`. The value of `item_num` starts from 0. If `item_num` is negative or :const:`None` the last item of the dockpaned will be removed. ''' if item_num is None or item_num < 0: child = self.get_nth_item(len(self) - 1) else: child = self.get_nth_item(item_num) self._remove_item(child) def item_num(self, child): ''' :param child: a :class:`gtk.Widget`. :returns: the index of the item containing `child`, or :const:`None` if `child` is not in the dockpaned. The :meth:`item_num()` method returns the index of the item which contains the widget specified by `child` or :const:`None` if no item contains `child`. ''' try: return self.get_children().index(child) except ValueError: pass def get_nth_item(self, item_num): ''' :param item_num: the index of an item in the dockpaned. :returns: the child widget, or :const:`None` if `item_num` is out of bounds. The :meth:`get_nth_item‘ method returns the child widget contained at the index specified by `item_num`. If `item_num` is out of bounds for the item range of the dockpaned this method returns :const:`None`. ''' if item_num >= 0 and item_num <= len(self) - 1: return self._items[item_num].child else: return None def get_item_at_pos(self, x, y): ''' :param x: the x coordinate of the position. :param y: the y coordinate of the position. :returns: the child widget at the position specified by x and y or :const:`None`. The :meth:`get_item_at_pos` method returns the child widget whose allocation contains the position specified by `x` and `y` or :const:`None` if no child widget is at that position. ''' for item in self._items: if (x, y) in item: return item.child else: return None def reorder_item(self, child, position): ''' :param child: the child widget to move. :param position: the index that `child` is to move to, or :const:`None` to move to the end. The :meth:`reorder_item` method reorders the dockpaned child widgets so that `child` appears in the location specified by `position`. If `position` is greater than or equal to the number of children in the list or negative or :const:`None`, `child` will be moved to the end of the list. ''' item_num = self.item_num(child) assert item_num is not None if position is None or position < 0 or position > len(self) - 1: position = len(self) item = self._items[item_num] self._items.remove(item) self._items.insert(position, item) self.queue_resize() ############################################################################ # Install child properties ############################################################################ for index, (name, pspec) in enumerate(DockPaned.__gchild_properties__.iteritems()): pspec = list(pspec) pspec.insert(0, name) DockPaned.install_child_property(index + 1, tuple(pspec)) def fair_scale(weight, wmpairs): """ Fair scaling algorithm. A weight and a list of (weight, min_weight) pairs is provided. The result is a list of calculated weights that add up to weight, but are no smaller than their specified min_weight's. >>> fair_scale(.7, ((.3, .2), (.5, .1))) [0.26249999999999996, 0.43749999999999994] >>> fair_scale(.5, ((.3, .2), (.5, .1))) [0.20000000000000001, 0.29999999999999999] >>> fair_scale(.4, ((.3, .2), (.5, .1))) [0.20000000000000001, 0.20000000000000001] """ # List of new weights n = [0] * len(wmpairs) # Values that have been assigned their min_weight end up in this list: skip = [False] * len(wmpairs) while True: try: f = weight / sum(a[0] for a, s in zip(wmpairs, skip) if not s) except ZeroDivisionError: f = 0 for i, (w, m) in enumerate(wmpairs): if skip[i]: continue n[i] = w * f if n[i] < m: n[i] = m weight -= m skip[i] = True break else: break # quit while loop return n etk.docking-0.2/lib/etk/docking/docksettings.py000066400000000000000000000055121175471717700216210ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . """ Configuration settings for elements in a Etk.Docking configuration. The configuration can be set for every element in the hierarchy. By default the class name can be used. """ import gtk class DockSettings(object): ''' Container for group specific settings. The following settings can be set: * auto_remove: group is removed if if empty. * can_float: Group can be a floating group. * expand: A group can expand/shrink on resize. * inherit_settings: new groups constructed from items dragged from a group should get the same group name. ''' __slots__ = [ 'auto_remove', 'can_float', 'float_retain_size', 'expand', 'inherit_settings' ] def __init__(self, auto_remove=True, can_float=True, float_retain_size=False, expand=True, inherit_settings=True): self.auto_remove = auto_remove self.can_float = can_float self.float_retain_size = float_retain_size self.expand = expand self.inherit_settings = inherit_settings class DockSettingsDict(object): ''' Settings container. Adheres partly to the dict protocol, only get() and setitem are supported. Settings can deal with widget names as well as widgets itself (in which case the name is requested). By overriding ``widget_name()`` it is possible to customize the behaviour for settings. ''' def __init__(self): self._settings = {} # Map group-id -> layout settings def get(self, target): return self[target] def widget_name(self, target): if isinstance(target, gtk.Widget): return target.get_name() return str(target) def __getitem__(self, target): target = self.widget_name(target) settings = self._settings.get(target) if not settings: settings = self._settings[target] = DockSettings() return settings def __setitem__(self, target, settings): self._settings[self.widget_name(target)] = settings settings = DockSettingsDict() etk.docking-0.2/lib/etk/docking/dockstore.py000066400000000000000000000152401175471717700211140ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . from __future__ import absolute_import import sys from simplegeneric import generic from xml.etree.ElementTree import Element, SubElement, tostring, fromstring import gtk from .docklayout import DockLayout from .dockframe import DockFrame from .dockpaned import DockPaned from .dockgroup import DockGroup from .dockitem import DockItem SERIALIZABLE = ( DockFrame, DockPaned, DockGroup, DockItem ) def serialize(layout): def _ser(widget, element): if isinstance(widget, SERIALIZABLE): sub = SubElement(element, type(widget).__name__.lower() , attributes(widget)) widget.foreach(_ser, sub) else: sub = SubElement(element, 'widget', attributes(widget)) tree = Element('layout') map(_ser, layout.frames, [tree] * len(layout.frames)) return tostring(tree, encoding=sys.getdefaultencoding()) widget_factory = {} def deserialize(layoutstr, itemfactory): ''' Return a new layout with it's attached frames. Frames that should be floating already have their gtk.Window attached (check frame.get_parent()). Transient settings and such should be done by the invoking application. ''' def _des(element, parent_widget=None): if element.tag == 'widget': name = element.attrib['name'] widget = itemfactory(name) widget.set_name(name) parent_widget.add(widget) else: factory = widget_factory[element.tag] widget = factory(parent=parent_widget, **element.attrib) assert widget, 'No widget (%s)' % widget if len(element): map(_des, element, [widget] * len(element)) return widget tree = fromstring(layoutstr) layout = DockLayout() map(_des, tree, [ layout ] * len(tree)) return layout def get_main_frames(layout): return (f for f in layout.frames \ if not isinstance(f.get_parent(), gtk.Window)) def finish(layout, main_frame): ''' Finish the loading process by setting all floating windows "transient_for" the main window (which should be a ancestor of the main_frame). ''' main_window = main_frame.get_toplevel() for frame in layout.frames: if frame is main_frame: continue parent = frame.get_parent() if parent: assert isinstance(parent, gtk.Window), parent parent.set_transient_for(main_window) def parent_attributes(widget): """ Add properties defined in the parent widget specific for this instance (like weight). """ container = widget.get_parent() d = {} if isinstance(container, DockPaned): paned_item = [i for i in container._items if i.child is widget][0] if paned_item.weight: d['weight'] = str(int(paned_item.weight * 100)) return d @generic def attributes(widget): raise NotImplementedError @attributes.when_type(gtk.Widget) def widget_attributes(widget): return { 'name': widget.get_name() or 'empty' } @attributes.when_type(DockItem) def dock_item_attributes(widget): d = { 'title': widget.props.title, 'tooltip': widget.props.title_tooltip_text } if widget.props.icon_name: d['icon_name'] = widget.props.icon_name if widget.props.stock: d['stock_id'] = widget.props.stock return d @attributes.when_type(DockGroup) def dock_group_attributes(widget): d = parent_attributes(widget) name = widget.get_name() if name != widget.__gtype__.name: d['name'] = name return d @attributes.when_type(DockPaned) def dock_paned_attributes(widget): return dict(orientation=(widget.get_orientation() == gtk.ORIENTATION_HORIZONTAL and 'horizontal' or 'vertical'), **parent_attributes(widget)) @attributes.when_type(DockFrame) def dock_frame_attributes(widget): a = widget.allocation d = dict(width=str(a.width), height=str(a.height)) parent = widget.get_parent() if isinstance(parent, gtk.Window) and parent.get_transient_for(): d['floating'] = 'true' d['x'], d['y'] = map(str, parent.get_position()) return d def factory(typename): ''' Simple decorator for populating the widget_factory dictionary. ''' def _factory(func): widget_factory[typename] = func return func return _factory @factory('dockitem') def dock_item_factory(parent, title, tooltip, icon_name=None, stock_id=None, pos=None, vispos=None, current=None, name=None): item = DockItem(title, tooltip, icon_name, stock_id) if name: item.set_name(name) if pos: pos = int(pos) if vispos: vispos = int(vispos) parent.insert_item(item, pos, vispos) return item @factory('dockgroup') def dock_group_factory(parent, weight=None, name=None): group = DockGroup() if name: group.set_name(name) if weight is not None: parent.insert_item(group, weight=float(weight) / 100.) else: parent.add(group) return group @factory('dockpaned') def dock_paned_factory(parent, orientation, weight=None, name=None): paned = DockPaned() if name: paned.set_name(name) if orientation == 'horizontal': paned.set_orientation(gtk.ORIENTATION_HORIZONTAL) else: paned.set_orientation(gtk.ORIENTATION_VERTICAL) if weight is not None: item = parent.insert_item(paned, weight=float(weight) / 100.) else: parent.add(paned) return paned @factory('dockframe') def dock_frame_factory(parent, width, height, floating=None, x=None, y=None): assert isinstance(parent, DockLayout), parent frame = DockFrame() frame.set_size_request(int(width), int(height)) parent.add(frame) if floating == 'true': window = gtk.Window(gtk.WINDOW_TOPLEVEL) #self.window.set_type_hint(gdk.WINDOW_TYPE_HINT_UTILITY) window.set_property('skip-taskbar-hint', True) window.move(int(x), int(y)) window.add(frame) return frame etk.docking-0.2/lib/etk/docking/hslcolor.py000066400000000000000000000146311175471717700207470ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . from __future__ import division import gobject import gtk.gdk as gdk class HslColor(gobject.GObject): __gtype_name__ = 'EtkHslColor' __gproperties__ = {'h': (float, 'h', 'h', 0.0, 1.0, 0.0, gobject.PARAM_READWRITE), 's': (float, 's', 's', 0.0, 1.0, 0.0, gobject.PARAM_READWRITE), 'l': (float, 'l', 'l', 0.0, 1.0, 0.0, gobject.PARAM_READWRITE), 'red-float': (float, 'red-float', 'red-float', 0.0, 1.0, 0.0, gobject.PARAM_READABLE), 'green-float': (float, 'green-float', 'green-float', 0.0, 1.0, 0.0, gobject.PARAM_READABLE), 'blue-float': (float, 'blue-float', 'blue-float', 0.0, 1.0, 0.0, gobject.PARAM_READABLE)} def __init__(self, color): gobject.GObject.__init__(self) self._update_hsl(color) self._update_rgb() ############################################################################ # GObject ############################################################################ def do_get_property(self, pspec): if pspec.name == 'h': return self.get_h() elif pspec.name == 's': return self.get_s() elif pspec.name == 'l': return self.get_l() elif pspec.name == 'red-float': return self.get_red_float() elif pspec.name == 'green-float': return self.get_green_float() elif pspec.name == 'blue-float': return self.get_blue_float() def do_set_property(self, pspec, value): if pspec.name == 'h': self.set_h(value) elif pspec.name == 's': self.set_s(value) elif pspec.name == 'l': self.set_l(value) def get_h(self): return self._h def set_h(self, value): if value < 0: self._h = 0.0 elif value > 1: self._h = 1.0 else: self._h = value self._update_rgb() def get_s(self): return self._s def set_s(self, value): if value < 0: self._s = 0.0 elif value > 1: self._s = 1.0 else: self._s = value self._update_rgb() def get_l(self): return self._l def set_l(self, value): if value < 0: self._l = 0.0 elif value > 1: self._l = 1.0 else: self._l = value self._update_rgb() def get_red_float(self): return self._red_float def get_green_float(self): return self._green_float def get_blue_float(self): return self._blue_float def get_rgb_float(self): return (self._red_float, self._green_float, self._blue_float) def get_rgb(self): return (int(self._red_float * 65535), int(self._green_float * 65535), int(self._blue_float * 65535)) ############################################################################ # HslColor ############################################################################ def to_gdk_color(self): return gdk.Color(*self.get_rgb_float()) def _update_hsl(self, color): r = color.red / float(65535) g = color.green / float(65535) b = color.blue / float(65535) v = max((r, g, b)) m = min((r, g, b)) self._l = (m + v) / 2.0 if self._l <= 0.0: return vm = v - m self._s = vm if self._s > 0.0: if self._l <= 0.5: self._s = self._s / (v + m) else: self._s = self._s / (2.0 - v - m) else: return r2 = (v - r) / vm g2 = (v - g) / vm b2 = (v - b) / vm if r == v: if g == m: self._h = 5.0 + b2 else: self._h = 1.0 - g2 elif g == v: if b == m: self._h = 1.0 + r2 else: self._h = 3.0 - b2 else: if r == m: self._h = 3.0 + g2 else: self._h = 5.0 - r2 self._h = self._h / 6.0 def _update_rgb(self): if self._h > 1: self._h = 1 if self._h < 0: self._h = 0 if self._s > 1: self._s = 1 if self._s < 0: self._s = 0 if self._l > 1: self._l = 1 if self._l < 0: self._l = 0 if self._l == 0: self._red_float = self._green_float = self._blue_float = 0.0 elif self._s == 0: self._red_float = self._green_float = self._blue_float = self._l else: if self._l <= 0.5: t2 = self._l * (1.0 + self._s) else: t2 = self._l + self._s - (self._l * self._s) t1 = 2.0 * self._l - t2 t3 = [self._h + 1.0 / 3.0, self._h, self._h - 1.0 / 3.0] clr = [0.0, 0.0, 0.0] for i in range(3): if t3[i] < 0: t3[i] += 1.0 if t3[i] > 1: t3[i] -= 1.0 if 6.0 * t3[i] < 1.0: clr[i] = t1 + (t2 - t1) * t3[i] * 6.0 elif 2.0 * t3[i] < 1.0: clr[i] = t2 elif 3.0 * t3[i] < 2.0: clr[i] = t1 + (t2 - t1) * ((2.0 / 3.0) - t3[i]) * 6.0 else: clr[i] = t1 self._red_float = clr[0] self._green_float = clr[1] self._blue_float = clr[2] etk.docking-0.2/lib/etk/docking/icons/000077500000000000000000000000001175471717700176565ustar00rootroot00000000000000etk.docking-0.2/lib/etk/docking/icons/16x16/000077500000000000000000000000001175471717700204435ustar00rootroot00000000000000etk.docking-0.2/lib/etk/docking/icons/16x16/compact-close-prelight.png000066400000000000000000000006401175471717700255160ustar00rootroot00000000000000PNG  IHDRasRGBbKGD pHYs  tIME 2 D6TtEXtCommentCreated with GIMPWIDAT8c_3 , 7-2g*#B\rLganCnCSėOg```@փ3 mJƽHn] ?<kLBLaɌ`0]=IM- .nCGCP5?€nCC#AqMC&2LaSl8&g8t}la8sg2b漆7lIENDB`etk.docking-0.2/lib/etk/docking/icons/16x16/compact-close.png000066400000000000000000000004641175471717700237060ustar00rootroot00000000000000PNG  IHDRasRGBbKGD pHYs  tIME)-4?tEXtCommentCreated with GIMPWIDAT8c|%B@,Ȝֶ0vuU #Qro^ހ0P7P] &f(*HcȦۆmf VW0bXTT.TWsa~lbD R l 4`c3qg&9L560IENDB`etk.docking-0.2/lib/etk/docking/icons/16x16/compact-list.png000066400000000000000000000004061175471717700235500ustar00rootroot00000000000000PNG  IHDRasRGBbKGD pHYs  tIME )utEXtCommentCreated with GIMPWaIDAT8c|%B@,Ȝֶh*as޼ S)3a3 ֢TT!430000FXLЀF (LVPIENDB`etk.docking-0.2/lib/etk/docking/icons/16x16/compact-maximize.png000066400000000000000000000003511175471717700244170ustar00rootroot00000000000000PNG  IHDRasRGBbKGDC pHYs  tIME ;5-tEXtCommentCreated with GIMPWDIDAT8c@`bhX9E)DoF0000MWsQA*n`S@q00hd VIENDB`etk.docking-0.2/lib/etk/docking/icons/16x16/compact-minimize.png000066400000000000000000000003461175471717700244210ustar00rootroot00000000000000PNG  IHDRasRGBbKGDC pHYs  tIME ;/ZtEXtCommentCreated with GIMPWAIDAT8cd`@ `bPl 20?(O͈ j.*H@0h5`0D j|IENDB`etk.docking-0.2/lib/etk/docking/icons/16x16/compact-restore.png000066400000000000000000000004021175471717700242540ustar00rootroot00000000000000PNG  IHDRasRGBbKGDC pHYs  tIMEE6tEXtCommentCreated with GIMPW]IDAT8c@`b(O<}0 }f NGրn^f;4\E"Nby/atd²IENDB`etk.docking-0.2/lib/etk/docking/tests/000077500000000000000000000000001175471717700177055ustar00rootroot00000000000000etk.docking-0.2/lib/etk/docking/tests/test_code.py000066400000000000000000000042641175471717700222360ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . import os import sys import unittest import tabnanny class Capture(object): def __init__(self): self._output = [] self.old_stdout = None self.old_stderr = None def start(self): self.old_stdout = sys.stdout sys.stdout = self self.old_stderr = sys.stderr sys.stderr = self def stop(self): if self.old_stdout: sys.stdout = self.old_stdout if self.old_stderr: sys.stderr = self.old_stderr def output(self): count = 0 while count < len(self._output): yield (self._output[count], self._output[count + 1], self._output[count + 2]) count += 3 def write(self, message): message = message.strip() if message != '': self._output.append(message) class TestCode(unittest.TestCase): ############################################################################ # Test indentation (tab/spaces) ############################################################################ def test_indentation(self): capture = Capture() capture.start() tabnanny.verbose = 0 tabnanny.check(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..'))) capture.stop() for line in capture.output(): raise IndentationError('Ambiguous indentation detected in %s on line %s' % (line[0], line[1])) etk.docking-0.2/lib/etk/docking/tests/test_dockgroup.py000066400000000000000000000266301175471717700233220ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . import unittest import gtk from etk.docking import DockItem, DockGroup class TestDockGroup(unittest.TestCase): ############################################################################ # Test signals ############################################################################ def test_remove_signal(self): remove_events = [] item_removed_events = [] def on_remove(self, widget): remove_events.append(widget) def on_item_removed(dockgroup, child): item_removed_events.append(child) dockitem = DockItem() dockitem.add(gtk.Button()) dockgroup = DockGroup() dockgroup.connect('remove', on_remove) dockgroup.connect('item-removed', on_item_removed) dockgroup.add(dockitem) dockgroup.remove(dockitem) self.assertTrue(dockitem in remove_events) self.assertTrue(dockitem in item_removed_events) dockitem.destroy() dockgroup.destroy() def test_item_added_signal(self): add_events = [] item_added_events = [] def on_add(self, widget): add_events.append(widget) def on_item_added(dockgroup, child): item_added_events.append(child) dockitem1 = DockItem() dockitem2 = DockItem() dockgroup = DockGroup() dockgroup.connect('add', on_add) dockgroup.connect('item-added', on_item_added) dockgroup.add(dockitem1) dockgroup.insert_item(dockitem2) self.assertTrue(dockitem1 in item_added_events) self.assertTrue(dockitem1 in add_events) self.assertTrue(dockitem2 in item_added_events) self.assertFalse(dockitem2 in add_events) dockitem2.destroy() dockitem1.destroy() dockgroup.destroy() ############################################################################ # Test public api ############################################################################ def test_add(self): dockitem = DockItem() dockgroup = DockGroup() dockgroup.add(dockitem) self.assertTrue(dockitem in dockgroup) dockitem.destroy() dockgroup.destroy() def test_remove(self): win = gtk.Window() dockitem = DockItem() dockgroup = DockGroup() dockgroup.add(dockitem) win.add(dockgroup) win.show_all() assert dockitem.flags() & gtk.REALIZED dockgroup.remove(dockitem) self.assertTrue(dockitem not in dockgroup) win.destroy() assert not dockitem.flags() & gtk.REALIZED assert not dockgroup.flags() & gtk.REALIZED def test_item_destroy(self): win = gtk.Window() dockitem = DockItem() dockgroup = DockGroup() dockgroup.add(dockitem) win.add(dockgroup) win.show_all() assert len(dockgroup.items) == 1 dockitem.destroy() self.assertTrue(dockitem not in dockgroup) assert len(dockgroup.items) == 0 win.destroy() dockgroup.destroy() def test_append_item(self): dockitem = DockItem() dockgroup = DockGroup() index = dockgroup.append_item(dockitem) self.assertTrue(index == 0) self.assertTrue(dockgroup.get_nth_item(0) is dockitem) dockitem.destroy() dockgroup.destroy() def test_prepend_item(self): dockitem1 = DockItem() dockitem2 = DockItem() dockgroup = DockGroup() index1 = dockgroup.append_item(dockitem1) index2 = dockgroup.prepend_item(dockitem2) self.assertTrue(index1 == 0) self.assertTrue(index2 == 0) self.assertTrue(dockgroup.get_nth_item(0) is dockitem2) self.assertTrue(dockgroup.get_nth_item(1) is dockitem1) dockitem1.destroy() dockitem2.destroy() dockgroup.destroy() def test_insert_item(self): dockitem1 = DockItem() dockitem2 = DockItem() dockitem3 = DockItem() dockgroup = DockGroup() dockgroup.insert_item(dockitem1, None) dockgroup.insert_item(dockitem2, 0) dockgroup.insert_item(dockitem3, 1) self.assertTrue(dockgroup.get_nth_item(0) is dockitem2) self.assertTrue(dockgroup.get_nth_item(1) is dockitem3) self.assertTrue(dockgroup.get_nth_item(2) is dockitem1) dockitem1.destroy() dockitem2.destroy() dockitem3.destroy() dockgroup.destroy() def test_remove_item(self): dockitem1 = DockItem() dockitem2 = DockItem() dockgroup = DockGroup() dockgroup.add(dockitem1) dockgroup.add(dockitem2) dockgroup.remove_item(0) dockgroup.remove_item(None) self.assertTrue(dockitem1 not in dockgroup) self.assertTrue(dockitem2 not in dockgroup) dockitem1.destroy() dockitem2.destroy() dockgroup.destroy() def test_item_num(self): dockitem1 = DockItem() dockitem2 = DockItem() dockgroup = DockGroup() dockgroup.add(dockitem1) self.assertTrue(dockgroup.item_num(dockitem1) == 0) self.assertTrue(dockgroup.item_num(dockitem2) is None) dockitem1.destroy() dockgroup.destroy() def test_get_n_items(self): dockgroup = DockGroup() self.assertTrue(dockgroup.get_n_items() == len(dockgroup) == 0) dockitem = DockItem() dockgroup.add(dockitem) self.assertTrue(dockgroup.get_n_items() == len(dockgroup) == 1) dockitem.destroy() dockgroup.destroy() def test_get_nth_item(self): dockitem1 = DockItem() dockitem2 = DockItem() dockgroup = DockGroup() dockgroup.add(dockitem1) dockgroup.add(dockitem2) self.assertTrue(dockgroup.get_nth_item(0) is dockitem1) self.assertTrue(dockgroup.get_nth_item(1) is dockitem2) self.assertTrue(dockgroup.get_nth_item(2) is None) self.assertTrue(dockgroup.get_nth_item(-1) is None) dockitem1.destroy() dockitem2.destroy() dockgroup.destroy() def test_get_current_item(self): dockitem = DockItem() dockgroup = DockGroup() self.assertTrue(dockgroup.get_current_item() is None) index = dockgroup.append_item(dockitem) self.assertTrue(dockgroup.get_current_item() == index) dockgroup.remove(dockitem) self.assertTrue(dockgroup.get_current_item() is None) dockitem.destroy() dockgroup.destroy() def test_set_current_item(self): dockitem1 = DockItem() dockitem2 = DockItem() dockgroup = DockGroup() self.assertTrue(dockgroup.get_current_item() is None) index = dockgroup.append_item(dockitem1) self.assertTrue(dockgroup.get_current_item() == index) index = dockgroup.append_item(dockitem2) self.assertTrue(dockgroup.get_current_item() == index) dockgroup.set_current_item(0) self.assertTrue(dockgroup.get_current_item() == 0) dockgroup.set_current_item(len(dockgroup) + 10) self.assertTrue(dockgroup.get_current_item() == len(dockgroup) - 1) dockgroup.set_current_item(-1) self.assertTrue(dockgroup.get_current_item() == 0) dockitem1.destroy() dockitem2.destroy() dockgroup.destroy() def test_next_item(self): dockitem1 = DockItem() dockitem2 = DockItem() dockgroup = DockGroup() dockgroup.add(dockitem1) dockgroup.add(dockitem2) dockgroup.set_current_item(0) self.assertTrue(dockgroup.get_current_item() == 0) dockgroup.next_item() self.assertTrue(dockgroup.get_current_item() == 1) dockgroup.next_item() self.assertTrue(dockgroup.get_current_item() == 1) dockitem1.destroy() dockitem2.destroy() dockgroup.destroy() def test_prev_item(self): dockitem1 = DockItem() dockitem2 = DockItem() dockgroup = DockGroup() dockgroup.add(dockitem1) dockgroup.add(dockitem2) self.assertTrue(dockgroup.get_current_item() == 1) dockgroup.prev_item() self.assertTrue(dockgroup.get_current_item() == 0) dockgroup.prev_item() self.assertTrue(dockgroup.get_current_item() == 0) dockitem1.destroy() dockitem2.destroy() dockgroup.destroy() def test_reorder_item(self): dockitem1 = DockItem() dockitem2 = DockItem() dockitem3 = DockItem() dockgroup = DockGroup() dockgroup.add(dockitem1) dockgroup.add(dockitem2) dockgroup.add(dockitem3) dockgroup.reorder_item(dockitem3, 0) dockgroup.reorder_item(dockitem1, 2) self.assertTrue(dockgroup.item_num(dockitem1) == 2) self.assertTrue(dockgroup.item_num(dockitem2) == 1) self.assertTrue(dockgroup.item_num(dockitem3) == 0) dockitem1.destroy() dockitem2.destroy() dockitem3.destroy() dockgroup.destroy() def test_add_signal(self): events = [] item_in = [] item_in_after = [] def event_handler(self, w): events.append(w) item_in.append(w in self.items) def event_handler_after(self, w): item_in_after.append(w in self.items) dockgroup = DockGroup() dockgroup.connect('add', event_handler) dockgroup.connect_after('add', event_handler_after) dockitem1 = DockItem() dockgroup.add(dockitem1) self.assertEquals([dockitem1], events) self.assertEquals([True], item_in) self.assertEquals([True], item_in_after) dockitem2 = DockItem() dockgroup.insert_item(dockitem2) self.assertEquals([dockitem1], events) self.assertEquals([True], item_in) self.assertEquals([True], item_in_after) def test_drag_begin(self): dockitem1 = DockItem() dockitem2 = DockItem() dockitem3 = DockItem() dockgroup = DockGroup() dockgroup.add(dockitem1) dockgroup.add(dockitem2) dockgroup.add(dockitem3) window = gtk.Window() window.add(dockgroup) window.set_size_request(200, 200) self.assertEquals(dockitem3, dockgroup._current_tab.item) def test_item_closed_event_is_emited_on_close(self): dockitem = DockItem() dockgroup = DockGroup() dockgroup.add(dockitem) tab = dockgroup._tabs[0] item_closed = [] def item_closed_handler(item): item_closed.append(item) dockitem.connect('close', item_closed_handler) # Simulate clicking the close button tab.button.emit('clicked') assert [dockitem] == item_closed etk.docking-0.2/lib/etk/docking/tests/test_dockitem.py000066400000000000000000000215371175471717700231250ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . import unittest import gtk from etk.docking import DockItem class TestDockItem(unittest.TestCase): ############################################################################ # Test properties ############################################################################ def test_prop_title(self): global notify_called def _on_notify(gobject, pspec): global notify_called notify_called = True dockitem = DockItem() dockitem.connect('notify::title', _on_notify) notify_called = False dockitem.set_title('sometitle') self.assertEquals(dockitem.get_title(), 'sometitle', msg='get_title method did not return expected value') self.assertTrue(notify_called, msg='title property change notification failed when using set_title method') notify_called = False dockitem.set_property('title', 'anothertitle') self.assertEquals(dockitem.get_property('title'), 'anothertitle', msg='get_property method did not return expected value') self.assertTrue(notify_called, msg='title property change notification failed when using set_property method') notify_called = False dockitem.props.title = 'hello' self.assertEquals(dockitem.props.title, 'hello', msg='.props attribute did not return expected value') self.assertTrue(notify_called, msg='title property change notification failed when using .props attribute') dockitem.destroy() def test_prop_title_tooltip_text(self): global notify_called def _on_notify(gobject, pspec): global notify_called notify_called = True dockitem = DockItem() dockitem.connect('notify::title-tooltip-text', _on_notify) notify_called = False dockitem.set_title_tooltip_text('sometext') self.assertEquals(dockitem.get_title_tooltip_text(), 'sometext', msg='get_title_tooltip_text method did not return expected value') self.assertTrue(notify_called, msg='title-tooltip-text property change notification failed when using set_title_tooltip_text method') notify_called = False dockitem.set_property('title-tooltip-text', 'anothertext') self.assertEquals(dockitem.get_property('title-tooltip-text'), 'anothertext', msg='get_property method did not return expected value') self.assertTrue(notify_called, msg='title-tooltip-text property change notification failed when using set_title_tooltip_text method') notify_called = False dockitem.props.title_tooltip_text = 'hello' self.assertEquals(dockitem.props.title_tooltip_text, 'hello', msg='.props attribute did not return expected value') self.assertTrue(notify_called, msg='title-tooltip-text property change notification failed when using .props attribute') dockitem.destroy() def test_prop_icon_name(self): global notify_called def _on_notify(gobject, pspec): global notify_called notify_called = True dockitem = DockItem() dockitem.connect('notify::icon-name', _on_notify) notify_called = False dockitem.set_icon_name('someicon') self.assertEquals(dockitem.get_icon_name(), 'someicon', msg='get_icon_name method did not return expected value') self.assertTrue(notify_called, msg='icon-name property change notification failed when using set_icon_name method') notify_called = False dockitem.set_property('icon-name', 'anothericon') self.assertEquals(dockitem.get_property('icon-name'), 'anothericon', msg='get_property method did not return expected value') self.assertTrue(notify_called, msg='icon-name property change notification failed when using set_property method') notify_called = False dockitem.props.icon_name = 'niceicon' self.assertEquals(dockitem.props.icon_name, 'niceicon', msg='.props attribute did not return expected value') self.assertTrue(notify_called, msg='icon-name property change notification failed when using .props attribute') dockitem.destroy() def test_prop_stock(self): global notify_called def _on_notify(gobject, pspec): global notify_called notify_called = True dockitem = DockItem() dockitem.connect('notify::stock', _on_notify) notify_called = False dockitem.set_stock(gtk.STOCK_ABOUT) self.assertEquals(dockitem.get_stock(), gtk.STOCK_ABOUT, msg='get_stock method did not return expected value') self.assertTrue(notify_called, msg='stock property change notification failed when using set_stock method') notify_called = False dockitem.set_property('stock', gtk.STOCK_ADD) self.assertEquals(dockitem.get_property('stock'), gtk.STOCK_ADD, msg='get_property method did not return expected value') self.assertTrue(notify_called, msg='stock property change notification failed when using set_property method') notify_called = False dockitem.props.stock = gtk.STOCK_APPLY self.assertEquals(dockitem.props.stock, gtk.STOCK_APPLY, msg='.props attribute did not return expected value') self.assertTrue(notify_called, msg='stock property change notification failed when using .props attribute') dockitem.destroy() def test_prop_image(self): #TODO: is there a way to check we actually received the image we expect? dockitem = DockItem() dockitem.set_icon_name('someicon') self.assertTrue(isinstance(dockitem.get_image(), gtk.Image)) self.assertTrue(isinstance(dockitem.props.image, gtk.Image)) self.assertTrue(isinstance(dockitem.get_property('image'), gtk.Image)) dockitem.destroy() dockitem = DockItem() dockitem.set_stock(gtk.STOCK_ABOUT) self.assertTrue(isinstance(dockitem.get_image(), gtk.Image)) self.assertTrue(isinstance(dockitem.props.image, gtk.Image)) self.assertTrue(isinstance(dockitem.get_property('image'), gtk.Image)) dockitem.destroy() ############################################################################ # Test public api ############################################################################ def test_add(self): button = gtk.Button() dockitem = DockItem() dockitem.add(button) self.assertTrue(dockitem.child is button) button.destroy() dockitem.destroy() def test_remove(self): button = gtk.Button() dockitem = DockItem() dockitem.add(button) dockitem.remove(button) self.assertTrue(dockitem.child is None) button.destroy() dockitem.destroy() ############################################################################ # Test appearance ############################################################################ # def test_appearance(self): # frame = gtk.Frame() # frame.set_shadow_type(gtk.SHADOW_NONE) # frame.set_size_request(25, 25) # di = DockItem('gtk-missing-image', 'test') # di.add(frame) # dg = DockGroup() # dg.add(di) # window = gtk.Window() # window.add(dg) # window.show_all() # # snapshot = dg.get_snapshot(None) # pixbuf = gdk.Pixbuf(gdk.COLORSPACE_RGB, True, 8, *snapshot.get_size()) # pixbuf.get_from_drawable(snapshot, window.get_colormap(), 0, 0, 0, 0, *snapshot.get_size()) # pixbuf.save(os.path.join(os.path.dirname(__file__), 'test_dockitem.test_appearance.png'), 'png', {}) etk.docking-0.2/lib/etk/docking/tests/test_docklayout.py000066400000000000000000000272701175471717700235040ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . import unittest import gtk from etk.docking import DockLayout, DockFrame, DockPaned, DockGroup, DockItem from etk.docking.dockgroup import DockGroup, DRAG_TARGET_ITEM_LIST from etk.docking.dnd import DockDragContext class TestDockLayout(unittest.TestCase): def test_construction(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) frame = DockFrame() paned = DockPaned() group = DockGroup() item = DockItem() win.add(frame) frame.add(paned) paned.add(group) group.add(item) layout = DockLayout() layout.add(frame) assert frame in layout.frames print layout._signal_handlers self.assertEquals(4, len(layout._signal_handlers)) self.assertEquals(9, len(layout._signal_handlers[frame])) layout.remove(frame) assert not layout._signal_handlers, layout._signal_handlers assert frame not in layout.frames def test_construction_after_setting_layout(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) frame = DockFrame() paned = DockPaned() group = DockGroup() item = DockItem() layout = DockLayout() layout.add(frame) win.add(frame) frame.add(paned) paned.add(group) group.add(item) assert frame in layout.frames self.assertEquals(4, len(layout._signal_handlers)) self.assertEquals(9, len(layout._signal_handlers[frame])) paned.remove(group) self.assertEquals(2, len(layout._signal_handlers), layout._signal_handlers) assert frame in layout._signal_handlers.keys(), layout._signal_handlers assert paned in layout._signal_handlers.keys(), layout._signal_handlers assert group not in layout._signal_handlers.keys(), layout._signal_handlers assert item not in layout._signal_handlers.keys(), layout._signal_handlers assert frame in layout.frames def test_get_widgets(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) frame = DockFrame() paned = DockPaned() group = DockGroup() item = DockItem() label = gtk.Label() layout = DockLayout() layout.add(frame) win.add(frame) frame.add(paned) paned.add(group) group.add(item) item.add(label) group2 = DockGroup() paned.add(group2) paned.set_name('foo') widgets = list(layout.get_widgets('foo')) assert len(widgets) == 1 assert widgets[0] is paned widgets = list(layout.get_widgets('EtkDockGroup')) assert len(widgets) == 2 assert widgets[0] is group assert widgets[1] is group2 def test_get_widgets_with_many_frames(self): frame1 = DockFrame() frame2 = DockFrame() frame3 = DockFrame() layout = DockLayout() layout.add(frame1) layout.add(frame2) layout.add(frame3) widgets = list(layout.get_widgets('foo')) assert len(widgets) == 0 class StubContext(object): def __init__(self, source_widget, items): self.targets = [ DRAG_TARGET_ITEM_LIST[0] ] self.source_widget = source_widget # Set up dragcontext (nornally done in motion_notify event) if items: self.source_widget.dragcontext = dragcontext = DockDragContext() dragcontext.dragged_object = items def get_source_widget(self): return self.source_widget def finish(self, success, delete, timestamp): self.finished = (success, delete) class StubSelectionData(object): def set(self, atom, bytes, data): print 'StubSelectionData.set(%s, %s, %s)' % (atom, bytes, data) class TestDockLayoutDnD(unittest.TestCase): def setUp(self): self.layout = DockLayout() def drag_get_data(widget, context, target, timestamp): selection_data = StubSelectionData() context.source_widget.do_drag_data_get(context, selection_data, None, timestamp) self.layout.on_widget_drag_data_received(widget, context, 20, 20, selection_data, None, timestamp) DockGroup.drag_get_data = drag_get_data DockPaned.drag_get_data = drag_get_data DockFrame.drag_get_data = drag_get_data def tearDown(self): del self.layout del DockGroup.drag_get_data del DockPaned.drag_get_data del DockFrame.drag_get_data def test_drag_drop_on_group(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) frame = DockFrame() paned = DockPaned() group = DockGroup() item = DockItem() layout = self.layout layout.add(frame) win.add(frame) frame.add(paned) paned.add(group) group.add(item) win.set_default_size(200, 200) win.show_all() while gtk.events_pending(): gtk.main_iteration() context = StubContext(group, [group.items[0]]) group.do_drag_begin(context) x, y = 30, 30 layout.on_widget_drag_motion(group, context, x, y, 0) assert layout._drag_data assert layout._drag_data.drop_widget is group layout.on_widget_drag_drop(group, context, x, y, 0) def test_drag_drop_on_paned(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) frame = DockFrame() paned = DockPaned() groups = (DockGroup(), DockGroup()) item = DockItem() layout = self.layout layout.add(frame) win.add(frame) frame.add(paned) map(paned.add, groups) groups[0].add(item) win.set_default_size(200, 200) win.show_all() x, y = 10, 10 context = StubContext(groups[0], [groups[0].items[0]]) layout.on_widget_drag_motion(paned, context, x, y, 0) assert layout._drag_data assert layout._drag_data.drop_widget is paned, '%s != %s' % (layout._drag_data.drop_widget, paned) def test_remove_paned_with_one_child(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) frame = DockFrame() paned = DockPaned() groups = (DockGroup(), DockGroup()) item = DockItem() layout = self.layout layout.add(frame) win.add(frame) frame.add(paned) map(paned.add, groups) #groups[0].add(item) win.set_default_size(200, 200) win.show_all() # simulate end of DnD on group context = StubContext(groups[0], None) layout.on_widget_drag_end(groups[0], context) assert not paned.get_parent() assert groups[1].get_parent() is frame def test_remove_nested_paned_with_one_child(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) frame = DockFrame() paneds = (DockPaned(), DockPaned()) groups = (DockGroup(), DockGroup()) item = DockItem() layout = self.layout layout.add(frame) win.add(frame) frame.add(paneds[0]) paneds[0].add(groups[0]) paneds[0].add(paneds[1]) paneds[1].add(groups[1]) #groups[0].add(item) win.set_default_size(200, 200) win.show_all() # simulate end of DnD on group, where group is removed and only one # paned remains. context = StubContext(groups[0], None) layout.on_widget_drag_end(paneds[1], context) assert not paneds[1].get_parent() assert groups[1].get_parent() is paneds[0], (paneds, groups[1].get_parent()) def test_remove_empty_groups_recursively(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) frame = DockFrame() paneds = (DockPaned(), DockPaned(), DockPaned()) group = DockGroup() item = DockItem() layout = self.layout layout.add(frame) win.add(frame) frame.add(paneds[0]) paneds[0].add(paneds[1]) paneds[1].add(paneds[2]) paneds[2].add(group) win.set_default_size(200, 200) win.show_all() context = StubContext(group, None) layout.on_widget_drag_end(group, context) # TODO: check is paned[0] assert not paneds[0].get_parent() from etk.docking import docklayout class PlacementTest(unittest.TestCase): def setUp(self): self.layout = DockLayout() self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.frame = DockFrame() self.group = DockGroup() self.layout.add(self.frame) self.window.add(self.frame) self.frame.add(self.group) def test_placement_left(self): g1, g2 = DockGroup(), DockGroup() docklayout.add_new_group_left(self.group, g1) paned = g1.get_parent() assert isinstance(paned, DockPaned), paned assert self.group.get_parent() is paned assert paned.get_nth_item(0) is g1 assert paned.get_nth_item(1) is self.group docklayout.add_new_group_left(self.group, g2) assert self.group.get_parent() is paned assert g2.get_parent() is paned assert paned.get_nth_item(0) is g1 assert paned.get_nth_item(1) is g2 assert paned.get_nth_item(2) is self.group def test_placement_right(self): g1, g2 = DockGroup(), DockGroup() docklayout.add_new_group_right(self.group, g1) paned = g1.get_parent() assert isinstance(paned, DockPaned), paned assert self.group.get_parent() is paned assert paned.get_nth_item(0) is self.group assert paned.get_nth_item(1) is g1 docklayout.add_new_group_right(self.group, g2) assert self.group.get_parent() is paned assert g2.get_parent() is paned assert paned.get_nth_item(0) is self.group assert paned.get_nth_item(1) is g2 assert paned.get_nth_item(2) is g1 def test_placement_above(self): g1, g2 = DockGroup(), DockGroup() docklayout.add_new_group_above(self.group, g1) paned = g1.get_parent() assert isinstance(paned, DockPaned), paned assert self.group.get_parent() is paned assert paned.get_nth_item(0) is g1 assert paned.get_nth_item(1) is self.group docklayout.add_new_group_above(self.group, g2) assert self.group.get_parent() is paned assert g2.get_parent() is paned assert paned.get_nth_item(0) is g1 assert paned.get_nth_item(1) is g2 assert paned.get_nth_item(2) is self.group def test_placement_below(self): g1, g2 = DockGroup(), DockGroup() docklayout.add_new_group_below(self.group, g1) paned = g1.get_parent() assert isinstance(paned, DockPaned), paned assert self.group.get_parent() is paned assert paned.get_nth_item(0) is self.group assert paned.get_nth_item(1) is g1 docklayout.add_new_group_below(self.group, g2) assert self.group.get_parent() is paned assert g2.get_parent() is paned assert paned.get_nth_item(0) is self.group assert paned.get_nth_item(1) is g2 assert paned.get_nth_item(2) is g1 etk.docking-0.2/lib/etk/docking/tests/test_dockpaned.py000066400000000000000000000411251175471717700232510ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . import unittest import gtk import gtk.gdk as gdk from etk.docking import DockPaned, DockGroup class TestDockPaned(unittest.TestCase): ############################################################################ # Test properties ############################################################################ def test_prop_handle_size(self): global notify_called def _on_notify(gobject, pspec): global notify_called notify_called = True dockpaned = DockPaned() dockpaned.connect('notify::handle-size', _on_notify) notify_called = False dockpaned.set_handle_size(1) self.assertEquals(dockpaned.get_handle_size(), 1, msg='get_handle_size method did not return expected value') self.assertTrue(notify_called, msg='handle-size property change notification failed when using set_handle_size method') notify_called = False dockpaned.set_property('handle-size', 2) self.assertEquals(dockpaned.get_property('handle-size'), 2, msg='get_property method did not return expected value') self.assertTrue(notify_called, msg='handle-size property change notification failed when using set_property method') notify_called = False dockpaned.props.handle_size = 3 self.assertEquals(dockpaned.props.handle_size, 3, msg='.props attribute did not return expected value') self.assertTrue(notify_called, msg='handle-size property change notification failed when using .props attribute') dockpaned.destroy() def test_prop_orientation(self): global notify_called def _on_notify(gobject, pspec): global notify_called notify_called = True dockpaned = DockPaned() dockpaned.connect('notify::orientation', _on_notify) notify_called = False dockpaned.set_orientation(gtk.ORIENTATION_VERTICAL) self.assertEquals(dockpaned.get_orientation(), gtk.ORIENTATION_VERTICAL, msg='get_orientation method did not return expected value') self.assertTrue(notify_called, msg='orientation property change notification failed when using set_orientation method') notify_called = False dockpaned.set_property('orientation', gtk.ORIENTATION_HORIZONTAL) self.assertEquals(dockpaned.get_property('orientation'), gtk.ORIENTATION_HORIZONTAL, msg='get_property method did not return expected value') self.assertTrue(notify_called, msg='orientation property change notification failed when using set_property method') notify_called = False dockpaned.props.orientation = gtk.ORIENTATION_VERTICAL self.assertEquals(dockpaned.props.orientation, gtk.ORIENTATION_VERTICAL, msg='.props attribute did not return expected value') self.assertTrue(notify_called, msg='orientation property change notification failed when using .props attribute') dockpaned.destroy() ############################################################################ # Test child properties ############################################################################ def test_child_prop_weight(self): global child_notify_called def _on_child_notify(gobject, pspec): global child_notify_called child_notify_called = True dockpaned = DockPaned() dockgroup = DockGroup() dockgroup.connect('child-notify::weight', _on_child_notify) dockpaned.add(dockgroup) child_notify_called = False dockpaned.child_set_property(dockgroup, 'weight', 0.3) self.assertTrue(child_notify_called, msg='weight child property change notification failed') dockgroup.destroy() dockpaned.destroy() ############################################################################ # Test signals ############################################################################ def test_add_signal(self): add_events = [] def on_add(self, widget): add_events.append(widget) dockgroup = DockGroup() dockpaned = DockPaned() dockpaned.connect('add', on_add) dockpaned.add(dockgroup) self.assertTrue(dockgroup in add_events) dockgroup.destroy() dockpaned.destroy() def test_remove_signal(self): remove_events = [] item_removed_events = [] def on_remove(self, widget): remove_events.append(widget) def on_item_removed(dockpaned, child): item_removed_events.append(child) dockgroup = DockGroup() dockpaned = DockPaned() dockpaned.connect('remove', on_remove) dockpaned.connect('item-removed', on_item_removed) dockpaned.add(dockgroup) dockpaned.remove(dockgroup) self.assertTrue(dockgroup in remove_events) self.assertTrue(dockgroup in item_removed_events) dockgroup.destroy() dockpaned.destroy() def test_item_added_signal(self): add_events = [] item_added_events = [] def on_add(self, widget): add_events.append(widget) def on_item_added(dockpaned, child): item_added_events.append(child) dockgroup1 = DockGroup() dockgroup2 = DockGroup() dockpaned = DockPaned() dockpaned.connect('add', on_add) dockpaned.connect('item-added', on_item_added) dockpaned.add(dockgroup1) dockpaned.insert_item(dockgroup2) self.assertTrue(dockgroup1 in item_added_events) self.assertTrue(dockgroup1 in add_events) self.assertTrue(dockgroup2 in item_added_events) self.assertFalse(dockgroup2 in add_events) dockgroup2.destroy() dockgroup1.destroy() dockpaned.destroy() def test_item_removed_signal(self): remove_events = [] item_removed_events = [] def on_remove(self, widget): remove_events.append(widget) def on_item_removed(dockpaned, child): item_removed_events.append(child) dockgroup1 = DockGroup() dockgroup2 = DockGroup() dockpaned = DockPaned() dockpaned.connect('remove', on_remove) dockpaned.connect('item-removed', on_item_removed) dockpaned.add(dockgroup1) dockpaned.add(dockgroup2) dockpaned.remove(dockgroup1) dockpaned.remove_item(0) self.assertTrue(dockgroup1 in item_removed_events) self.assertTrue(dockgroup1 in remove_events) self.assertTrue(dockgroup2 in item_removed_events) self.assertFalse(dockgroup2 in remove_events) dockgroup1.destroy() dockgroup2.destroy() dockpaned.destroy() ############################################################################ # Test protected api ############################################################################ def test_redistribute_weight(self): dockpaned = DockPaned() dockgroup1 = DockGroup() dockgroup2 = DockGroup() dockpaned.insert_item(dockgroup1) dockpaned._items[0].min_size = 20 self.assertEquals(1, len(dockpaned._items)) self.assertEquals(1.0, dockpaned._items[0].weight) self.assertEquals(None, dockpaned._items[0].weight_request) dockpaned._redistribute_weight(100) self.assertEquals(1.0, dockpaned._items[0].weight) self.assertEquals(None, dockpaned._items[0].weight_request) dockpaned.insert_item(dockgroup2, weight=0.5) dockpaned._items[1].min_size = 20 self.assertTrue(0.5, dockpaned._items[1].weight_request) dockpaned._redistribute_weight(100) self.assertAlmostEquals(0.5, dockpaned._items[0].weight, 4) self.assertAlmostEquals(0.5, dockpaned._items[1].weight, 4) def test_redistribute_weight_resize(self): dockpaned = DockPaned() dockgroup1 = DockGroup() dockgroup2 = DockGroup() dockpaned.insert_item(dockgroup1, weight=0.5) dockpaned._items[0].min_size = 20 self.assertEquals(1, len(dockpaned._items)) self.assertEquals(None, dockpaned._items[0].weight) self.assertEquals(0.5, dockpaned._items[0].weight_request) dockpaned._redistribute_weight(100) self.assertEquals(1.0, dockpaned._items[0].weight) self.assertEquals(None, dockpaned._items[0].weight_request) dockpaned.insert_item(dockgroup2, weight=0.5) dockpaned._items[1].min_size = 20 self.assertTrue(0.5, dockpaned._items[1].weight_request) dockpaned._redistribute_weight(100) self.assertAlmostEquals(0.5, dockpaned._items[0].weight, 4) self.assertAlmostEquals(0.5, dockpaned._items[1].weight, 4) ############################################################################ # Test public api ############################################################################ def test_add(self): dockgroup = DockGroup() dockpaned = DockPaned() dockpaned.add(dockgroup) self.assertTrue(dockgroup in dockpaned) dockgroup.destroy() dockpaned.destroy() def test_remove(self): dockgroup = DockGroup() dockpaned = DockPaned() dockpaned.add(dockgroup) dockpaned.remove(dockgroup) self.assertTrue(dockgroup not in dockpaned) dockgroup.destroy() dockpaned.destroy() def test_delitem(self): dg = DockGroup() dockpaned = DockPaned() dockpaned.add(DockGroup()) dockpaned.add(dg) dockpaned.add(DockGroup()) assert dg in dockpaned assert len(dockpaned) == 3 assert dg is dockpaned[1] del dockpaned[1] assert len(dockpaned) == 2 assert dg not in dockpaned dg.destroy() dockpaned.destroy() def test_append_item(self): dockgroup1 = DockGroup() dockgroup2 = DockGroup() dockpaned = DockPaned() item_num1 = dockpaned.append_item(dockgroup1) item_num2 = dockpaned.append_item(dockgroup2) self.assertTrue(item_num1 == 0) self.assertTrue(dockpaned.get_nth_item(0) is dockgroup1) self.assertTrue(item_num2 == 1) self.assertTrue(dockpaned.get_nth_item(1) is dockgroup2) dockgroup2.destroy() dockgroup1.destroy() dockpaned.destroy() def test_prepend_item(self): dockgroup1 = DockGroup() dockgroup2 = DockGroup() dockpaned = DockPaned() item_num1 = dockpaned.prepend_item(dockgroup1) self.assertTrue(item_num1 == 0) item_num2 = dockpaned.prepend_item(dockgroup2) self.assertTrue(item_num2 == 0) self.assertTrue(dockpaned.get_nth_item(0) is dockgroup2) self.assertTrue(dockpaned.get_nth_item(1) is dockgroup1) dockgroup2.destroy() dockgroup1.destroy() dockpaned.destroy() def test_insert_item(self): dockgroup1 = DockGroup() dockgroup2 = DockGroup() dockgroup3 = DockGroup() dockpaned = DockPaned() item_num1 = dockpaned.insert_item(dockgroup1, position=None) self.assertTrue(item_num1 == 0) item_num2 = dockpaned.insert_item(dockgroup2, position=-1) self.assertTrue(item_num2 == 1) item_num3 = dockpaned.insert_item(dockgroup3, position=1, weight=0.5) self.assertTrue(item_num3 == 1) self.assertTrue(dockpaned.get_nth_item(0) is dockgroup1) self.assertTrue(dockpaned.get_nth_item(1) is dockgroup3) self.assertTrue(dockpaned.get_nth_item(2) is dockgroup2) dockgroup3.destroy() dockgroup2.destroy() dockgroup1.destroy() dockpaned.destroy() def test_remove_item(self): dockgroup1 = DockGroup() dockgroup2 = DockGroup() dockgroup3 = DockGroup() dockpaned = DockPaned() dockpaned.add(dockgroup1) dockpaned.add(dockgroup2) dockpaned.add(dockgroup3) dockpaned.remove_item(None) dockpaned.remove_item(0) dockpaned.remove_item(-1) self.assertTrue(dockgroup1 not in dockpaned) self.assertTrue(dockgroup2 not in dockpaned) dockgroup2.destroy() dockgroup1.destroy() dockpaned.destroy() def test_item_num(self): dockgroup1 = DockGroup() dockgroup2 = DockGroup() dockpaned = DockPaned() dockpaned.add(dockgroup1) dockpaned.add(dockgroup2) self.assertTrue(dockpaned.item_num(dockgroup1) == 0) self.assertTrue(dockpaned.item_num(dockgroup2) == 1) dockgroup2.destroy() dockgroup1.destroy() dockpaned.destroy() def test_len(self): dockgroup1 = DockGroup() dockgroup2 = DockGroup() dockpaned = DockPaned() dockpaned.add(dockgroup1) dockpaned.add(dockgroup2) self.assertTrue(len(dockpaned) == 2) dockgroup2.destroy() dockgroup1.destroy() dockpaned.destroy() def test_get_n_handles(self): dockgroup1 = DockGroup() dockgroup2 = DockGroup() dockpaned = DockPaned() dockpaned.add(dockgroup1) dockpaned.add(dockgroup2) self.assertTrue(dockpaned._get_n_handles() == 1) dockgroup2.destroy() dockgroup1.destroy() dockpaned.destroy() def test_get_nth_item(self): dockgroup1 = DockGroup() dockgroup2 = DockGroup() dockpaned = DockPaned() dockpaned.add(dockgroup1) dockpaned.add(dockgroup2) self.assertTrue(dockpaned.get_nth_item(0) == dockgroup1) self.assertTrue(dockpaned.get_nth_item(1) == dockgroup2) dockgroup2.destroy() dockgroup1.destroy() dockpaned.destroy() def test_get_item_at_pos(self): dockgroup1 = DockGroup() dockgroup2 = DockGroup() dockpaned = DockPaned() dockpaned.add(dockgroup1) dockpaned.add(dockgroup2) window = gtk.Window() window.add(dockpaned) window.show_all() child1 = dockpaned.get_item_at_pos(dockgroup1.allocation.x + 1, dockgroup1.allocation.y + 1) child2 = dockpaned.get_item_at_pos(dockgroup2.allocation.x + 1, dockgroup2.allocation.y + 1) self.assertTrue(child1 is dockgroup1) self.assertTrue(child2 is dockgroup2) dockgroup2.destroy() dockgroup1.destroy() dockpaned.destroy() window.destroy() def test_reorder_item(self): dockgroup1 = DockGroup() dockgroup2 = DockGroup() dockgroup3 = DockGroup() dockpaned = DockPaned() dockpaned.add(dockgroup1) dockpaned.add(dockgroup2) dockpaned.add(dockgroup3) dockpaned.reorder_item(dockgroup3, 0) dockpaned.reorder_item(dockgroup1, 2) self.assertTrue(dockpaned.item_num(dockgroup1) == 2) self.assertTrue(dockpaned.item_num(dockgroup2) == 1) self.assertTrue(dockpaned.item_num(dockgroup3) == 0) dockgroup3.destroy() dockgroup2.destroy() dockgroup1.destroy() dockpaned.destroy() ############################################################################ # Test parent/child interaction ############################################################################ def test_child_destroy(self): dockgroup = DockGroup() dockpaned = DockPaned() dockpaned.add(dockgroup) self.assertTrue(len(dockpaned) == len(dockpaned._items) == 1) dockgroup.destroy() self.assertTrue(len(dockpaned) == len(dockpaned._items) == 0) dockgroup.destroy() dockpaned.destroy() etk.docking-0.2/lib/etk/docking/tests/test_dockstore.py000066400000000000000000000077411175471717700233240ustar00rootroot00000000000000# vim:sw=4:et:ai import unittest import gtk from etk.docking import DockLayout, DockFrame, DockPaned, DockGroup, DockItem from etk.docking.dockstore import serialize, deserialize, get_main_frames, finish class ItemFactory(object): def __call__(self, label): return gtk.Button(label) class LoadingTestCase(unittest.TestCase): def test_serialize(self): win = gtk.Window(gtk.WINDOW_TOPLEVEL) layout = DockLayout() frame = DockFrame() win.add(frame) layout.add(frame) paned = DockPaned() frame.add(paned) group = DockGroup() paned.add(group) item = DockItem(title='t', title_tooltip_text='xx', icon_name='icon', stock_id="") item.set_name('fillme') group.add(item) s = serialize(layout) assert ''\ ''\ ''\ ''\ '' == s, s def test_deserialize(self): xml = ''' ''' layout = deserialize(xml, ItemFactory()) assert 1, len(layout.frames) frame = iter(layout.frames).next() assert frame.child paned = frame.child assert len(paned) group = paned.get_nth_item(0) assert isinstance(group, DockGroup), group assert len(group) item = group.get_nth_item(0) assert isinstance(item, DockItem) button = item.child assert isinstance(button, gtk.Button) assert "fillme" == button.get_label(), button.get_label() win = gtk.Window(gtk.WINDOW_TOPLEVEL) win.add(frame) win.show() while gtk.events_pending(): gtk.main_iteration() #assert frame.allocation.width == 200, frame.allocation.width #assert frame.allocation.height == 120, frame.allocation.height def test_deserialize_floating_windows(self): xml = """ """ layout = deserialize(xml, ItemFactory()) assert layout assert len(layout.frames) == 2, layout.frames frames = list(get_main_frames(layout)) assert len(frames) == 1, frames win = gtk.Window() win.add(frames[0]) finish(layout, frames[0]) main_frames = list(layout.get_main_frames()) floating_frames = list(layout.get_floating_frames()) assert len(main_frames) == 1 assert len(floating_frames) == 1 assert floating_frames[0].get_toplevel().get_transient_for() is win self.assertEquals(0.45, main_frames[0].get_children()[0]._items[0].weight_request) win.show_all() self.assertEquals(0.45, main_frames[0].get_children()[0]._items[0].weight) etk.docking-0.2/lib/etk/docking/tests/test_settings.py000066400000000000000000000012611175471717700231560ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai import unittest from etk.docking.docksettings import DockSettingsDict class TestDockLayout(unittest.TestCase): def test_settings(self): settings = DockSettingsDict() s = settings['gid'] assert s.auto_remove is True assert s.can_float is True assert s.inherit_settings is True # On subsequent fetches, get the same settings. assert s is settings['gid'] s2 = settings['other-gid'] assert s2 is not settings s3 = settings.get('other-gid') assert s3 is s2 settings['other-gid'] = DockSettings() assert s2 is not settings['other-gid'] etk.docking-0.2/lib/etk/docking/util.py000066400000000000000000000047741175471717700201060ustar00rootroot00000000000000# -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . import gtk def rect_contains(rect, x, y): ''' The rect_contains function checks if a point, defined by x and y falls within the gdk.Rectangle defined by rect. Note: Unlike rect_overlaps defined below, this function ignores a 1 pixel border. ''' if x > rect.x and x < rect.x + rect.width and y > rect.y and y < rect.y + rect.height: return True else: return False def rect_overlaps(rect, x, y): ''' The rect_overlaps function checks if a point, defined by x and y overlaps the gdk.Rectangle defined by rect. Note: Unlike rect_contains defined above, this function does not ignore a 1 pixel border. ''' if x >= rect.x and x <= rect.x + rect.width and y >= rect.y and y <= rect.y + rect.height: return True else: return False # TODO: Should change/add on this 'cause it does not work well with IconFactories for example. def load_icon(icon_name, size): icontheme = gtk.icon_theme_get_default() if not icontheme.has_icon(icon_name): icon_name = 'gtk-missing-image' return icontheme.load_icon(icon_name, size, gtk.ICON_LOOKUP_USE_BUILTIN) def load_icon_image(icon_name, size): icontheme = gtk.icon_theme_get_default() if not icontheme.has_icon(icon_name): icon_name = 'gtk-missing-image' return gtk.image_new_from_icon_name(icon_name, size) def flatten(w, child_getter=gtk.Container.get_children): """ Generator function that returns all items in a hierarchy. Default `child_getter` returns children in a GTK+ widget hierarchy. """ yield w try: for c in child_getter(w): for d in flatten(c, child_getter): yield d except TypeError: pass # Not a child of the right type etk.docking-0.2/setup.cfg000066400000000000000000000004111175471717700154110ustar00rootroot00000000000000[egg_info] tag_date = 0 tag_svn_revision = 0 [nosetests] tests=etk.docking,doc with-doctest=0 verbosity=0 detailed-errors=1 with-coverage=1 cover-package=etk.docking [build_sphinx] source-dir = doc/reference/source/ build-dir = doc/reference/build/ all_files = 1 etk.docking-0.2/setup.py000066400000000000000000000073641175471717700153200ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # vim:sw=4:et:ai # Copyright © 2010 etk.docking Contributors # # This file is part of etk.docking. # # etk.docking is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # etk.docking is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with etk.docking. If not, see . import os import re import fnmatch from ez_setup import use_setuptools; use_setuptools() from setuptools import setup, find_packages def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() def version(): file = os.path.join(os.path.dirname(__file__), 'lib', 'etk', 'docking', '__init__.py') return re.compile(r".*__version__ = '(.*?)'", re.S).match(read(file)).group(1) def _get_data_files(dest, src, filter): path = os.path.abspath(os.path.join(os.path.dirname(__file__), src)) files = [] if os.path.isdir(path): for item in fnmatch.filter(os.listdir(path), filter): if os.path.isfile(os.path.join(path, item)): files.append(('%s/%s' % (src, item))) else: print 'get_data_files: "%s" does not exist, ignoring' % src return files def get_data_files(*args): data_files = [] for (dest, src, filter) in args: path = os.path.abspath(os.path.join(os.path.dirname(__file__), src)) if os.path.isdir(path): data_files.append(('%s' % dest, _get_data_files('%s' % dest, '%s' % src, filter))) for item in os.listdir(path): if os.path.isdir(os.path.join(path, item)): data_files.append(('%s/%s' % (dest, item), _get_data_files('%s/%s' % (dest, item), '%s/%s' % (src, item), filter))) else: print 'get_data_files: "%s" does not exist, ignoring' % src return data_files setup(namespace_packages = ['etk'], name = 'etk.docking', version = version(), description = 'PyGTK Docking Widgets', long_description = read('README'), author = 'etk.docking Contributors', author_email = 'etk-list@googlegroups.com', url = 'http://github.com/dieterv/etk.docking/', download_url = 'http://github.com/dieterv/etk.docking/downloads/', license = 'GNU Lesser General Public License', classifiers = ['Development Status :: 4 - Beta', 'Environment :: X11 Applications :: GTK', 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', 'Natural Language :: English', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Programming Language :: Python', 'Topic :: Software Development :: Libraries :: Python Modules'], install_requires = ['setuptools', 'simplegeneric >= 0.6'], zip_safe = False, include_package_data = True, packages = find_packages('lib'), package_dir = {'': 'lib'}, data_files = get_data_files(('doc/examples', 'doc/examples', '*.py'), ('doc/reference', 'doc/reference/build/html', '*')), tests_require = ['nose'], test_suite = 'nose.collector') etk.docking-0.2/setup.sh000066400000000000000000000010121175471717700152620ustar00rootroot00000000000000#!/bin/bash # Configure the path to your Python interpreter installations. INTERPRETERS="/d/bin/Python27 /d/bin/Python26" # Build documentation /d/bin/Python27/python setup.py build_sphinx # Build source distribution # There's a bug in gztar on windows: a "dist" dir is created in the .gz # part that contains the .tar part. bztar does not have this bug. *sigh* /d/bin/Python27/python setup.py sdist --formats=bztar # Build eggs for INTERPRETER in ${INTERPRETERS}; do ${INTERPRETER}/python setup.py bdist_egg done