gquilt-0.25/0000775000076400007640000000000011516672673013255 5ustar peterpeter00000000000000gquilt-0.25/COPYING0000664000076400007640000004311011413455575014303 0ustar peterpeter00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) 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 this service 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 make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. 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. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the 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 a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE 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. 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 convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision 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, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This 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 Library General Public License instead of this License. gquilt-0.25/setup.cfg0000664000076400007640000000041211476544755015077 0ustar peterpeter00000000000000[bdist_rpm] release = 1 packager = Peter Williams doc_files = COPYING copyright group = "Development/Tools" requires = python >= 2.6 pygtk2 pygtksourceview pycairo pygobject2 quilt icon = gquilt.xpm build_requires = python >= 2.6 gquilt-0.25/gquilt.desktop0000664000076400007640000000030411451033522016130 0ustar peterpeter00000000000000[Desktop Entry] Encoding=UTF-8 Name=gquilt Patch Manager GenericName=Quilt Patch Manager Exec=gquilt Terminal=false Type=Application Icon=gquilt Categories=GNOME;Application;Development;X-Fedora; gquilt-0.25/PKG-INFO0000664000076400007640000000173111516672673014354 0ustar peterpeter00000000000000Metadata-Version: 1.0 Name: gquilt Version: 0.25 Summary: a PyGTK GUI wrapper for quilt and/or Mercurial Queues (mq) Home-page: http://gquilt.sourceforge.net/ Author: Peter Williams Author-email: peter_ono@users.sourceforge.net License: GNU General Public License (GPL) Version 2.0 Description: Quilt (quilt) is a tool for managing patches. Mercurial (hg) is a distributed source control tool and Mercurial Queues (mq) is a patch management tool extension to hg. gquilt is a PyGTK GUI wrapper for quilt and/or mq. Platform: UNKNOWN Classifier: Development Status :: Alpha Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU General Public License (GPL) Version 2.0 Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Source Control Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX gquilt-0.25/setup.py0000664000076400007640000000444311516672051014762 0ustar peterpeter00000000000000#!/usr/bin/env python ### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from distutils.core import setup import os import glob NAME = 'gquilt' VERSION = '0.25' DESCRIPTION = 'a PyGTK GUI wrapper for quilt and/or Mercurial Queues (mq)' LONG_DESCRIPTION =\ ''' Quilt (quilt) is a tool for managing patches. Mercurial (hg) is a distributed source control tool and Mercurial Queues (mq) is a patch management tool extension to hg. gquilt is a PyGTK GUI wrapper for quilt and/or mq. ''' pixmaps = glob.glob('pixmaps/*.png') PIXMAPS = [('share/pixmaps', ['gquilt.png']), ('share/pixmaps/gquilt', pixmaps)] COPYRIGHT = [('share/doc/gquilt', ['COPYING', 'copyright'])] LICENSE = 'GNU General Public License (GPL) Version 2.0' CLASSIFIERS = [ 'Development Status :: Alpha', 'Intended Audience :: Developers', 'License :: OSI Approved :: %s' % LICENSE, 'Programming Language :: Python', 'Topic :: Software Development :: Source Control', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', ] AUTHOR = 'Peter Williams' AUTHOR_EMAIL = 'peter_ono@users.sourceforge.net' URL = 'http://gquilt.sourceforge.net/' SCRIPTS = ['gquilt'] PACKAGES = ['gquilt_pkg'] DATA_FILES = PIXMAPS + [('/etc/gquilt.d', ['gquilt_pkg/qbsfe.sh'])] setup( name = NAME, version = VERSION, description = DESCRIPTION, long_description = LONG_DESCRIPTION, classifiers = CLASSIFIERS, license = LICENSE, author = AUTHOR, author_email = AUTHOR_EMAIL, url = URL, scripts = SCRIPTS, packages = PACKAGES, data_files = DATA_FILES ) gquilt-0.25/MANIFEST.in0000664000076400007640000000027711507206720015003 0ustar peterpeter00000000000000include COPYING copyright include MANIFEST.in include Makefile include gquilt gquilt.desktop gquilt.png gquilt.xpm include *.py include gquilt_pkg/*.py gquilt_pkg/*.sh include pixmaps/*.png gquilt-0.25/pixmaps/0000775000076400007640000000000011516672673014736 5ustar peterpeter00000000000000gquilt-0.25/pixmaps/stock_finish.png0000664000076400007640000000610611451033514020111 0ustar peterpeter00000000000000PNG  IHDR@@iqPLTE۶mmmIII$$$mI$mI$mmII$$mI$۶mmII$$mmII$$۶mmmIII$$ےmmIIm$$mmII$$mmII$$II$$$$۶۶mmImI$I$ےmmII$m$mmII$$mmII$$II$$$$ےmmIIm$$ImmII$$mmmII$$mmII$$II$$$$۶mmmIII$ےmImm$mI$mI$I$$۶۶mmImI$Iے۶mIm$mm۶I$mI۶$I$$ےmImm$IImI$mmmI$mI$I$$۶۶mmImI$I$ےmmII$m$ےmImm$II$mIm$Im$۶ےmmII$m$ImmII$$mmIm$Im$ImIm$I$mے۶mIm$mII$mےIm$Im$ےmmIIm$$ImmII$$mےmImImI* IDATxlS?RHAkRܩ*]W궨SKiSSnMNTjִ$*M+NeZT BB$vbサ?K8`l?{={eCd` ` ?5^b@hr` F9sY X'njuL?!1)CcGƎ([iKٯ t-in8k.K|P8L@o]x{V[8j/n%.+S34>r^Gq^6`& x`6eJ^7265NLoꥷLcK𶩣m.˭aËU'qYs`7mO1P=JΓS"<خw`f~W/H`WMMYgt_XB>$ӤJZt?mh)B0__[ED\Bζs ~*eH(q5`#!kC8ڗa|-%$Ύ(FJ>(Ü*zmЙqBk*B4ݏP]]$d-l5I<O>oʪ>*KnSŃϕtN؛K圔MgymWW끝XoԊn3s% =Edc\D{KPwHƼIStߋPB%|*|v5 OJB J&PToI`h#6PK#TRF+˭>{y'z3"U'7>s_5H`65ll1`*76B4%sisN_Z>zP l i)g5!FZRpc7NњRlglBJijdwd3p8lˠ/oi0<5vTrR2UKqg x%kpᶻpٝL;q8mvvai1oیPߊOxs T<)"$Gteǀꢢ`,GQ}h h*7>.*&,m797؊PeMRV)6~rx?XUV$IlD;^E+ kⷎgoǙWwRR\ѥ0Iplo;|4gʛN/음%{qG=E k?` xX/>{ E3OیvVDnjh2N,y2v|~oQbiFZ`މRR1yi[f͟3y( {&qf뽢bKSc'sUK"ʛh-xq /z6^ +_Q]l>@AV;ךF (P@ (P@_;0 ٹX7N\B2g,BG!7FE6e#R';1$ӹN"  wmي2:U_~r^ Ɯ9t l'N[|q't0zXh\ ?~0/% /,'ᰒCvk6~ac^"Ex;/L-;++#VēS&ߌU ۶x2=p(\ʦk'EEX>N1f_GTS.vΖزjef&E+YݮAߩ%zKTK@'d=}ʙ7 HM4Ngy?mI Hɭ?z)h5K{t*zp͕}{~!J|ywXqF2u0JL㼄9BPjIv'@OZF8E1ma% m^91a [O @ڰ͛G@z{KbYeh%2zӑb6VihBMw1ʔ!2&=~ qX]&X5A dx>`E!e5Q4mhX`]*ϡ;TjBxJm=@M|TW%@B' :CL>8ؠSO`fZͲ)JIg @V`/J#D~<Q3jt \/튥}!kaza x $N" ,]*#BL2qs9m~r|@3x 8b&Xn+bK6C7^qιN}Z*4% *zl)d3BO:f.ko+,ӚrQ Td쇢8{Oc0bsP/S5 /͕<O<~:ЋPV"XqІ*r[gsY|d?0i+$J6?j6i[q]OpqH65XZ!MEsukpÉ W2OEyvFԱ||͛A|N4< FvqFl۶m#?T8@^f_|Ŷ ޯq{9b)q[>u۫/+c]*m՗ˁWGUK i2Voߞ$WWWaoЬͺ.oS w߭ӥ<5gE4IX&[־3_>BzR pWx~; {篭k6o|JɮA&"1<ƒxx,F21M}>A3ODQ[xqA!ּ3t֬+ɖ|aUTTtR;c- /˥]#E ͅ%F_sTdS"h@*|q@2u]w_ Hy)rmi*ykQXX"?ˑ)W] |,1466$8@mۖǃaKKKS[ʛJe{Iܝ%@TWYٚ%~ۘ/Kk46i޽[luuux)e*1@}Kz ^<RW;|Lb-xJ4iݻif4M1p9z***m'W?c˛,yMJ CД:6;3`0H$)^o `0y{RD~*yGsR!RT_URR2Ry꬇6nE1 طoߴzGGGg-S~rȑ3&PeaɌAIENDB`gquilt-0.25/pixmaps/green_arrow.png0000664000076400007640000000403211451033514017734 0ustar peterpeter00000000000000PNG  IHDR@@iqbKGD pHYs  tIME2E}tEXtCommentCreated with The GIMPd%n~IDATx]lsf;6^Haj-nR.URQ/*mj{Xi[n *JYUYnUJ{04J*Ę$%,vD]E-\Ma޽w0VoD"={WL&=>O$}Ts8z({A4b4MٰaX @OOT~qΝ?̺(ccc[ر˗/G__HK###rtt%?NudR:<088hd2_r2\ΪX顡yW@6% buIcډOeY)O /x`.lڴip@;rHoڪf˱c:nzmtJ]|ihhǙ{K.8κV8q_N#5}\.-eYL)9;ԪLbjɶ۶?wsܺf,|o >,YGKK ǏOVo9seikP $!Jg[9~৮x>_tW;@ݪ~F3p DT ]hhH!}GG}˟13o&uCB/ 44[ٌFp|X`,foULFpgSIPKdALѦGQ@+^ h;tB U7e27! Z ܎${(囒_4ȻGz&1=F1u++=|XlDŌϐ4TЅD"DP8ʡ |:)( +U׉Bw_ Wox& B(Ƌt)?b#Jsp0L­ /wS/v/B'i4f ځ߬4.u"?LLL1E cB@Ju0tck~+_G|.$C"LQ&0Х܍+Wx0ܷr1P XM" Be Bd) edG?"nƃ d]@.oT<6y''r9oFboŞD]Nm4H&fK+,*($ ܺg:B2koV".*gҭ%DԜs?t/) ߝBvkB~fTX@_@/M ;1G0-~d7 Q.G\V;+腅A"2AJI]njZL!RH4J!dLi,6:@^G Y)ͩ XxPWg*^SΎr("EU (GհJ>I5tNbI9lUI^s4meiASZqG H% O 1jN䟓\ye-0!j㔾3w_Լ4BP:ꨣ:x DhIENDB`gquilt-0.25/pixmaps/stock_import.png0000664000076400007640000000614511451033514020146 0ustar peterpeter00000000000000PNG  IHDR@@iqbKGD pHYs  tIME{NtEXtCommentCreated with The GIMPd%n IDATx{lS?vBR0-huclkuJUQմnVE؋tCJuQGI_,Z XنVRNiK£$&c?|m\:ht{} qf@שk=)?l~z^rT.]ۗr^JnZ_JGjմFvk$ Fã(Jr5Xu\5YM>UiʻdU*Фx!z /"jkUX$TR r2@ۯb0㻎[ʿ¦qꗧPv?Nw|+W)ZFŰ2Y -Y"gܻ^ӦMۗ*^dC?loY[Kwv ̧̺z6m:%B0a 1T+&>=!ȱ]-%s=vwtt,= ?ZQ=(wqwy\Y&f`ݣ' :~;MVSMvh䥷GmX{ǹ*,=F?֭[TWWv\TVV?|vmkD9 ~+WuߪuR~>W_Y@7ŻKYn Y̢rM2 4t]% 1zc B|'Zno@Ͷfڵ| qM]>@RS{u̮BH:uF)Uh^ЈԹ][O1Zy2-XzB'\}m S\uVSROp466:sM62=byoV+J7aQb B^W) eij7-iaghp< `^Hgdzt2E_{^}a![hTA)Ho)۝8Cߡ~y4s@gȲf2l6[OMĈcD "baB0mAb?,`F@ c.34B+le) $>`$!G "hK]#g;bŻˆoiy-QpSDܝ#^~d2B5~;l Y.긡a.A9K^Α;KӊZ:T@H}w(JK _RG$ Vך΁#xJ3`46/)8TN30 `FؚVVV6'w\qH$pX^9ט&iIS30; 1rً1N}?obg1 GxƳ6tZϷ,n6޽B͗u^~лGOpT桡ʪ^i!y cRI* n?o>5+B)8{H*8%$P$zqv{ %U-[󽥥b}NJU 54Fxw Zt]&q>hO Wcs8?o4GZ,:n&,KVXeZ\lkq*CtܨJJf↛ԛ18,_r`oShA\ pZYY!G@L D*`XU1E'+xp0R)`>o,g>|߬Zi'߽uabJ*p 8\___ݍX ! fm=F3Օo/bf)mk@gB;Cƞ8 }Ғؾ}M6՛M2g4#?N0JRWWmii.ݓ03Tx_N}*QBNfs {Rw836tبVÎ#t |r<x]lG:N(Du^0$ee/Ǟo;c,yԏ2%<4}6F ~,EiR` YIe!5FYf=Q굴خ}6~?o_N֡uqlW{F v!@5g6X$`pIuq%E7X YduvjMbűq; '^jy>W!%I%HTjs|֛>. AqVFѪ @ ܻQ|J1&P3FVF)ϊ86!K#=@*>:\/q0@+I$R#T4{-ݦO~xppf+z.qG&plC@(-N&ZOgc{U9e7`1eB Kss$PA! :}ɴzC3?/B*j͵ѹ߃sy D0h,2eZ~lYrtPe2Yguԇ먏wCc=\͠Ub fg/Z$k+uEκ5bҮ#j'IENDB`gquilt-0.25/pixmaps/stock_cross.png0000664000076400007640000000410311451033514017755 0ustar peterpeter00000000000000PNG  IHDR@@iqsBIT|dIDATx]lWkDZ]8uq>m֭4M"-5 !I$@HATCZԆ*JӤcvl7+zg?flvzgu~x9؏,@mev h+ A[Y@V h%\x4]/ހ)8EDB\camضmLqy^8zzѻxz~펍㥘 6[֭jޥ^hkcf᫪87h_քX&b0= >JQP a~Rjj]8s cϷ"/a7wmjB,KבYB]4}![ŝ,0041X(Edx/;ϵEDxylWwumZYX 5,wJ&} &'9}8DU(%y+p>[>G^ko>*Oeϛ>FG|.kj8m.FAAX.݆%\"Dkyp0xkŵѝH[j^j]K]8sB;`?*9dm-*X=۝^ߜCglv۶p5WDCkaNwpY_s=FBd&9OoU-$|Va<;:\?~s_ ER׀,ٹjef:$A_9>_bq)B}';VIqewC^_~2>9"R S>Ζ2 !B/sxzbƍE(T;?ˎOu@_'dyp(swdݟljc*x`U8Ċ²LϑyI]aef,+sߟ=e{qyUuW:W?21x!d1x?Q[ ,NTw_hk!2Q$KX~ޓHrG׏T#WN `/St)j*GE4ILLrC9GDG)qwXWo:wƙ$qG+#%D,Tlγ{̞dGGFsA;l&߻2q4(SKFO eYDBO2ţ#c);$o48)Df,Tɞ^fIdˈFƘ+ XF~䇓*/{PgZTK>A{$ގynr9Pxe -hEKkK84g%p)-zQU9q~mDB,rb.ſr y|F[`fQ] o =A;ځ,@mev h+ Ajv1IENDB`gquilt-0.25/pixmaps/stock_patch_guard.png0000664000076400007640000001157511476545002021127 0ustar peterpeter00000000000000PNG  IHDR@@iqsRGBbKGD pHYs B(xtIME 0$>sIDATx[yWycgwvwvW:VNl p$6GJQ sJ`)#N V|ɦe˖%`Y IZiW~Ǘ?gvv5kKڵM ~}?샽R_|wvqj㟺:!~ji)m~ BEz1 V|[)_X/r&.&}ʲUI7AJ3[][9}Q__%َNqPk(Kb1kbwM^p߶Rߑt;Jw=`F\l~R~Vj~mv SwVynKt]C>v ^.H\.mdY?1qrL_;НFەVdi]טy@`CtZSTٮϫ`rjŪ+ѻtC~FX蟚H=@w8;[oˀ+Վ uvvnu6vgL9`'NM626ضM``300c*V*bt3QfGgם|Ep]D(J뤢uLR7$?M&SV6EW " % D|+9\ׅpR 2@v\[6lvem 2<'ѯξ, z!mmBuŮ{YȚ}+x= 9'%%cL ²86v| /5\^z VZZVq `, \pfATD\jUh}m6C=np5綾F 0o#FW̫D= uVR>8B@91K͛q X2Ơ0qlSmVnū%ד<+0, ;;Cn^kyL'S듩`2tǁ"#c̊g]`lh|pTZ9h=Y)(! 1m ֐a 5~3Dž\6J?e׾Ob֠`b$bv\ i$In18mmö,a y19#B7H_V"#C*WP. "tl)#>hIy3Zˌ<CKm&Pex$Ec_~ց=l ˊ˕h˂ υap߮BgϢNL$"l~f<[Qv,t[CCkL)X2&iwӍc sG{֖Hhϴ-D"[ !v\pLO`d`YJ(k TS but1_T@c tt1-gR>h7H@6"`;9; (JQN2n2"! H)`0N-/4F hh !؎8PR1o(O_*u:&@k "cA# -xDAK0 ]:Ly<"?K 2Cqa1)Uș8_9c_[H&>,aE,}m50KB7RO|zLcD&XDDdARRi!cL]v%]ful "׬dU[<L#?9ݻvcMRB|P9E<}fX[q*Νaxk+< xƅ֞OUVg]jϵJh)T戁TRJAp\*5@Mj;&&c"?CY\y}O1!gM@}[ 5$dRJXE!b?Aí01B-Ythc 3PJB*[Jxmw~nb1؋PirfEʊ"K.+8Jf3tT8Fb[ 3Ϝ0ԍ1t k9ki" ՕBdlva~|# _(b|cK>EJ $B L2:s.AlR f)́$Dtn:R*- f㓡egk44OeYJRc`2q-_-f>Mc%Rg$f@q,6PJ5jˁIR-O?[Gb7_<)G(_ߟL&!!Ub J8ǖ-`wWכD;oAZunM(mܴO~sG#2( 踨%G,|TE_r7'h_[O~>pކ 1m;z(?}aZvۭa/ Exg}1@cZkv~PCūC?Lj=s7EJ%/i2@ *DT:UOG280dh.QDh"o8\`+lLcK&p1_ +_˷.}b9@vlt|EzPvuvl6˂ )C0Xc`p]0U(A68GP䴧lݾ}<߰H$Jry(&zmV[&0dZ+""P9\7޽kV"Ӟ"Bͯ֊ٶvP0===51>~kSOe&{gَ>ߴy#gxߚՃܲ YJbq-KX(bv6޾>e @Tk9 g166V;yr[21fQ=Iç mJb?W|~h hBibc 2ذq`j~WAʱD" ʼnwx]ĹL3%L Mo61 0'Nȧө"(L,>1odx'v~'vh8o2ͼԙz1"R98:r4NZ5eYV.n&ӎZX*"}X6L!ɣ#FFF~=d_ł2ȾN.f L~xqt _')5V̶lA#ZFO{:r#GF?gi[5G->$m899>tx*PJ92|~Fbvv<~#{|{wB&6-Ηu Zk/&EVkzkƘbqrjG1zXxl;p>gCZki*6V'~/ Z sdV l9/\OυniUͯ;*r=QU~.>3ql ^0؅-G=T7lSVʻw97@D5=LpA -nȷ2LppF̢c$ϧzC{@P?O^ȼ3+R[_JʙS@^/7*eDDDDDDDDDDDDD ,HCIENDB`gquilt-0.25/pixmaps/red_arrow.png0000664000076400007640000000431011451033514017405 0ustar peterpeter00000000000000PNG  IHDR@@iqbKGD pHYs  tIME''MtEXtCommentCreated with The GIMPd%n,IDATx[leY_pL0-p rE(J}hSH}7P+HmQDKV%HXPeLUL5/{s1m|fwv VqV0^tB@(øLdы@ӨsÇr@qq? ΝƍD%[Ϯ?/c6jSNq! @ ^?v x,`\v zEKKˢ-Kvww^T3PF峞f`^蘱ɥRh4`s ! mmmsÜ X ˲"Hu,4̤*$ƫT\Q9KH$< =P)H$B$򤔦!7qTwwwOĚNYaOOe 6y^<AN>ޖ-[>SfYZZIcr`*\rʂn.//?3f@+.4O Czmaao~QP8~mT֭[ضMYYl9j^s156+**&gzȐ2-tKGY3P1.Fkp ~b?s=CjJ&!rrhZ'C^CZFCgo E=1v;y›ژcu( 1/SoF1pXw¿MB)176yy:{֡x>]^a̹0_i/]DP !B!8xu+FGr-QP복8|Gs lȆ_&[k)2Mo\Zk7ZXã/t~>qpC!#f lN]ϕH!6;s.FĀ˗Oh+<S?$5]/)/W"wݑ ij,a/T 6}:3)aZFি,MDP܆92@?leej^t"e6GV[yT$'.bx;{{a#YCJjԹ^)3Cd30Ɍ#y~֨h#r@j | Lbu \Gݿ`~Q߻C 7Ktbd+TJggz>wu-Z*IeԀ猀y!XD]ՐS%I_/  ) !)#A-χm.ٙFL ˜4Y&`[}BB#&+X V|>߰ZIENDB`gquilt-0.25/pixmaps/orange_bent_arrow.png0000664000076400007640000000406611451033514021126 0ustar peterpeter00000000000000PNG  IHDR@@iqbKGD pHYs  tIME1%kކIDATx_L[ 8!M ] +K6*J4{C51-qEUS=-3SuꨴVH%4,mlu(Ia͒BHgט& 6#]aw}= nBrX- `6SV6%9:UE iU iOjG+Ub*Ĝ;vh8SN28)OՌcF5${G$6/ݚo±k|zAڻi9{MfQ (`vukW妼R?=:q pVXp63 9uzP C@[Ռ)K%c%}apbSO1.˚o\3{a[=#0[t0JP`HR*M=_@|Qx?R :ڞS~K>4@~ޫ>6W_W@F*(3} V3-mOW&&\ ;*r_HzN VKI -?nXt kQ$ͯ^uHzPd8܌+hx ER@~bV= /DLy)I^$!5(a?Fe^a>1BrtfnsغX}-q;C #1WPJ>Tw។bŹYrGBUN"AJ/>UcdRy8IM v&-J&X q0;Pjc3o*\L\̅kdOfڂX,5Ie,Knw֩~nοo'l/+/i1h^#r R`d4bŜ&uSJ؞;!MtMm;٬qD1sN[G2r(}}5Ttſ*N;Q5eISjԞj {쀫nx'dJӪ|`ͧA[hXՁ 5fĎU.ʻe꺟4n ^o%%D-7"7Z#]tՕ-$(ScR Q^PhDHA25XwbЃ!_ }AD7Pc!ˆ@X~+oKrUZrqxpUcwݸ}(VT/ONǍ-S|wT7WJw?~Px]9>DOsH)97(ǧҍxO=y7‹c[e`)pd(Ȅ}|>|*@x}CA}O+# ʂ##x|0;#*A ݱ$I -=fwWRX"RH @~k}Snd !y CWf[ 7KWϝsn"OyO/;uy0D&s5\T/4=DE90Ȉy WYÆ\̓G;ƲY_zs9^2b aU|Pq(vkX D|*Pi 𕕕i7EA*?ꓪFi5Օ( YY#޲e%~6R^U^zǏ`ٽΦk{6{k k&L+zM p ՎUXzSꡎ[8W"1bvwRp >b.tZW4q̺48u̶?(ϛ7oT6uvvʮpWWWNS cR{jÞ hg􌴴օPq(VW;w@8se-ʁ&'3c36oiiE[>k5~.qmsUXLbX,s;:x .5'ݶ6dB2 |=W據'O0MJka:::8%K Ν;mɒ%a3`פO?@:u{eea:@}}OT(RhtJYj߮RZ˼m|ag eeel8wܤ= a0J0,++(Vjժa0~ȓ0Yaܳnݺ]:#"hWy7o۷ P4<l^z)Z__tzBx1lٲkMw? FG0 \__+]())644\+M6D9zMO??\:GUᖒGGq>=V TTT(0|错 M6y?D~@dǡٷϦ;͑GسaO3Pei08bHPD"57_8+N("; 8O|뉞 <:/Ko=@0,^Ѕ2@p)u e~97 ㎇rPi<NLzc/ ^u<ƒ.t -ʤ3щeϿP] 4a9 ^‹G@ p^}V;-r EwT߬||ަWx)xhhĿ(A P*LwJ^#<ttuw@a@Cwe Bo0ЅDf ,*A&.p-UlB}matPa[ !RTx#`[FZk̀+Ώ вkA!iZ!|X-t|Đi1tR JG#\Χ1Kŧ+?p^֊EnKpF@@t胳PCw7HPͶj "7?~O@K?~͏_i ;+u`'{87xn3+GGXS @5X,0h2 0ɠ$.:ϻkE+b^ Ž.l'W9"CN(L+U$ N3• mpL@:D׍TΖ ^!՚2O;+PƀGFq@t_%GE41LexbZ"3 nҎp=ײt5| ͮPtׂ%7Jg(O̔a d@HdWx ,y'/]S~Tk+6j>ߖPj)d!m6fc<cLXYk+Ei쇣?V,H/w%?ӌIߐN3o{ĥKR&;rӟH./FIr6so|1ǣ_RxQA)"J2ɶfQCU slJH @YÁg/`dd"A21M鎋;lř T&&fJ+Ak"IbvҊ wcRiLf\]]]qfaakwS4haU7-NOYIbc d0dKQV,eTIVJ05{5{΀J&L&i7v7(|MbZV:YhB绝W!hl6*~- ,/"_z=.3&J1 XEh"7ZAl ډZM(kR [y{eIENDB`gquilt-0.25/pixmaps/series.png0000664000076400007640000000176311451033514016724 0ustar peterpeter00000000000000PNG  IHDR@@iqbKGD pHYs  tIME 0CtEXtCommentCreated with The GIMPd%nWIDATx=lI,RW` 1 N!i [HuB,ѰE (H)kNQ@N0(!BO6~4kɠz-y?΂7B [m`P/'Y.6?stq+L(c@&,k }y.F|zNmh{H<WE7tH2vL""}M<[Sd ,T?e] 06&H&>Ȼ;d'FNXZd_loG~9zm6014xWe(=zjQ*VX{\pO;wݕe[.>(@bF7tgvyVr$L4Vuhh>>00X%v;Fs2X S2t]luL8_O̲߫h;m3 dJAݛ^J7tZ^veHbi9N@PǡV!w{<9~K*Lk։d,` %O 膾#,L,UJAD mibJ)g2zRd7|J(NxeYyw/jA`Ef5;_S=@P('e[{/d,z,2ضZ~򻯯b@Ɓ@{gYs|pð5os6e㻁u o{h\ bIENDB`gquilt-0.25/pixmaps/stock_refresh_patch.png0000664000076400007640000001260211476545002021453 0ustar peterpeter00000000000000PNG  IHDR@@iqsRGBbKGD pHYs  tIME .4tEXtCommentCreated with The GIMPd%nIDATxZg\Ց=3==9h$MPBBK$!1:q|X] xցd`06X#tH(Œ4:?g5A3a7V}nU tY:Ko> ־A\n a$bAi- Tb5y2q» _قB)OʰC?Ypuq؜ CGx;YƂ_9wR0X6` `(Q  LDb磽lΞHɦc'*vܰ??y ,f `vC #ǧa_wu=k_zR<4lж-͢8*$iԉD 7N%!h=DΧ ϔy?c[no.sOߩ}\fŘ6m'vLIR?2 "Ϯy˯\OQ rc{4 0s+I9mٴڟ*ri,L$'0 $J 3CG @_`6Gui"@iP }Ң@s@*@ju6('pёОDs|\0S_m e$ h"*OJUZ[:)hσa0, ye()󡬨 À Dx )L@V@(@FGc*C[al J[!Tv@UO lv\2Uz[pP.nj>r](%&x6?8rPPU3QQۀv^399Jm[%'< ZJ4L ) 0kom+PBQ‰yb LϕtHv[` 7ϿWS`;c [ۗ3/?_w'^믮cψŃ϶4=ԥ'_>|Ituu(?Ø;~_s@fݙNՑ)X8 WaH6˾7{߷ xlVƿ[]8# @vERX@0qǪ = tx @W J8{U)/1ʅ ݏ>+c.(J%bVNk18p+"(:PY,@$C$I-qSwͼ \3o(J`5/h8LLp2ljc`Zi[v$8ju"|`b}s\u r& P1kVJy8x@v,,& *0f֨װ,X50L  ~|C4tẩDOP?F4B1Т"lZ( =ݷVZS2=+ic .λ[3o,,yqwR)I-*53q$'TfcO s1TV l,Z4 #MʵUs\ÜξH.ُ@ Xҩǯ}~z4@0WzYxEɯ)ZìHvzuJU%9`wRZA*D hw fͲf(,I2>< |eA,y+dStMc~ydc &(`V~|f +1kaWm3=lfZ#xfS<UZ@,s`uC u(,y#F=3y *3u7^k=li햃qDM5#f``gsYR%YOK4W&n]7TC uuu/6sm8#lfm?cs)U˖$' *u!HI J@ Tr\nWͭ.xl 0 \"g޶}mm=MXYsyF{FÞ]h; x> ;@I\qk\#Zryl: I]%- ݆΅]$X@nCK7G;;{k{yJ8=<f._=3//W^q]^`:4cb0L*/e pi yCуǚGe{T7P?i~>gRm)omxa)t"a B-]tzm5-qyka@0@`IՠF\Ga-ݘY#!+IwYK=03lǴS;د6wI{ۦ̜;Kf౶i?b H`߲pa^um܂En[DpLNF nfXյhS%RxAR(R]% O*'X<ѝ9Ka贎ti㐵G +IENDB`gquilt-0.25/pixmaps/stock_fold.png0000664000076400007640000000477211451033514017564 0ustar peterpeter00000000000000PNG  IHDR@@iqbKGD pHYs  tIME!(tEXtCommentCreated with The GIMPd%n ^IDATx{l[?~ďƍ$mRC@ Zwii"džZ $i3 1lT4- mj:4H:-'~$~\{]!qK{#[{}wKnxzO_ۀ](oU` lVWwwⰔMN;4Fg_K^{!j< v *hC~>`X~ <|!贛tky sOכ"|+[;ݸkeА( si|R I@#~/Xs7-3; -3X@ RM`AOspӧFZlĻaUFWES`)zd PJ[WN8D'Kǖfڛ3tVAi='>o[>0Œd4-1HcADǎ9H.x7BZvǤY|Nfs%lpW`7X7913^Aƫ>gKv5ݰU6ҷU P>uŋ/lQ7PBF-YᆺrbJIIPUV>6= "86,n("+l?CAm+~ ^ (BD2,ǁ@˵_: }b_M&km'Ijփ9-Bpwk%v{Zlw:Qd/5L',lB{'4BI#'w"sSmnµtuJ:"+rߖX^ "q9ݭ'fEV lxx?&m("DqT '%r<Y9'K'J\a'#q5033cKh_l9ghGqFᲒhlOO$Ҋ{gs_9NgQ@(unucMn]cuum%g@  :)W0:٥=_D"$ͨ3` F|})ʜe,WWW/ c 'X2*tAYe֬`_el*3L_'5e[ZDmk Ep̲yᏟg `<mV&bh*cSaF|Ws=cj\\FIOɄlV F 9FCJ<:"M8MTX,jh4Έ$ IXn3kTqUH_ 3 000[3:h.Ջ]@Ǎf~ -W׳>Q;+ 17;V(%”0*  hdPrp\B[gsPUU>/̹IS|rx Efrɭ4}3eg~y`#sƛ 5`G"$HѼOWH۲#@mDZSt&%dX*珍3HgI8|̉0AÖGA|"RڕEe )!].׿5w2.:>"Sl>wWFdGIř@CTV٨)/cPw0&byugIԳɛ%?^(O/bozl]9Mf/۷Ax]f f47C'%-+譱Q{PM!iXs;̦rX bmVE '}j0WSNP4L0*O=#)ñl|0tl+(:HH 6>#Z{O1ݹՙYssɊϣquH(I4왃Qk ĮZ&N~G}`E,ʢ,ʢ >p}@5F}֢ 3c(ߑǻm"횚k;m6Kh 9sdU]fV?Dkm, l6[ҥKy`ttT 4r- UK P#$|._ D_(o 1Nue6N5"@ęRPˈ'aCWWyÃ/_4L (@p#GvjRØnvR$蓠 V^۷ox/Ø%%%0`Y cadh`4ٺ:S0KJJ؈כ z hh6ڗ"zZZq_%E2l@ UYYZmYY:'%|e `27WC***LKdI ɓ8u6 øw^>|0<< LJ /|mVNtUg'QI%CYYY(,,L>7ꋔbXB'b*2WQDIIjEKK g>}eee dOODQW]] `[np8CggXjvfl6~6*2d2~ݻwQ^^po>0 /"( Vtjc̪?|L̎c.[k@e IH"9!$#&d0 + $*ABRcaba.T!R 䙲|ؽ{w\Apbb꟝qs4$N'P^~ y}[[[X,Obb2-A~3EQ( (Pgu"ތwS@Vd2}7EѣG˾L \檶<؈W0C?Ld[@oTJ?)I٨VTDhDff&!`~f` @30BosJ:pSҮi )@BE90`F'f9C$izSA$~˲>oB<!"$I$Ihmm <$B! BIs>Glz* z W^UKW U;@^~9sFm8Zoor!###x>sN;]-D7摷$ %+˧/=0EYEs!RIENDB`gquilt-0.25/pixmaps/stock_new_patch.png0000664000076400007640000000574711476545002020622 0ustar peterpeter00000000000000PNG  IHDR@@iqsRGBbKGD pHYs  tIME *tEXtCommentCreated with The GIMPd%n >IDATxklugKrE45ԋFrUIډQrֱ$E|mLJ4EE-RCP-7<;pFtdPlu-di)SK.33˗E%`0;wι¯ԋHݷ[o)^,(w u0 bTXbLjӕpFg$׉^_Q`%0`@%B땅Sp%u2 =zn#Jʚz1P&%)Sΰ+ > 75{Z ʈ<0Gu^ihژDÍ;x~o戺W! /`+Fi# `/8F4^pm?{G2U{ t'hD~L֋bt^7k>F\DҁHo[*7, ey?d k#ML7]K(;AnOJJla}=> Vq0Qɥ΃ ɈFw dh旈 uPUPk"v/EѶ-۟VYV`̴.ǗLEa~A:9T^9:J@?:^΀d= ̔O Q$t!~'/!OoYۙ*{u_p5y/W-03m>LBHSڷMwpjYliV{D@ fڳHrGtbť%)O#Ӌ4{;ۦ(Gk#HzBV u t2x>mU ti_tjH]F@#?$h+YۘPH70 *ZǂtQxė&q7 ݱ}ACDui߈`LxT{ W_DPmd}Xjm{% JRFQ {!'lD2v~394W+ďM1gJbȇ9/\ߡ]]˧ALS2~z1:TnRع ,Y&Y9d/\dG{_)},?~x[M xOYT pkH%1K2`Y<{S?7y~z<3oxxύoj5$&! I0,u~+S5ʟelYlBl IHdw/nL}kŜ˜ =fEQT!ߛ'6 eM zvD ѠXm5l\ѽf(Vy40k1.ٗMkYK&W{ߪ~0g@b,3sk%`й5HSn}v]+s$3Zt.π:/0g (*mt] Edz|~c@T-Mp;K~LͩWfP.Xqy8;4|*hJ n^XIs%B 0L;҈3M$K}ySLGk̒ Snҽ`d^dGhE`ʪʪʪʪʪʪʪʪZy Nz<IENDB`gquilt-0.25/pixmaps/stock_pop.png0000664000076400007640000000647611451033514017441 0ustar peterpeter00000000000000PNG  IHDR@@iqbKGD pHYs  tIME [l8tEXtCommentCreated with The GIMPd%n IDATx}pu?0LȄ7Id,o bxkUNdOaw2 \w.#UeyWgՖs nVA=ZƬ.`B|[ I2KwLKHB}~==}^K@|_p˂[R A_ ߭#._p]"֯Xs0v۷o/ʯ'NJ~H:Z@3] o5Y% @)'[~"kEZY(x>h7=I$p|AS?*6bNfʒ)R՞;s߇~OۑoȭS|>ߎ!5W>o$xXr,7[ +{+w]0X v1Tn(|ZG>CFz06y`괙̩?E%[m{~ :c`Cr˟XND`M]ZxzSר"fν7~"j:keĢlU/ -IM-yg1y|THZ9? V"zC{6Z=F|Ǐ|>ߌH$p*jb,MψDOS е ?íx(^)ٳgܮ]ʒɤS/ƏE7gᕗ… O\[i}#=StK1si'ʯR/[:2Re<ʖs9sVRn}䁦Jk269[-CᡭXg7W-"7RJp_}C["}>zk\vIєYPn)ʄ!ذ)h`5YE?eL!`+Ӑ@(bIa9cM5^б~={ֺ޾qX@8@?K8pk0ksRjؕ.KG̼/)f0qt&4~m;sέtnm+p,1Sn@ɫu 4bt1k6F KF;J=wd>N|%0`8f^{g1nb2v2'jccZZ-ج:B=p^V>ivŔ{́Qk,z^o3bS,AEƷGOϩܰh+$#gP?ɤ^~?~E`U!<dor҉ŕ8 &6=H*]L鼿7|Q1qZz_fݖU>/4bZa-DgV_c;5@MIRgEI:tU MS5%ns|R#eힽ{ikY7OQ>::q䑗9g+5YE-!p̙ >8}-!)TawنjbA/X;N b* ؝v{Fr۰|f-=DMt!u3 <V¬Y71vL!=D4CHѓ8o - IE(odHir1cMzj蚚9nsA9GMt%S{3Zuh8wii0+*8xVE"*QQ#FgB+eT׈adl8\.GD0.'ĢɿaAg1anBMN2 ʰ*LTEsn,qﴣ(ay6"~ 0 8!  c]XtIR9'={+esXcϼx 9AѥD.6J^jKTMN4}zP rjV3(/ ͛7 Ǹ3 XL sH 򺼈Y%@$Ϸ&:Ba''߿d _wy<«vM3\ťD׹ܷI4]'sM|{w{(vKDDiO|Bݶ'ϙW -3KW~-*ᵽ&Li|]pqWkk.MuuuMj=ی"]ɳ:*]IF+WBu_n\# -\EAKJ {ι.23|cÙ|r?7wPEQEQEQE(@u՜VYrei @(x^8 \NjG/(#[ P}q \Rx5^sj, 0dsEhΙ @L 29`e1dPP4@qYgwi8s9P4++7 s=:903I@'`I4k7Q lZ[T;>L/˔G)be K}a KX|p@JQH)ņ,Lf_D3 bL&FR"n`=p 0DRVWxv5rP:k+7rNf a%fDf8JlUIENDB`gquilt-0.25/pixmaps/stock_tick.png0000664000076400007640000000407011451033514017561 0ustar peterpeter00000000000000PNG  IHDR@@iqsBIT|dIDATxl?wNNBUEUjDitS%PpidS2LZiic*tJ҄ZF: M ?QRF`Ii ؾV_lrsGyGy|]!fzSD#o38z@'fv:74nn7zpxе>5Y#5*>էWm}%¡\ "sO vl[lW~oX leem+FzͶ 4_7HiՏQ[l6 TSveH? -5ZjJ^qS'dٖU7mm){?烟#@(Ђص(o#gLN |FЦH 2WA[a6 0cPqWRB (,/~Emoٞp2܇*N^ F7b?g; \aW{Y8H s8'Mb*-nM;64N) $FHv1gvOv|'Ory&U{Հ֠b]1F|h|UmXkWEݳ-&?u= @q:I<،H1oBjH i Y[ҝL 7_oL_鸙O  j0$+0w)31p|o͜s6wiHXDގ;6YpĽSȄT/S( (J+2d{Q# ,Cvy@=-(W ZЯc칱t!cb"wi64ۛښXTu2}t#":S8E:) 1ITg,2^TG&&*Z'Cv‰KpINi;BQWv@C46+*WΗmi7 0!"B|xw '.Ჭ9DD]A Y PS_CTGAx j`b ڮp8 X3Cg>t-$j"p"KW[w`S8Yw? ,_]sN>1%Kl 댁8{!2% NQqش1Ϲq-!Wφz.EKP5ˆVk1k^c9x_x)30:@w{i ~@[y`P%Q҉%B Vzx"gͶ5)}B]H%HлCsk5HW=pIr{yGQeTHHa>; +h 6Pw/q/vPC,q-! "ɗ[Ua&t &mȝ?h't3y6<'Etb cKqmrq ᒮqQ0099̞;(3OJ*RV~|q 0&L#BPwgzX~ɤHXAr]>'\js}87ԦLuW; IxA .p$]C6@]]Em )$gVH8ӆ\=`2i>LQI}PHkx] p]C(A^^=0yƔ/vutFFq#w"ڰ" .x}^XPo'b't{]/Xfof`[^zGX/5x>RueL 1#<#<#YN4@ ;IENDB`gquilt-0.25/pixmaps/stock_push.png0000664000076400007640000000641511451033514017613 0ustar peterpeter00000000000000PNG  IHDR@@iqbKGD pHYs  tIME8+x\tEXtCommentCreated with The GIMPd%n qIDATx{pT}?ܻ/l6F865%2#A:Π:h 4iP锠$.xp9$~Ɩm`/HVXI{{WZi޻=|Aj@|/zԞ'p6y@Æ=Լ7"y{e, 5dnlwo _Nhyޠw߬(41ZԳKϽh/=y@u+UҔĕB,`1Ԋu+^ UﳔKt@FԯLLwB_˼y\{.'~pYV>TYYY= LÌwgc $|gZj]'i[kxzT=||7m{zӪ HŠ"k=7~ > h` Vzyۥ }u2 {6ivW&*++ˆPHF5Zg> HPm@;G:#VUVVF֬Y3>eee<OG֏TPQߨ[yHgļPV1&t#hԹz -Qw,Z5SNn*xj' sjg\޵ ɚJwQfbJtH @#7>(YSfP l oz1PԇJԹg7|'ΘqZ1v2YWϧyK`}헄KXhڵaBbݏ^w5ǺѯBC7oo"T0Rח_ɼ ./Ræ1)׏&%#>{#aqutwSl lPMMM(#ÝKƙk8m+}mRfr<цitNp譵81 ̴Op؃i(Ӥq'-^r!`pr͞ۥqǸR R$Ezu=Wp"=mtua "|Ό%x\6NFX76]&E IrTO@Nn&I =v2O0b!wv6 /[q`z|ӆǙ\1z4Ā!ѤDΟG54M0EH+gixi@ Akk)v/#uwY!"Guv踝:젻X8;ffijj+?XT]#-.w\11&e{R E+.Җd[hXg#G[?kDR"ė3mGGӁ8:d>_57XJp@kp;g84\ǦkP*y43 $a(~5(4J)P}t2zt<„Uc8 Jny`)\ˡrh8Mֱ(0q6Mb%]S*h B)%oΤʀ/͹Z5Bꙺi_ZOrHѧM$F4I3q{)))) B& rW]yw lZE{#>hL%ДTi*LE)ɀtM) .' c(vf ζOЪɾn4+HR LiCtRɠg޼y%JТ[0L:ȾOeG8dEI& ɨfR1Y!u kO`|lٲE3O):ItJb@ݜoᗛk.z&=0#<ĺM(+zItJ뀒fWzJv;q ;_4 ~,7Ф@!㲓3΋ԒY`ެ;R߳<d|nN0@J8_6|@{ CضRTzuǢ:R׸/J9ݩhn yVȶRc#b=քlyi3B$S2 =5)%[IPC)c ?ٶ5[8' V/ǩ Snj?p˲4,wh G$ Cq-̇c䆦,% 7#_L`HCnj#cged}b1}ժU7DQ/sodOB\JA5 qS/wO%㝛}fg$k֬)zVSUc4a(Ϣ nFAɡNtr=;iG <&~?Ą)WHf8<0ԙ_P7i8B]]]Xļ+͛ab5FG}򚙺TTS3?xvɵt0s0}ߵCϖ[/Ծxu!7CtU@.NJz@B w̛oŅq[f0 }onNOsK%"j+r{? d^o\no!p(?zhkSzg){<\E5ôOp/ Ye$\IENDB`gquilt-0.25/pixmaps/stock_patch_guard_select.png0000664000076400007640000001353411476545002022463 0ustar peterpeter00000000000000PNG  IHDR@@iqsRGBbKGD pHYs B(xtIME ;VynIDATx[yTՙ{RknnEYbDC`ԸdQ%s2YN♌I&9,ddN&12I8hT fPDD@2hMM/V{{Qsf8߹߻}-__䝺#e`˽?\սk+^tvrECNo} ű<{xZl#X0pZ YWXVXK$I4Mx뺊3d!J>$A)i0C&}4ƈni:tM7c\Wzܛ3iWqٛ?#ۺ!>' 3JӤ3M׌t^gƴx,!4LXRL˜F(@ڊ: 듨M@J(yB(@4 Ɛf;QcjTt9]jvSA8VjʅRPj:bۭpղ, aQ!1FR B)H`(d@4e>!DRR@yo\ɫ1o%CPd5'(Q"v# ~ /ZS'f D(P&@Ng9-lnv7vW^58w5]נ|aE],XӴ`!i`6fM@)@ ߿<:^~B1p?Oq7n.Vg+|d * rT'%w\JK&ud29@m[_H®3lE RaH4x4Fah`-Ƃ޽#͕^PK/m۶hx45M… PWuI$HضԶbKC[‹H)*@dgC5 bCd2 @1;K&pMRBq"xA=^{Ns [rtw|%KmnzjϪ^!+3`"fq3pmKb mW~a=:Szb,ڟ '=imt/NB! XdJasJG/!ǢzÈ K u\&q ڃ 4hxsaFBa\T5<O#tBb7UUQp8X4(B! z+PJ%lJ1ˤ |p¢ŋ"!>=0QU Ua Al o8jj;pMmB3 V()XoS) /|R/pΡiz) 0ā$&<f9>Xf(GwnSk1|sAmm-DRAJ5*h۹~k R Q)}:`%8菦Ʋ[L)%K_6!.cxw^)ȀĒeu9 >@tc((U62fs%pҋ0X X8P%_Ry455=6mn*o y0t/m3T F4pWnl_2kBu?[<%{Ƃ Ic&JQ{W%=OKGLTZ@⺯!ĘM(pĬcc׃}p烀 L`<؅sfA*bqژ 'w~kt+51 (sx!FTc_gs} ҔH 9k)e~k,G|%_`r<2x$D](,O9xL K?< j<'"`~3Oo\6Su[ؙ,p.Bմs0gL߇EǏ^8W8889qL)6PW_|XsJPYglKRISi4L9.w}}}Ϟe3_r)3Hя}ܙ3gJ RJ^Ct~xn\Ç eiR?)!sNTÆM'}]}}}:AK !p 'f`YtÀTR9)E)#A4UeX , eiTBo2̓iM{pWqd`p_&yx`'RaBt"TJӓR5E5)&ȱK*H!9Ws(St\q&<}sqU ˻}Ο:y@ EM+:fo2*RUl+{ T砄1)t\(/3>tsxUcc#5$@AұPC 1"U>$!}pΕa9 vTju*Iy䚚Ο_|d͝MSommim6#C9WRiz K B@e rskk+,˪*8wP,( %BH544\fsBѽhsW[g躁\> sD; A*+j/?`+sv0ʠ> o=|g +'t¶ڠ:\σsRNԧ ̚1MJg0vB {([a˜t*%RTso>ؙK.VJ{oaD8@HpVʘýxq."d MRAWqm6DtCTk~_ P(}f{& es܌TiU(<+)5P)L3}4"1)*!|t@:pj4@н?b*!4P(hvrx㍾sfTj64RMc# i t)74*Z amDˀ6QGϞ30TK:jIU<#/ gH$ZHgbz 4B51 ;ne/p] fd=E) x;iRP]\7>>6{Qr]!8&HX-mL8 #؏[ 84k<-Zu4Cd[Yg!4!b>`|AQ{`fW}j45 lA9@= cșyJ| l0>}VS ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Launch a GUI wrapper for quilt and Mercurial Queues (mq) patch management tools""" import sys import os import getopt import gtk from gquilt_pkg import dialogue try: from gquilt_pkg import gquilt except Exception: dialogue.report_exception(sys.exc_info()) sys.exit(3) def usage(): """Print the program's usage message""" print("Usage: gquilt [dir]") # read required directory from the command line try: OPTION_LIST, ARG_LIST = getopt.getopt(sys.argv[1:], "") except getopt.GetoptError as err: sys.stderr.write(str(err) + os.linesep) usage() sys.exit(1) if len(ARG_LIST) > 1: usage() sys.exit(1) DIR_SPECIFIED = len(ARG_LIST) == 1 if DIR_SPECIFIED: try: os.chdir(sys.argv[1]) except OSError as msg: sys.stderr.write(str(msg) + os.linesep) sys.exit(2) try: gquilt.GQuilt(DIR_SPECIFIED).show() gtk.main() except (SystemExit, KeyboardInterrupt): pass except Exception: dialogue.report_exception(sys.exc_info()) sys.exit(3) gquilt-0.25/gquilt.xpm0000664000076400007640000000435311451226605015301 0ustar peterpeter00000000000000/* XPM */ static char * icon_xpm[] = { "32 32 72 1", " c None", ". c #000000", "+ c #FF004D", "@ c #FF008D", "# c #FF00B3", "$ c #A500FF", "% c #7200FF", "& c #00FFD8", "* c #00FF7F", "= c #00FF0C", "- c #1AFF00", "; c #40FF00", "> c #5AFF00", ", c #99FF00", "' c #A6FF00", ") c #FF00E6", "! c #8C00FF", "~ c #4C00FF", "{ c #00FFA5", "] c #00FF26", "^ c #27FF00", "/ c #73FF00", "( c #80FF00", "_ c #B200FF", ": c #1900FF", "< c #0000FF", "[ c #00FFCC", "} c #00FF8C", "| c #00FF59", "1 c #00FF3F", "2 c #00FF00", "3 c #4DFF00", "4 c #66FF00", "5 c #FFFF00", "6 c #2600FF", "7 c #00FFF2", "8 c #00FF99", "9 c #00FF72", "0 c #D800FF", "a c #00FF19", "b c #0DFF00", "c c #33FF00", "d c #3300FF", "e c #5900FF", "f c #6600FF", "g c #00FF33", "h c #9900FF", "i c #00FFB2", "j c #00FF66", "k c #00FF4C", "l c #E500FF", "m c #00FFBF", "n c #7F00FF", "o c #0C00FF", "p c #00FFE5", "q c #912CEE", "r c #EE00EE", "s c #CC00FF", "t c #FF0000", "u c #00FFFF", "v c #F200FF", "w c #FF00C0", "x c #3F00FF", "y c #FF005A", "z c #FF0066", "A c #00EE00", "B c #BF00FF", "C c #272518", "D c #FF00F3", "E c #FF00D9", "F c #FF00FF", "G c #CDB79E", " . . . . . . ", " .................... ", " .+@#.$%.&*.=-.;>.,'. ", " . . ....).!~.&{.]=.^;./(. ", " ..........._%~:<[}|1]2^3>4. ", " ....<<.<..55.$%~67[89:0abc.....", " .<< ### 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; version 2 of the License only. ### 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 (see /usr/share/doc/gwsmhg/COPYING); ### if not, write to the Free Software Foundation, 51 Franklin Street, ### Fifth Floor, Boston, MA 02110-1301, USA gquilt-0.25/Makefile0000664000076400007640000000252111451227614014702 0ustar peterpeter00000000000000VERSION:=$(subst ',,$(subst VERSION = ',,$(shell grep "VERSION = " setup.py))) RELEASE:=$(subst ,,$(subst release = ,,$(shell grep "release = " setup.cfg))) PREFIX=/usr PKG_DIR=gquilt_pkg RPMBDIR=~/rpmbuild RPMSRC:=$(RPMBDIR)/SOURCES/$(SRCDIST) SRCDIST:=gquilt-$(VERSION).tar.gz RPMDIST:=gquilt-$(VERSION)-$(RELEASE).noarch.rpm #all: $(MODULES_BIN) $(MODULES_OPT) # @echo "\"make install\" to install in /usr/local" gquilt.spec: setup.py Makefile setup.cfg python setup.py bdist_rpm --spec-only --dist-dir . echo "%{_prefix}" >> gquilt.spec sed -i \ -e 's/^\(python setup.py install.*\)/\1\ndesktop-file-install gquilt.desktop --dir $$RPM_BUILD_ROOT%{_datadir}\/applications/' \ -e 's|-f INSTALLED_FILES|\n/etc/gquilt.d/qbsfe.sh|' \ gquilt.spec install: python setup.py install --prefix=$(PREFIX) desktop-file-install gquilt.desktop --dir $(PREFIX)/share/applications source_dist: $(SRCDIST) $(SRCDIST): python setup.py sdist --dist-dir . rpm: $(RPMDIST) $(RPMSRC): $(SRCDIST) cp $(SRCDIST) $(RPMSRC) $(RPMBDIR)/SOURCES/gquilt.xpm: cp gquilt.xpm $(RPMBDIR)/SOURCES $(RPMDIST): $(RPMSRC) gquilt.spec $(RPMBDIR)/SOURCES/gquilt.xpm rpmbuild -bb gquilt.spec cp $(RPMBDIR)/RPMS/noarch/$(RPMDIST) . release: $(SRCDIST) $(RPMDIST) clean: -rm gquilt_pkg/*.pyc gquilt_pkg/*~ *~ -rm *.rpm *.spec *.exe *.tar.gz MANIFEST -rm -r build dist gquilt-0.25/gquilt_pkg/0000775000076400007640000000000011516672673015423 5ustar peterpeter00000000000000gquilt-0.25/gquilt_pkg/tlview.py0000664000076400007640000002030311476544760017305 0ustar peterpeter00000000000000### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ Provide generic enhancements to Textview widgets primarily to create them from templates and allow easier access to named contents. """ import gtk, collections from gquilt_pkg import gutils def model_col(descr, label): """Return the index of the column with the given label in the descr which is named tuple""" return descr._fields.index(label) class Model(gtk.TreeModel): def __init__(self, descr): """descr is a named tuple""" self.row_tuple_type = type(descr) def get_col(self, label): return model_col(self.row_tuple_type, label) def get_cols(self, labels): return [self.get_col(col) for col in labels] def get_values(self, model_iter, cols): return self.get(*([model_iter] + cols)) def set_values(self, model_iter, col_vals): return self.set(*([model_iter] + col_vals)) def get_row(self, model_iter): return self.get_values(model_iter, list(range(self.get_n_columns()))) def get_labelled_value(self, model_iter, label): return self.get_value(model_iter, self.get_col(label)) def get_labelled_values(self, model_iter, labels): return self.get_values(model_iter, self.get_cols(labels)) def set_labelled_value(self, model_iter, label, value): self.set_value(model_iter, self.get_col(label), value) def set_labelled_values(self, model_iter, label_values): col_values = [] for index in len(label_values): if (index % 2) == 0: col_values.append(self.get_col(label_values[index])) else: col_values.append(label_values[index]) self.set_values(model_iter, col_values) def get_contents(self): contents = [] model_iter = self.get_iter_first() while model_iter: contents.append(self.get_row(model_iter)) model_iter = self.iter_next(model_iter) return contents def get_row_with_key_value(self, key_label, key_value): index = self.get_col(key_label) model_iter = self.get_iter_first() while model_iter: if self.get_value(model_iter, index) == key_value: return model_iter else: model_iter = self.iter_next(model_iter) return None class ListStore(Model, gtk.ListStore): def __init__(self, descr): """descr is a named tuple""" Model.__init__(self, descr) gtk.ListStore.__init__(*[self] + list(descr)) def append_contents(self, rows): for row in rows: self.append(row) def set_contents(self, rows): self.clear() for row in rows: self.append(row) class TreeStore(Model, gtk.TreeStore): def __init__(self, descr): """descr is a named tuple""" Model.__init__(self, descr) gtk.TreeStore.__init__(*[self] + list(descr)) def insert_contents(self, rows): assert True, "append_contents(%s) must be defined in child" % rows def set_contents(self, rows): assert True, "set_contents(%s) must be defined in child" % rows ViewTemplate = collections.namedtuple('ViewTemplate', ['properties', 'selection_mode', 'columns']) """ properties is a dictionary: {property_name: value, ...} selection_mode is one of gtk.SELECTION_NONE, gtk.SELECTION_SINGLE, gtk.SELECTION_BROWSE or gtk.SELECTION_MULTIPLE column_descr is class Column """ Column = collections.namedtuple('Column', ['title', 'properties', 'cells']) """ title is a string properties is a dictionary: {property_name: value, ...} selection_mode is one of gtk.SELECTION_NONE, gtk.SELECTION_SINGLE, gtk.SELECTION_BROWSE or gtk.SELECTION_MULTIPLE cells is a list of class Cell """ CellCreator = collections.namedtuple('CellCreator', ['function', 'expand', 'start']) """ function is a gtk cell class creation function expand is boolean start is boolean """ def _create_cell(column, descr): """descr is a CellCreator""" cell = descr.function() if descr.expand is not None: if descr.start: column.pack_start(cell, descr.expand) else: column.pack_end(cell, descr.expand) else: if descr.start: column.pack_start(cell) else: column.pack_end(cell) return cell Renderer = collections.namedtuple('Renderer', ['function', 'user_data']) Cell = collections.namedtuple('Cell', ['creator', 'properties', 'renderer', 'attributes']) """ creator is a class CellCreator properties is a dictionary: {property_name: value, ...} renderer is a named tuple Renderer attributes is a dictionary: {attribute_name: index, ...} """ ColumnAndCells = collections.namedtuple('ColumnAndCells', ['column', 'cells']) class View(gtk.TreeView): def __init__(self, descr, model=None): gtk.TreeView.__init__(self, model) for prop_name, prop_val in descr.properties.items(): self.set_property(prop_name, prop_val) if descr.selection_mode is not None: self.get_selection().set_mode(descr.selection_mode) self._view_col_dict = {} self._view_col_list = [] for col_d in descr.columns: self._view_add_column(col_d) self.connect("button_press_event", self._handle_button_press_cb) self._modified_cbs = [] def _view_add_column(self, col_d): col = gtk.TreeViewColumn(col_d.title) col_cells = ColumnAndCells(col, []) self._view_col_dict[col_d.title] = col_cells self._view_col_list.append(col_cells) self.append_column(col) for prop_name, prop_val in col_d.properties.items(): col.set_property(prop_name, prop_val) for cell_d in col_d.cells: self._view_add_cell(col, cell_d) def _view_add_cell(self, col, cell_d): cell = _create_cell(col, cell_d.creator) self._view_col_dict[col.get_title()].cells.append(cell) for prop_name, prop_val in cell_d.properties.items(): cell.set_property(prop_name, prop_val) if cell_d.renderer is not None: col.set_cell_data_func(cell, cell_d.renderer.function, cell_d.renderer.user_data) for attr_name, attr_index in cell_d.attributes.items(): col.add_attribute(cell, attr_name, attr_index) if attr_name == 'text': cell.connect('edited', self._cell_text_edited_cb, attr_index) elif attr_name == 'active': cell.connect('toggled', self._cell_toggled_cb, attr_index) def _notify_modification(self): for cbk, data in self._modified_cbs: if data is None: cbk() else: cbk(data) def register_modification_callback(self, cbk, data=None): self._modified_cbs.append([cbk, data]) def _handle_button_press_cb(self, widget, event): if event.type == gtk.gdk.BUTTON_PRESS: if event.button == 2: self.get_selection().unselect_all() return True return False def _cell_text_edited_cb(self, cell, path, new_text, index): self.get_model()[path][index] = new_text self._notify_modification() def _cell_toggled_cb(self, cell, path, index): self.model[path][index] = cell.get_active() self._notify_modification() def get_col_with_title(self, title): return self._view_col_dict[title].column def get_cell_with_title(self, title, index=0): return self._view_col_dict[title].cells[index] def get_cell(self, col_index, cell_index=0): return self._view_col_list[col_index].cells[cell_index] gquilt-0.25/gquilt_pkg/putils.py0000664000076400007640000004165411507224700017307 0ustar peterpeter00000000000000### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA '''Provide functions for manipulating patch files and/or text buffers''' import re import os.path import shutil import tempfile import collections from gquilt_pkg import utils, fsdb from gquilt_pkg import const _DIFFSTAT_EMPTY = re.compile("^#? 0 files changed$") _DIFFSTAT_END = re.compile("^#? (\d+) files? changed(, (\d+) insertions?\(\+\))?(, (\d+) deletions?\(-\))?(, (\d+) modifications?\(\!\))?$") _DIFFSTAT_FSTATS = re.compile("^#? (\S+)\s*\|((binary)|(\s*(\d+)(\s+\+*-*\!*)?))$") _TIMESTAMP_RE_STR = '(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{9} [-+]{1}\d{4})' _ALT_TIMESTAMP_RE_STR = '([A-Z][a-z]{2} [A-Z][a-z]{2} \d{2} \d{2}:\d{2}:\d{2} \d{4} [-+]{1}\d{4})' _EITHER_TS_RE = '(%s|%s)' % (_TIMESTAMP_RE_STR, _ALT_TIMESTAMP_RE_STR) _UDIFF_H1 = re.compile('^--- (.*?)(\s+%s)?$' % _EITHER_TS_RE) _UDIFF_H2 = re.compile('^\+\+\+ (.*?)(\s+%s)?$' % _EITHER_TS_RE) _UDIFF_PD = re.compile("^@@\s+-(\d+)(,(\d+))?\s+\+(\d+)(,(\d+))?\s+@@\s*(.*)$") _GIT_HDR_DIFF = re.compile("^diff --git (.*)$") _GIT_OLD_MODE = re.compile('^old mode (\d*)$') _GIT_NEW_MODE = re.compile('^new mode (\d*)$') _GIT_DELETED_FILE_MODE = re.compile('^deleted file mode (\d*)$') _GIT_NEW_FILE_MODE = re.compile('^new file mode (\d*)$') _GIT_COPY_FROM = re.compile('^copy from (.*)$') _GIT_COPY_TO = re.compile('^copy to (.*)$') _GIT_RENAME_FROM = re.compile('^rename from (.*)$') _GIT_RENAME_TO = re.compile('^rename to (.*)$') _GIT_SIMILARITY_INDEX = re.compile('^similarity index (\d*)%$') _GIT_DISSIMILARITY_INDEX = re.compile('^dissimilarity index (\d*)%$') _GIT_INDEX = re.compile('^index ([a-fA-F0-9]+)..([a-fA-F0-9]+) (\d*)%$') _GIT_EXTRAS = \ [ _GIT_OLD_MODE, _GIT_NEW_MODE, _GIT_DELETED_FILE_MODE, _GIT_NEW_FILE_MODE, _GIT_COPY_FROM, _GIT_COPY_TO, _GIT_RENAME_FROM, _GIT_RENAME_TO, _GIT_SIMILARITY_INDEX, _GIT_DISSIMILARITY_INDEX, _GIT_INDEX ] _CDIFF_H1 = re.compile("^\*\*\* (\S+)\s*(.*)$") _CDIFF_H2 = re.compile("^--- (\S+)\s*(.*)$") _CDIFF_H3 = re.compile("^\*+$") _CDIFF_CHG = re.compile("^\*+\s+(\d+)(,(\d+))?\s+\*+\s*(.*)$") _CDIFF_DEL = re.compile("^-+\s+(\d+)(,(\d+))?\s+-+\s*(.*)$") _HDR_INDEX = re.compile("^Index:\s+(.*)$") _HDR_DIFF = re.compile("^diff\s+(.*)$") _HDR_SEP = re.compile("^==*$") _HDR_RCS1 = re.compile("^RCS file:\s+(.*)$") _HDR_RCS2 = re.compile("^retrieving revision\s+(\d+(\.\d+)*)$") _BLANK_LINE = re.compile("^\s*$") _DIVIDER_LINE = re.compile("^---$") def _udiff_starts_at(lines, i): """ Return whether the ith line in lines is the start of a unified diff Arguments: lines -- the list of lines to be examined i -- the line number to be examined """ if (i + 2) >= len(lines): return False if not _UDIFF_H1.match(lines[i]): return False if not _UDIFF_H2.match(lines[i + 1]): return False return _UDIFF_PD.match(lines[i + 2]) def _is_git_extra_line(line): """ Return whether the line is a git diff "extra" line Argument: line -- theline to be examined """ for regex in _GIT_EXTRAS: match = regex.match(line) if match: return (regex, match) return False def _git_diff_starts_at(lines, i): """ Return whether the ith line in lines is the start of a git diff Arguments: lines -- the list of lines to be examined i -- the line number to be examined """ if i < len(lines) and _GIT_HDR_DIFF.match(lines[i]): i += 1 else: return False extra_count = 0 while i < len(lines) and _is_git_extra_line(lines[i]): i += 1 extra_count += 1 if extra_count == 0: return _udiff_starts_at(lines, i) elif i < len(lines): return _GIT_HDR_DIFF.match(lines[i]) or _udiff_starts_at(lines, i) else: return True def _cdiff_starts_at(lines, i): """ Return whether the ith line in lines is the start of a combined diff Arguments: lines -- the list of lines to be examined i -- the line number to be examined """ if (i + 3) >= len(lines): return False if not _CDIFF_H1.match(lines[i]): return False if not _CDIFF_H2.match(lines[i + 1]): return False if not _CDIFF_H3.match(lines[i + 2]): return False return _CDIFF_CHG.match(lines[i + 3]) or _CDIFF_DEL.match(lines[i + 3]) def _trisect_patch_lines(lines): """ Return indices splitting lines into comments, stats and diff parts Arguments: lines -- the list of lines to be trisected Return a two tuple indicating start of stats and diff parts. For stats part provide integer index of first stats line or None if the stats part is not present. For diff part provide a two tuple (index of first diff line, diff type) or None if the diff part is not present. """ n = len(lines) patch_type = None patch_si = None diffstat_si = None i = 0 while i < n: if _DIFFSTAT_EMPTY.match(lines[i]): diffstat_si = i elif _DIFFSTAT_FSTATS.match(lines[i]): k = 1 while (i + k) < n and _DIFFSTAT_FSTATS.match(lines[i + k]): k += 1 if (i + k) < n and _DIFFSTAT_END.match(lines[i + k]): diffstat_si = i i += k else: diffstat_si = None i += k - 1 elif _git_diff_starts_at(lines, i): patch_si = i patch_type = 'git' break elif _HDR_INDEX.match(lines[i]) or _HDR_DIFF.match(lines[i]): k = i + 1 if k < n and _HDR_SEP.match(lines[k]): k += 1 if _udiff_starts_at(lines, k): patch_si = i patch_type = "u" break elif _cdiff_starts_at(lines, k): patch_si = i patch_type = "c" break else: i = k diffstat_si = None elif _HDR_RCS1.match(lines[i]): if (i + 1) < n and _HDR_RCS2.match(lines[i]): k = i + 1 if k < n and _HDR_SEP.match(lines[k]): k += 1 if _udiff_starts_at(lines, k): patch_si = i patch_type = "u" break elif _cdiff_starts_at(lines, k): patch_si = i patch_type = "c" break else: i = k diffstat_si = None else: diffstat_si = None elif _udiff_starts_at(lines, i): patch_si = i patch_type = "u" break elif _cdiff_starts_at(lines, i): patch_si = i patch_type = "c" break elif not (_BLANK_LINE.match(lines[i]) or _DIVIDER_LINE.match(lines[i])): diffstat_si = None i += 1 if patch_si is None: return (diffstat_si, None) else: return (diffstat_si, (patch_si, patch_type)) def _trisect_patch_file(path): try: fobj = open(path, 'r') except IOError: return (False, (None, None, None)) buf = fobj.read() fobj.close() lines = buf.splitlines() diffstat_si, patch = _trisect_patch_lines(lines) if patch is None: if diffstat_si is None: res = (lines, [], []) else: res = (lines[0:diffstat_si], lines[diffstat_si:], []) else: plines = lines[patch[0]:] if diffstat_si is None: res = (lines[:patch[0]], [], plines) else: res = (lines[0:diffstat_si], lines[diffstat_si:patch[0]], plines) return (True, res) def get_patch_descr_lines(path): try: fobj = open(path, 'r') except IOError: return (False, None) buf = fobj.read() lines = buf.splitlines() fobj.close() diffstat_si, patch = _trisect_patch_lines(lines) if diffstat_si is None: if patch is None: res = lines else: res = lines[0:patch[0]] else: res = lines[0:diffstat_si] return ( True, res) def get_patch_diff_fm_text(textbuf): lines = textbuf.splitlines() _, patch = _trisect_patch_lines(lines) if patch is None: return (False, '') return (True, os.linesep.join(lines[patch[0]:]) + os.linesep) def get_patch_diff_lines(path): try: fobj = open(path, 'r') except IOError: return (False, None) buf = fobj.read() lines = buf.splitlines() fobj.close() _diffstat_si, patch = _trisect_patch_lines(lines) if patch is None: return (False, []) return (True, lines[patch[0]:]) def get_patch_diff(path, file_list=None): if not file_list: try: f = open(path, 'r') except IOError: return (False, None) buf = f.read() f.close() return get_patch_diff_fm_text(buf) if not utils.which("filterdiff"): return (False, "This functionality requires \"filterdiff\" from \"patchutils\"") cmd = "filterdiff -p 1" for filename in file_list: cmd += " -i %s" % filename res, sout, serr = utils.run_cmd("%s %s" % (cmd, path)) if res == 0: return (True, sout) else: return (False, sout + serr) def _append_lines_to_file(fobj, lines): for line in lines: fobj.write(line + os.linesep) def _strip_trailing_ws(lines): n = len(lines) i = 0 while i < n: lines[i] = lines[i].rstrip() i += 1 def _lines_to_temp_file(lines): try: tmpf_name = tempfile.mktemp() tmpf = open(tmpf_name, 'w') _append_lines_to_file(tmpf, lines) tmpf.close() except IOError: if tmpf_name is not None and os.path.exists(tmpf_name): os.remove(tmpf_name) return "" return tmpf_name def set_patch_descr_lines(path, lines): if os.path.exists(path): res, parts = _trisect_patch_file(path) if not res: return False else: parts = ([], [], []) comments = [line for line in parts[0] if line.startswith('#')] tmpf_name = _lines_to_temp_file(comments + lines + parts[1] + parts[2]) if not tmpf_name: return False try: shutil.copyfile(tmpf_name, path) ret = True except IOError: ret = False os.remove(tmpf_name) return ret def _rediff_lines(lines, orig_lines=None): if not utils.which("rediff"): return lines lines_tf = _lines_to_temp_file(lines) if not lines_tf: return lines if orig_lines and len(orig_lines): orig_lines_tf = _lines_to_temp_file(orig_lines) else: orig_lines_tf = None if not orig_lines_tf: res, sout, _serr = utils.run_cmd("rediff %s" % lines_tf) else: res, sout, _serr = utils.run_cmd("rediff %s %s" % (orig_lines_tf, lines_tf)) os.remove(orig_lines_tf) os.remove(lines_tf) if res == 0: return sout.splitlines() else: return lines def set_patch_diff_lines(path, lines): if os.path.exists(path): res, parts = _trisect_patch_file(path) if not res: return False else: parts = ([], [], []) lines = _rediff_lines(lines, orig_lines=parts[2]) tmpf_name = _lines_to_temp_file(parts[0] + parts[1] + lines) if not tmpf_name: return False try: shutil.copyfile(tmpf_name, path) ret = True except IOError: ret = False os.remove(tmpf_name) return ret StatusMap = collections.namedtuple('StatusMap', ['extant', 'added', 'deleted']) DefaultStatusMap = StatusMap(extant=' ', added='+', deleted='-') def _file_name_in_diffline(diffline): match = re.match('diff --git \w+/(.*) \w+/(.*)', diffline) if match: return match.group(1) else: return None def _get_git_diff_file_data(lines, i, statusmap): assert _GIT_HDR_DIFF.match(lines[i]) diffline = lines[i] i += 1 new_file = False deleted_file = False copy_from = None copy_to = None rename_from = None rename_to = None while i < len(lines): match_data = _is_git_extra_line(lines[i]) if not match_data: break else: i += 1 regex, match = match_data if regex is _GIT_COPY_FROM: copy_from = match.group(1) elif regex is _GIT_COPY_TO: copy_to = match.group(1) elif regex is _GIT_RENAME_FROM: rename_from = match.group(1) elif regex is _GIT_RENAME_TO: rename_from = match.group(1) elif regex is _GIT_NEW_FILE_MODE: new_file = True elif regex is _GIT_DELETED_FILE_MODE: deleted_file = True if copy_to: return [i, [(copy_to, statusmap.added, copy_from)]] if rename_to: return [i, [(rename_to, statusmap.added, rename_from), (rename_from, statusmap.deleted, None)]] if deleted_file: while i < len(lines): match = _UDIFF_H1.match(lines[i]) i += 1 if match: filename = match.group(1)[2:] return [i, [(filename, statusmap.deleted, None)]] while i < len(lines) and not _GIT_HDR_DIFF.match(lines[i]): match = _UDIFF_H2.match(lines[i]) i += 1 if match: filename = match.group(1)[2:] if new_file: return [i, [(filename, statusmap.added, None)]] else: return [i, [(filename, statusmap.extant, None)]] filename = _file_name_in_diffline(diffline) if new_file: return (i, [[filename, statusmap.added, None]]) else: return (i, [[filename, statusmap.extant, None]]) def _get_git_diff_files_db(lines, i, statusmap): file_db = fsdb.GenFileDb() while i < len(lines): i, files_data = _get_git_diff_file_data(lines, i, statusmap) for datum in files_data: file_db.add_file(*datum) while i < len(lines) and not _git_diff_starts_at(lines, i): i += 1 file_db.decorate_dirs() return file_db def _get_unified_diff_files_db(lines, i, statusmap): file_db = fsdb.GenFileDb() while i < len(lines): match1 = _UDIFF_H1.match(lines[i]) i += 1 if match1: match2 = _UDIFF_H2.match(lines[i]) i += 1 file1 = match1.group(1) if file1 == '/dev/null': file_db.add_file(match2.group(1).split('/', 1)[1], statusmap.added, None) else: file2 = match2.group(1) if file2 == '/dev/null' : file_db.add_file(file1.split('/', 1)[1], statusmap.deleted, None) else: file_db.add_file(file2.split('/', 1)[1], statusmap.extant, None) file_db.decorate_dirs() return file_db def _get_combined_diff_files_db(_lines, _i, _statusmap): return fsdb.NullFileDb() def get_patch_file_db(path, statusmap=DefaultStatusMap): try: f = open(path, 'r') except IOError: return (False, 'Problem(s) open file "%s" not found' % path) buf = f.read() f.close() lines = buf.splitlines() _, patch = _trisect_patch_lines(lines) if patch is None: return fsdb.NullFileDb() if patch[1] == 'git': return _get_git_diff_files_db(lines, patch[0], statusmap) elif patch[1] == 'u': return _get_unified_diff_files_db(lines, patch[0], statusmap) else: return _get_combined_diff_files_db(lines, patch[0], statusmap) def get_patch_files(path, status=False): if not utils.which("lsdiff"): return (False, "This functionality requires \"lsdiff\" from \"patchutils\"") cmd = "lsdiff --strip=1" if not status: res, sout, serr = utils.run_cmd("%s %s" % (cmd, path)) if res == 0: return (True, sout.splitlines()) else: return (False, sout + serr) else: res, sout, serr = utils.run_cmd("%s -s %s" % (cmd, path)) if res == 0: filelist = [] for line in sout.splitlines(): if line[0] == "+": filelist.append((line[2:], const.ADDED)) elif line[0] == "-": filelist.append((line[2:], const.DELETED)) else: filelist.append((line[2:], const.EXTANT)) return (True, filelist) else: return (False, sout + serr) gquilt-0.25/gquilt_pkg/gquilt_mq.py0000664000076400007640000007100411516671255017774 0ustar peterpeter00000000000000# -*- python -*- ### Copyright (C) 2005 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # This file provides access to "quilt" functionality required by "gquilt" import os.path, os, re, pango from gquilt_pkg import gquilt_tool from gquilt_pkg import console, fsdb, ws_event, cmd_result from gquilt_pkg import const from gquilt_pkg import putils from gquilt_pkg import utils FSTATUS_MODIFIED = 'M' FSTATUS_ADDED = 'A' FSTATUS_REMOVED = 'R' FSTATUS_CLEAN = 'C' FSTATUS_MISSING = '!' FSTATUS_NOT_TRACKED = '?' FSTATUS_IGNORED = 'I' FSTATUS_ORIGIN = ' ' FSTATUS_UNRESOLVED = 'U' FSTATUS_MODIFIED_SET = set([FSTATUS_MODIFIED, FSTATUS_ADDED, FSTATUS_REMOVED, FSTATUS_MISSING, FSTATUS_UNRESOLVED]) PatchStatusMap = putils.StatusMap(extant=FSTATUS_MODIFIED, added=FSTATUS_ADDED, deleted=FSTATUS_REMOVED) class ScmDir(fsdb.GenDir): def __init__(self): fsdb.GenDir.__init__(self) def _new_dir(self): return ScmDir() def _update_own_status(self): if FSTATUS_UNRESOLVED in self.status_set: self.status = FSTATUS_UNRESOLVED elif self.status_set & FSTATUS_MODIFIED_SET: self.status = FSTATUS_MODIFIED elif self.status_set == set([FSTATUS_IGNORED]): self.status = FSTATUS_IGNORED elif self.status_set in [set([FSTATUS_NOT_TRACKED]), set([FSTATUS_NOT_TRACKED, FSTATUS_IGNORED])]: self.status = FSTATUS_NOT_TRACKED def _is_hidden_dir(self, dkey): status = self.subdirs[dkey].status if status not in [FSTATUS_UNRESOLVED, FSTATUS_MODIFIED]: return dkey[0] == '.' or status == FSTATUS_IGNORED return False def _is_hidden_file(self, fdata): if fdata.status not in FSTATUS_MODIFIED_SET: return fdata.name[0] == '.' or fdata.status == FSTATUS_IGNORED return False class ScmFileDb(fsdb.GenFileDb): def __init__(self, file_list, unresolved_file_list=list()): fsdb.GenFileDb.__init__(self, ScmDir) lfile_list = len(file_list) index = 0 while index < lfile_list: item = file_list[index] index += 1 filename = item[2:] status = item[0] origin = None if status == FSTATUS_ADDED and index < lfile_list: if file_list[index][0] == FSTATUS_ORIGIN: origin = file_list[index][2:] index += 1 elif filename in unresolved_file_list: status = FSTATUS_UNRESOLVED parts = filename.split(os.sep) self.base_dir.add_file(parts, status, origin) def _hg_command_avail(cmd): res, _sout, _serr = utils.run_cmd("hg help %s" % cmd) return res == 0 def _convert_pop_res(res, sout, serr): if res != cmd_result.OK and re.search("local changes found, refresh first", sout + serr): res |= cmd_result.SUGGEST_REFRESH return cmd_result.Result(res, sout, serr) def _convert_push_res(res, sout, serr): if res != cmd_result.OK and re.search("local changes found, refresh first", sout + serr): res |= cmd_result.SUGGEST_FORCE_OR_REFRESH return cmd_result.Result(res, sout, serr) def _convert_copy_res(res, sout, serr): if re.search("not overwriting - file exists", sout + serr): res |= cmd_result.SUGGEST_FORCE return cmd_result.Result(res, sout, serr) def _convert_import_res(res, sout, serr): if res != cmd_result.OK and re.search("already exists", sout + serr): res |= cmd_result.SUGGEST_FORCE_OR_RENAME return cmd_result.Result(res, sout, serr) def _map_cmd_result(result, ignore_err_re=None, mapper=None): if result.eflags == 0: if _inotify_warning(result.stdout): return result result = cmd_result.map_cmd_result(result, ignore_err_re=ignore_err_re) elif mapper is None: flags = cmd_result.ERROR if result.serr.find('use -f to force') != -1: flags |= cmd_result.SUGGEST_FORCE if result.serr.find('refresh first') != -1: flags |= cmd_result.SUGGEST_REFRESH if result.serr.find('(revert --all, qpush to recover)') != -1: flags |= cmd_result.SUGGEST_RECOVER if result.stdout.find('errors during apply, please fix and refresh') != -1: flags = cmd_result.WARNING result = cmd_result.Result(flags, result.stdout, result.serr) if mapper is not None: return mapper(*result) return result def _run_cmd_on_console(cmd, ignore_err_re=None, mapper=None): result = console.exec_console_cmd(cmd) return _map_cmd_result(result, ignore_err_re=ignore_err_re, mapper=mapper) def _hg_dir(fdir=None): if fdir is None: fdir = os.getcwd() return os.path.join(fdir, ".hg") def _patch_dir(fdir=None): if fdir is None: fdir = os.getcwd() return os.path.join(fdir, ".hg", "patches") def _patch_file_name(patch): return os.path.join(_patch_dir(), patch) def _numeric_str_sort(arg1, arg2): return cmp(int(arg1), int(arg2)) class MQError(Exception): """A Mercurial queues (mq) specific error""" _qpop_re = re.compile("^(popping)\s.*\nnow at:.*$", re.M) _qpush_re = re.compile("^(merging|applying)\s.*$", re.M) _qguard_re = re.compile("^\s*([+-].*)\s*$") def _extract_name_and_guards(string): name, gstring = string.split(':') match = _qguard_re.match(gstring) if match: return name, match.group(1).split() else: return name, [] def _inotify_warning(serr): return serr.strip() in ['(found dead inotify server socket; removing it)', '(inotify: received response from incompatible server version 1)'] # Now implement the tool interface for mq class Interface(gquilt_tool.Interface): def __init__(self): gquilt_tool.Interface.__init__(self, "mq", cmd="hg ") self.status_deco_map = { None: gquilt_tool.Deco(pango.STYLE_NORMAL, "black"), FSTATUS_CLEAN: gquilt_tool.Deco(pango.STYLE_NORMAL, "black"), FSTATUS_MODIFIED: gquilt_tool.Deco(pango.STYLE_NORMAL, "blue"), FSTATUS_ADDED: gquilt_tool.Deco(pango.STYLE_NORMAL, "darkgreen"), FSTATUS_REMOVED: gquilt_tool.Deco(pango.STYLE_NORMAL, "red"), FSTATUS_UNRESOLVED: gquilt_tool.Deco(pango.STYLE_NORMAL, "magenta"), FSTATUS_MISSING: gquilt_tool.Deco(pango.STYLE_ITALIC, "pink"), FSTATUS_NOT_TRACKED: gquilt_tool.Deco(pango.STYLE_ITALIC, "cyan"), FSTATUS_IGNORED: gquilt_tool.Deco(pango.STYLE_ITALIC, "grey"), } def _get_qparent(self): cmd = 'hg log --template "{rev}" -r qparent' res, rev, serr = utils.run_cmd(cmd) if not res and rev: return rev return None def _no_patches_applied(self): return self.get_top_patch() == "" def _patch_most_recent_parent_rev(self, patch): res, plist, serr = self._patch_parent_revs(patch) if res != 0: return cmd_result.Result(res, plist, serr) return cmd_result.Result(res, plist[-1], serr) def _patch_parent_revs(self, patch): if not self.is_patch_applied(patch) and patch != "qbase": return cmd_result.Result(cmd_result.ERROR, "", "Patch %s is not applied." % patch) res, sout, serr = utils.run_cmd("hg parent --template \"{rev}" + os.linesep + "\" -r " + patch) if res != 0: return cmd_result.Result(res, sout, serr) elif sout == "": return cmd_result.Result(res, ["0"], serr) plist = sout.splitlines() plist.sort(_numeric_str_sort) return cmd_result.Result(res, plist, serr) def _unresolved_file_list(self, fspath_list=None): cmd = 'hg resolve --list' if fspath_list: cmd += ' %s' % utils.file_list_to_string(fspath_list) res, sout, serr = utils.run_cmd(cmd) files = [] if not res: for line in sout.splitlines(): if line[0] == FSTATUS_UNRESOLVED: files.append(line[2:]) return files def _get_untracked_or_ignored_files(self, filelist): res, sout, serr = utils.run_cmd(" ".join(["hg status -uin"] + filelist)) if not res: return cmd_result.Result(res, sout, serr) newfilelist = [] for line in sout.splitlines(False): newfilelist.append(line) return cmd_result.Result(res, newfilelist, serr) def display_files_diff_in_viewer(self, viewer, files, patch=None): top = self.get_top_patch() if not top: return if not patch: patch = top elif not self.is_patch_applied(patch): return res, prev, serr = self._patch_most_recent_parent_rev(patch) if res != cmd_result.OK: raise MQError(serr) pats = (os.P_NOWAIT, "hg", "hg", "extdiff", "--program=" + viewer) if patch is top: pats += ("--rev=" + prev,) else: pats += ("--rev=" + prev, "--rev=" + patch) pats += tuple(files) pid = os.spawnlp(*pats) def do_add_files_to_patch(self, filelist): res, trimmedlist, serr = self._get_untracked_or_ignored_files(filelist) if res: if trimmedlist: ws_event.notify_events(ws_event.FILE_ADD) return cmd_result.Result(res, trimmedlist, serr) if not trimmedlist: return cmd_result.Result(cmd_result.OK, "", "") result = _run_cmd_on_console(" ".join(["hg add", trimmedlist])) ws_event.notify_events(ws_event.FILE_ADD) return result def do_copy_file(self, file_path, dest, force=False): cmd = "hg copy " if force: cmd += "-f " cmd += " ".join([file_path, dest]) result = _run_cmd_on_console(cmd, mapper=_convert_copy_res) ws_event.notify_events(ws_event.FILE_ADD) return result def do_delete_patch(self, patch): result = _run_cmd_on_console(" ".join(["hg", "qdelete", patch])) ws_event.notify_events(ws_event.PATCH_DELETE) return result def do_exec_tool_cmd(self, cmd): result = _run_cmd_on_console(" ".join(["hg", cmd])) ws_event.notify_events(ws_event.ALL_EVENTS) return result def do_finish_patch(self, patch): result = _run_cmd_on_console(" ".join(["hg", "qfinish", patch])) ws_event.notify_events(ws_event.FILE_CHANGES|ws_event.PATCH_DELETE|ws_event.PATCH_POP) return result def do_fold_patch(self, patch): result = _run_cmd_on_console(" ".join(["hg", "qfold", patch])) ws_event.notify_events(ws_event.FILE_CHANGES|ws_event.PATCH_DELETE) return result def do_fold_patch_file(self, filename): res, sout, serr = _run_cmd_on_console(" < ".join(["patch -p1 --dry-run", filename])) if res != 0: return cmd_result.Result(cmd_result.ERROR, sout, serr) res, sout, serr = utils.run_cmd("hg status -u") old_unknowns = sout.splitlines() pres, pso, pse = _run_cmd_on_console(" < ".join(["patch -p1", filename])) if pres != 0: ws_event.notify_events(ws_event.FILE_CHANGES) return cmd_result.Result(pres, pso, pse) res, sout, serr = utils.run_cmd("hg status -u") for f in sout.splitlines(): if f not in old_unknowns: console.LOG.exec_cmd_with_alert("hg add " + f[2:]) ws_event.notify_events(ws_event.FILE_CHANGES) return cmd_result.Result(pres, pso, pse) def do_import_patch(self, filename, patchname=None, force=False): if patchname: cmd = "hg qimport -n %s " % patchname else: cmd = "hg qimport " patchname = os.path.basename(filename) if self.is_patch_applied(patchname): return cmd_result.Result(cmd_result.ERROR, "", "Patch %s is already applied!" % patchname) if force: cmd += "-f " result = _run_cmd_on_console(cmd + filename, mapper=_convert_import_res) ws_event.notify_events(ws_event.PATCH_CREATE) return result def do_move_file(self, file_path, dest, force=False): cmd = "hg rename " if force: cmd += "-f " cmd += " ".join([file_path, dest]) result = _run_cmd_on_console(cmd, mapper=_convert_copy_res) ws_event.notify_events(ws_event.FILE_ADD|ws_event.FILE_DEL) return result def do_new_patch(self, name, force=False): cmd = "hg qnew" if force: cmd += " -f" result = _run_cmd_on_console(" ".join([cmd, name]), mapper=_convert_push_res) ws_event.notify_events(ws_event.PATCH_CREATE|ws_event.PATCH_PUSH) return result def do_pop_to(self, patch=None): cmd = "hg qpop" if patch is not None: if patch is "": cmd += " -a" else: cmd += " " + patch result = _run_cmd_on_console(cmd, ignore_err_re=_qpop_re, mapper=_convert_pop_res) events = ws_event.PATCH_POP if not self.get_in_progress(): events |= ws_event.PMIC_CHANGE ws_event.notify_events(events) return result def do_push_to(self, patch=None, force=False, merge=False): in_charge = self.get_in_progress() if merge: cmd = 'hg -y qpush -m' else: cmd = 'hg qpush' if patch is None: if force: result = _run_cmd_on_console('%s -f' % cmd, ignore_err_re=_qpush_re) else: result = _run_cmd_on_console(cmd, ignore_err_re=_qpush_re) elif patch is "": if force: result = _run_cmd_on_console('%s -f -a' % cmd, ignore_err_re=_qpush_re) else: result = _run_cmd_on_console('%s -a' % cmd, ignore_err_re=_qpush_re) else: if force: result = _run_cmd_on_console('%s -f %s' % (cmd, patch), ignore_err_re=_qpush_re) else: result = _run_cmd_on_console('%s %s' % (cmd, patch), ignore_err_re=_qpush_re) if cmd_result.is_less_than_error(result[0]): events = ws_event.PATCH_PUSH if not in_charge: events |= ws_event.PMIC_CHANGE ws_event.notify_events(events) return result def do_refresh(self, patch=None, force=False, notify=True): cmd = "hg qrefresh" if force: return cmd_result.Result(cmd_result.ERROR, "", "mq has no concept of forced refresh") if patch is not None and patch != self.get_top_patch(): return cmd_result.Result(cmd_result.ERROR, "", "mq will not refresh non top patches") result = _run_cmd_on_console(cmd) if notify: ws_event.notify_events(ws_event.PATCH_REFRESH) return result def do_remove_files_from_patch(self, filelist, patch=None): top = self.get_top_patch() if patch and patch != top: return cmd_result.Result(cmd_result.ERROR, "", "mq will not modify non top patches") if not top: return cmd_result.Result(cmd_result.ERROR, "", "No patches applied") res, prev, serr = self._patch_most_recent_parent_rev(top) if res != cmd_result.OK: return cmd_result.Result(res, prev, serr) files = " ".join(filelist) console.LOG.exec_cmd_with_alert("hg qrefresh " + files) cmd = " ".join(["hg revert --rev", prev, files]) res, sout, serr = _run_cmd_on_console(cmd) ws_event.notify_events(ws_event.FILE_MOD|ws_event.FILE_ADD|ws_event.FILE_DEL) return cmd_result.Result(res, sout, serr) def do_rename_patch(self, patch, newname): cmd = "hg qrename " if patch is not None: cmd += patch + " " cmd += newname res, sout, serr = _run_cmd_on_console(cmd) ws_event.notify_events(ws_event.PATCH_CREATE|ws_event.PATCH_DELETE) if res != 0: return cmd_result.Result(cmd_result.ERROR, sout, serr) else: return cmd_result.Result(cmd_result.OK, sout, serr) def do_revert_files_in_patch(self, filelist, patch=None): top = self.get_top_patch() if patch and patch != top: return cmd_result.Result(cmd_result.ERROR, "", "mq will not modify non top patches") if not top: return cmd_result.Result(cmd_result.ERROR, "", "No patches applied") cmd = " ".join(["hg revert", " ".join(filelist)]) res, sout, serr = _run_cmd_on_console(cmd) ws_event.notify_events(ws_event.FILE_MOD|ws_event.FILE_ADD|ws_event.FILE_DEL) return cmd_result.Result(res, sout, serr) def do_select_guards(self, guards): if not guards: guards = "--none" result = _run_cmd_on_console('hg qselect %s' % guards) ws_event.notify_events(ws_event.PATCH_MODIFY) return result def do_set_patch_guards(self, patch_name, guards): cmd = 'hg qguard ' if not guards: cmd += "--none %s" % patch_name else: cmd += "-- %s %s" % (patch_name, guards) result = _run_cmd_on_console(cmd) ws_event.notify_events(ws_event.PATCH_MODIFY) return result def extdiff_and_full_patch_ok(self): return _hg_command_avail("extdiff") def get_all_patches_data(self): output = [] res, sout, serr = utils.run_cmd('hg qguard -l') patch_plus_guard_list = sout.splitlines() applied_patches = self.get_applied_patches() if len(applied_patches) > 0: top_patch = applied_patches[-1] for ppg in patch_plus_guard_list: name, guards = _extract_name_and_guards(ppg) if name in applied_patches: if name == top_patch: state = const.TOP_PATCH else: state = const.APPLIED else: state = const.NOT_APPLIED output.append(gquilt_tool.PatchData(name, state, guards)) else: for ppg in patch_plus_guard_list: name, guards = _extract_name_and_guards(ppg) output.append(gquilt_tool.PatchData(name, const.NOT_APPLIED, guards)) return output def get_applied_patches(self): res, sout, err = utils.run_cmd('hg qapplied') if res != 0: return [] return sout.splitlines() def get_author_name_and_email(self): res, uiusername, serr = utils.run_cmd("hg showconfig ui.username") if res == 0 and uiusername: return uiusername.strip() else: return gquilt_tool.Interface.get_author_name_and_email(self) def get_combined_diff(self, start_patch=None, end_patch=None): if self._no_patches_applied(): return cmd_result.Result(cmd_result.ERROR, "", "No patches applied") cmd = "hg diff --rev " if start_patch is None: res, prev, serr = self._patch_most_recent_parent_rev("qbase") else: res, prev, serr = self._patch_most_recent_parent_rev(start_patch) if res != cmd_result.OK: return cmd_result.Result(res, prev, serr) cmd += prev if end_patch is not None and end_patch != self.get_top_patch(): cmd += " -rev " + end_patch res, sout, serr = utils.run_cmd(cmd) if res != 0: return cmd_result.Result(cmd_result.ERROR, sout, serr) return cmd_result.Result(res, sout, serr) def get_description_is_finish_ready(self, patch): pfn = self.get_patch_file_name(patch) res, pf_descr_lines = putils.get_patch_descr_lines(pfn) pf_descr = os.linesep.join(pf_descr_lines).strip() if not pf_descr: return False res, rep_descr, sout = utils.run_cmd('hg log --template "{desc}" --rev %s' % patch) if pf_descr != rep_descr.strip(): top = self.get_top_patch() if top == patch: utils.run_cmd('hg qrefresh') else: # let's hope the user doesn't mind having the top patch refreshed utils.run_cmd('hg qrefresh') utils.run_cmd('hg qgoto %s' % patch) utils.run_cmd('hg qrefresh') utils.run_cmd('hg qgoto %s' % top) return True def get_diff(self, filelist=list(), patch=None): if not patch or patch == self.get_top_patch(): cmd = "hg qdiff" elif not self.is_patch_applied(patch): res, diff = putils.get_patch_diff_lines(self.get_patch_file_name(patch)) if res: return cmd_result.Result(cmd_result.OK, os.linesep.join(diff), []) return cmd_result.Result(cmd_result.ERROR, "", "Patch %s is not applied." % patch) else: res, prev, serr = self._patch_most_recent_parent_rev(patch) if res != cmd_result.OK: raise MQError(serr) cmd = "hg diff -r " + prev + " -r " + patch res, sout, serr = utils.run_cmd(" ".join([cmd, " ".join(filelist)])) if res != 0: return cmd_result.Result(cmd_result.ERROR, sout, serr) return cmd_result.Result(res, sout, serr) def get_diff_for_files(self, file_list=None, patch=None): if patch: parent = self.get_parent(patch) if not parent: # the patch is not applied pfn = self.get_patch_file_name(patch) result, diff = putils.get_patch_diff(pfn, file_list) if result: return cmd_result.Result(cmd_result.OK, diff, '') else: return cmd_result.Result(cmd_result.WARNING, '', diff) else: top = self.get_top_patch() if top: parent = self.get_parent(top) else: return cmd_result.Result(cmd_result.OK, '', '') cmd = 'hg diff --rev %s' % parent if patch: cmd += ' --rev %s' % patch if file_list: cmd += ' %s' % utils.file_list_to_string(file_list) res, sout, serr = utils.run_cmd(cmd) if res != 0: res = cmd_result.ERROR return cmd_result.Result(res, sout, serr) def get_in_progress(self): return self.get_top_patch() is not '' def get_next_patch(self): res, sout, _serr = utils.run_cmd("hg qnext") if res == 0: return sout.strip() else: return "" def get_base_patch(self): res, sout, serr = utils.run_cmd('hg qapplied') if res or not sout: return None else: return sout.splitlines()[0] def get_parent(self, patch): parent = 'qparent' for applied_patch in self.get_applied_patches(): if patch == applied_patch: return parent else: parent = applied_patch return None def get_patch_file_db(self, patch=None): if patch and not self.is_patch_applied(patch): pfn = _patch_file_name(patch) return putils.get_patch_file_db(pfn, PatchStatusMap) top = self.get_top_patch() if not top: # either we're not in an mq playground or no patches are applied return ScmFileDb([]) cmd = 'hg status -mardC' if patch: parent = self.get_parent(patch) cmd += ' --rev %s --rev %s' % (parent, patch) else: parent = self.get_parent(top) cmd += ' --rev %s' % parent res, sout, serr = utils.run_cmd(cmd) return ScmFileDb(sout.splitlines()) def get_patch_file_name(self, patch): return _patch_file_name(patch) def get_patch_files(self, patch=None, withstatus=True): if self._no_patches_applied(): return cmd_result.Result(cmd_result.OK, "", "") cmd = "hg status -m -a -d" if not withstatus: cmd += " -n" top = self.get_top_patch() if patch is None: patch = top res, prev, serr = self._patch_most_recent_parent_rev(patch) if res != cmd_result.OK: if patch and not self.is_patch_applied(patch): patchfile = self.get_patch_file_name(patch) is_ok, filelist = putils.get_patch_files(patchfile, withstatus) if is_ok: return cmd_result.Result(cmd_result.OK, filelist, "") else: return cmd_result.Result(cmd_result.ERROR, "", filelist) return cmd_result.Result(res, prev, serr) cmd += " --rev " + prev if patch != top: cmd += " --rev " + patch res, sout, serr = utils.run_cmd(cmd) if res != 0: return cmd_result.Result(cmd_result.ERROR, sout, serr) if withstatus: filelist = [] for line in sout.splitlines(): fobj = line[2:] if line[0] == "A": filelist.append((fobj, const.ADDED)) elif line[0] == "!": filelist.append((fobj, const.DELETED)) else: filelist.append((fobj, const.EXTANT)) else: filelist = sout.splitlines() return cmd_result.Result(res, filelist, serr) def get_patch_guards(self, patch): res, sout, err = utils.run_cmd('hg qguard ' + patch) if res != 0: return [] name, guards = _extract_name_and_guards(sout) return guards def get_playground_root(self, fdir=None): if fdir: old_dir = os.getcwd() os.chdir(fdir) res, root, serr = utils.run_cmd("hg root") if fdir: os.chdir(old_dir) if res == 0: return root.strip() else: return None def get_selected_guards(self): res, sout, err = utils.run_cmd('hg qselect') if res != 0 or sout.strip() == "no active guards": return [] return sout.split() def get_top_patch(self): res, sout, serr = utils.run_cmd("hg qtop") if res == 0: return sout.strip() else: return "" def get_ws_file_db(self): cmd = 'hg status -AC' qprev = self._get_qparent() if qprev is not None: cmd += ' --rev %s' % qprev res, sout, serr = utils.run_cmd(cmd) scm_file_db = ScmFileDb(sout.splitlines(), self._unresolved_file_list()) scm_file_db.decorate_dirs() return scm_file_db def has_add_files(self): return False def has_finish_patch(self): return True def has_guards(self): # override in back end if the tool supports patch guards return True def has_refresh_non_top(self): return False def is_available(self): return _hg_command_avail("mq") def is_patch_applied(self, patch): res, sout, _serr = utils.run_cmd("hg qapplied") return res == 0 and patch in sout.splitlines() def is_playground(self, fdir=None): root = self.get_playground_root(fdir) if not root: return False pdir = _patch_dir(root) return os.path.exists(pdir) and os.path.isdir(pdir) def last_patch_in_series(self): res, sout, serr = utils.run_cmd("hg qseries") if res != 0: raise MQError(serr) return sout.splitlines()[-1] def new_playground(self, fdir=None): if os.path.exists(_patch_dir(fdir)): return cmd_result.Result(cmd_result.OK, "", "") if not os.path.exists(_hg_dir(fdir)): cmd = "hg init" if fdir is not None: cmd += " " + fdir res, sout, serr = utils.run_cmd(cmd) if res != cmd_result.OK: return cmd_result.Result(res, sout, serr) cmd = "hg qinit" if fdir is not None: olddir = os.getcwd() os.chdir(fdir) res, sout, serr = utils.run_cmd(cmd) if fdir is not None: os.chdir(olddir) return cmd_result.Result(res, sout, serr) def requires(self): return "\"mercurial\" with \"mq\" extension enabled " gquilt-0.25/gquilt_pkg/config.py0000664000076400007640000003374711477025627017255 0ustar peterpeter00000000000000### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import gtk, gobject, os, collections, fnmatch from gquilt_pkg import table, utils, dialogue, gutils, tlview PARow = collections.namedtuple('PARow', ['Alias', 'Path']) PATH_ALIAS_MODEL_DESCR = PARow(Alias=gobject.TYPE_STRING, Path=gobject.TYPE_STRING) PATH_ALIAS_TABLE_DESCR = tlview.ViewTemplate( properties={ 'enable-grid-lines' : False, 'reorderable' : False, 'rules_hint' : False, 'headers-visible' : True, }, selection_mode=gtk.SELECTION_SINGLE, columns=[ tlview.Column( title='Alias', properties={'expand': False, 'resizable' : True}, cells=[ tlview.Cell( creator=tlview.CellCreator( function=gtk.CellRendererText, expand=False, start=True ), properties={'editable' : True}, renderer=None, attributes = {'text' : tlview.model_col(PATH_ALIAS_MODEL_DESCR, 'Alias')} ), ], ), tlview.Column( title='Path', properties={'expand': False, 'resizable' : True}, cells=[ tlview.Cell( creator=tlview.CellCreator( function=gtk.CellRendererText, expand=False, start=True ), properties={'editable' : False}, renderer=None, attributes = {'text' : tlview.model_col(PATH_ALIAS_MODEL_DESCR, 'Path')} ), ], ), ] ) GQUILT_D_NAME = os.sep.join([utils.HOME, ".gquilt.d"]) SAVED_WS_FILE_NAME = os.sep.join([GQUILT_D_NAME, "workspaces"]) if not os.path.exists(GQUILT_D_NAME): os.mkdir(GQUILT_D_NAME, 0o775) def append_saved_ws(path, alias=None): fobj = open(SAVED_WS_FILE_NAME, 'a') abbr_path = utils.path_rel_home(path) if not alias: alias = os.path.basename(path) fobj.write(os.pathsep.join([alias, abbr_path])) fobj.write(os.linesep) fobj.close() _KEYVAL_ESCAPE = gtk.gdk.keyval_from_name('Escape') class AliasPathTable(table.Table): def __init__(self, saved_file): self._saved_file = saved_file table.Table.__init__(self, model_descr=PATH_ALIAS_MODEL_DESCR, table_descr=PATH_ALIAS_TABLE_DESCR, size_req=(480, 160)) self.view.register_modification_callback(self.save_to_file) self.connect("key_press_event", self._key_press_cb) self.connect('button_press_event', self._handle_button_press_cb) self.set_contents() def _extant_path(self, path): return os.path.exists(os.path.expanduser(path)) def _fetch_contents(self): extant_ap_list = [] if not os.path.exists(self._saved_file): return [] fobj = open(self._saved_file, 'r') lines = fobj.readlines() fobj.close() for line in lines: data = PARow(*line.strip().split(os.pathsep, 1)) if data in extant_ap_list: continue if self._extant_path(data.Path): extant_ap_list.append(data) extant_ap_list.sort() self._write_list_to_file(extant_ap_list) return extant_ap_list def _write_list_to_file(self, ap_list): fobj = open(self._saved_file, 'w') for alpth in ap_list: fobj.write(os.pathsep.join(alpth)) fobj.write(os.linesep) fobj.close() def _same_paths(self, path1, path2): return utils.samefile(os.path.expanduser(path1), path2) def _default_alias(self, path): return os.path.basename(path) def _abbrev_path(self, path): return utils.path_rel_home(path) def add_ap(self, path, alias=""): if self._extant_path(path): model_iter = self.model.get_iter_first() while model_iter: if self._same_paths(self.model.get_labelled_value(model_iter, 'Path'), path): if alias: self.model.set_labelled_value(model_iter, 'Alias', alias) return model_iter = self.model.iter_next(model_iter) if not alias: alias = self._default_alias(path) data = PARow(Path=self._abbrev_path(path), Alias=alias) self.model.append(data) self.save_to_file() def save_to_file(self, _arg=None): ap_list = self.get_contents() self._write_list_to_file(ap_list) def get_selected_ap(self): data = self.get_selected_data_by_label(['Path', 'Alias']) if not data: return False return data[0] def _handle_button_press_cb(self, widget, event): if event.type == gtk.gdk.BUTTON_PRESS: if event.button == 2: self.seln.unselect_all() return True return False def _key_press_cb(self, widget, event): if event.keyval == _KEYVAL_ESCAPE: self.seln.unselect_all() return True return False class WSPathTable(AliasPathTable): def __init__(self): AliasPathTable.__init__(self, SAVED_WS_FILE_NAME) class PathSelectDialog(dialogue.Dialog): def __init__(self, create_table, label, parent=None): dialogue.Dialog.__init__(self, title="gwsmg: Select %s" % label, parent=parent, flags=gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK) ) hbox = gtk.HBox() self.ap_table = create_table() self.ap_table.seln.connect("changed", self._selection_cb) hbox.pack_start(self.ap_table) self.vbox.pack_start(hbox) hbox = gtk.HBox() hbox.pack_start(gtk.Label("%s:" % label)) self._path = gutils.EntryWithHistory() self._path.set_width_chars(32) self._path.connect("activate", self._path_cb) hbox.pack_start(self._path, expand=True, fill=True) self._browse_button = gtk.Button(label="_Browse") self._browse_button.connect("clicked", self._browse_cb) hbox.pack_start(self._browse_button, expand=False, fill=False) self.vbox.pack_start(hbox, expand=False, fill=False) self.show_all() self.ap_table.seln.unselect_all() self._path.set_text('') def _selection_cb(self, _selection=None): alpth = self.ap_table.get_selected_ap() if alpth: self._path.clear_to_history() self._path.set_text(alpth[0]) def _path_cb(self, entry=None): self.response(gtk.RESPONSE_OK) def _browse_cb(self, button=None): dirname = dialogue.ask_dir_name("gquilt: Browse for Directory", existing=True, parent=self) if dirname: self._path.set_text(utils.path_rel_home(dirname)) def get_path(self): return os.path.expanduser(self._path.get_text()) class WSOpenDialog(PathSelectDialog): def __init__(self, parent=None): PathSelectDialog.__init__(self, create_table=WSPathTable, label="Workspace/Directory", parent=parent) # Manage external editors EDITORS_THAT_NEED_A_TERMINAL = ["vi", "joe"] DEFAULT_EDITOR = "gedit" DEFAULT_TERMINAL = "gnome-terminal" if os.name == 'nt' or os.name == 'dos': DEFAULT_EDITOR = "notepad" for env in ['VISUAL', 'EDITOR']: try: ed = os.environ[env] if ed != "": DEFAULT_EDITOR = ed break except KeyError: pass DEFAULT_PERUSER = os.environ.get('GQUILT_PERUSER', None) for env in ['COLORTERM', 'TERM']: try: term = os.environ[env] if term != "": DEFAULT_TERMINAL = term break except KeyError: pass EDITOR_GLOB_FILE_NAME = os.sep.join([GQUILT_D_NAME, "editors"]) PERUSER_GLOB_FILE_NAME = os.sep.join([GQUILT_D_NAME, "perusers"]) def _read_editor_defs(edeff=EDITOR_GLOB_FILE_NAME): editor_defs = [] if os.path.isfile(edeff): for line in open(edeff, 'r').readlines(): eqi = line.find('=') if eqi < 0: continue glob = line[:eqi].strip() edstr = line[eqi+1:].strip() editor_defs.append([glob, edstr]) return editor_defs def _write_editor_defs(edefs, edeff=EDITOR_GLOB_FILE_NAME): fobj = open(edeff, 'w') for edef in edefs: fobj.write('='.join(edef)) fobj.write(os.linesep) fobj.close() if not os.path.exists(EDITOR_GLOB_FILE_NAME): _write_editor_defs([('*', DEFAULT_EDITOR)]) def _assign_extern_editors(file_list, edeff=EDITOR_GLOB_FILE_NAME): ed_assignments = {} unassigned_files = [] editor_defs = _read_editor_defs(edeff) for fobj in file_list: assigned = False for globs, edstr in editor_defs: for glob in globs.split(os.pathsep): if fnmatch.fnmatch(fobj, glob): if edstr in ed_assignments: ed_assignments[edstr].append(fobj) else: ed_assignments[edstr] = [fobj] assigned = True break if assigned: break if not assigned: unassigned_files.append(fobj) return ed_assignments, unassigned_files def assign_extern_editors(file_list): ed_assignments, unassigned_files = _assign_extern_editors(file_list, EDITOR_GLOB_FILE_NAME) if unassigned_files: if DEFAULT_EDITOR in ed_assignments: ed_assignments[DEFAULT_EDITOR] += unassigned_files else: ed_assignments[DEFAULT_EDITOR] = unassigned_files return ed_assignments def assign_extern_perusers(file_list): ed_assignments, unassigned_files = _assign_extern_editors(file_list, PERUSER_GLOB_FILE_NAME) extra_assigns = assign_extern_editors(unassigned_files) for key in extra_assigns: if key in ed_assignments: ed_assignments[key] += extra_assigns[key] else: ed_assignments[key] = extra_assigns[key] return ed_assignments GERow = collections.namedtuple('GERow', ['globs', 'editor']) EDITOR_GLOB_MODEL_DESCR = GERow(globs=gobject.TYPE_STRING, editor=gobject.TYPE_STRING) EDITOR_GLOB_TABLE_DESCR = tlview.ViewTemplate( properties={ 'enable-grid-lines' : True, 'reorderable' : True, }, selection_mode=gtk.SELECTION_MULTIPLE, columns=[ tlview.Column( title='File Pattern(s)', properties={'expand' : True}, cells=[ tlview.Cell( creator=tlview.CellCreator( function=gtk.CellRendererText, expand=False, start=True ), properties={'editable' : True}, renderer=None, attributes={'text' : tlview.model_col(EDITOR_GLOB_MODEL_DESCR, 'globs')} ), ], ), tlview.Column( title='Editor Command', properties={'expand' : True}, cells=[ tlview.Cell( creator=tlview.CellCreator( function=gtk.CellRendererText, expand=False, start=True ), properties={'editable' : True}, renderer=None, attributes={'text' : tlview.model_col(EDITOR_GLOB_MODEL_DESCR, 'editor')} ), ], ), ] ) class EditorAllocationTable(table.Table): def __init__(self, edeff=EDITOR_GLOB_FILE_NAME): table.Table.__init__(self, EDITOR_GLOB_MODEL_DESCR, EDITOR_GLOB_TABLE_DESCR, (320, 160)) self._edeff = edeff self.set_contents() def _fetch_contents(self): return _read_editor_defs(self._edeff) def apply_changes(self): _write_editor_defs(edefs=self.get_contents(), edeff=self._edeff) self.set_contents() class EditorAllocationDialog(dialogue.Dialog): def __init__(self, edeff=EDITOR_GLOB_FILE_NAME, parent=None): dialogue.Dialog.__init__(self, title='gquilt: Editor Allocation', parent=parent, flags=gtk.DIALOG_DESTROY_WITH_PARENT, buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE, gtk.STOCK_OK, gtk.RESPONSE_OK) ) self._table = EditorAllocationTable(edeff=edeff) self._buttons = gutils.ActionHButtonBox(list(self._table.action_groups.values())) self.vbox.pack_start(self._table) self.vbox.pack_start(self._buttons, expand=False) self.connect("response", self._handle_response_cb) self.show_all() self._table.view.get_selection().unselect_all() def _handle_response_cb(self, dialog, response_id): if response_id == gtk.RESPONSE_OK: self._table.apply_changes() self.destroy() class PeruserAllocationDialog(EditorAllocationDialog): def __init__(self, parent=None): EditorAllocationDialog.__init__(self, edeff=PERUSER_GLOB_FILE_NAME, parent=None) gquilt-0.25/gquilt_pkg/ifce.py0000664000076400007640000000667211505227617016706 0ustar peterpeter00000000000000### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os from gquilt_pkg import gquilt_tool, gquilt_quilt, gquilt_mq from gquilt_pkg import dialogue, cmd_result, config, ws_event _BACKEND = {} _MISSING_BACKEND = {} def _add_backend(newifce): if newifce.is_available(): _BACKEND[newifce.name] = newifce else: _MISSING_BACKEND[newifce.name] = newifce _add_backend(gquilt_quilt.Interface()) _add_backend(gquilt_mq.Interface()) _NULL_BACKEND = gquilt_tool.NullInterface() def backend_requirements(): msg = "No back ends are available. At least one of:" + os.linesep for key in list(_MISSING_BACKEND.keys()): msg += "\t" + _MISSING_BACKEND[key].requires() + os.linesep msg += "must be installed/available for \"gquilt\" to do.anything useful." return msg def report_backend_requirements(parent=None): dialogue.inform_user(backend_requirements(), parent=parent) def avail_backends(): return list(_BACKEND.keys()) def playground_type(dirpath=None): for bname in list(_BACKEND.keys()): if _BACKEND[bname].is_playground(dirpath): return bname return None ifce = _NULL_BACKEND in_valid_pgnd = ifce is not _NULL_BACKEND def set_ifce(dirpath=None): global ifce, in_valid_pgnd pgt = playground_type(dirpath) if pgt is None: ifce = _NULL_BACKEND else: ifce = _BACKEND[pgt] in_valid_pgnd = ifce is not _NULL_BACKEND def choose_backend(): bel = avail_backends() if len(bel) == 0: report_backend_requirements() return False, "" elif len(bel) == 1: return True, bel[0] sfld = dialogue.SelectFromListDialog(olist=bel, prompt="Choose back end:") is_ok, req_backend = sfld.make_selection() return is_ok, req_backend def new_playground(pgdir, backend=None): if backend is None: result = ifce.new_playground(pgdir) else: result = _BACKEND[backend].new_playground(pgdir) set_ifce() return result def chdir(newdir=None): global ifce, in_valid_pgnd if newdir: try: os.chdir(newdir) except OSError as err: import errno ecode = errno.errorcode[err.errno] emsg = err.strerror return (cmd_result.ERROR, '', '%s: "%s" :%s' % (ecode, newdir, emsg)) result = (cmd_result.OK, '', '') newpgtype = playground_type() if newpgtype: newdir = _BACKEND[newpgtype].get_playground_root() os.chdir(newdir) ifce = _BACKEND[newpgtype] config.append_saved_ws(newdir) elif len(_BACKEND) > 0: ifce = _NULL_BACKEND else: result = (cmd_result.ERROR, '', backend_requirements()) in_valid_pgnd = ifce is not _NULL_BACKEND ws_event.notify_events(ws_event.CHANGE_WD, newdir) return result gquilt-0.25/gquilt_pkg/ws_event.py0000664000076400007640000000751411476544765017643 0ustar peterpeter00000000000000### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ Provide mechanism for notifying components of events that require them to update their displayed/cached data """ import gobject PMIC_CHANGE = 1 FILE_ADD = 2 FILE_DEL = 4 FILE_MOD = 8 PATCH_PUSH = 16 PATCH_POP = 32 PATCH_REFRESH = 64 PATCH_CREATE = 128 PATCH_DELETE = 256 PATCH_MODIFY = 512 CHANGE_WD = 1024 _LAST_EVENT = CHANGE_WD ALL_EVENTS = _LAST_EVENT * 2 - 1 ALL_BUT_CHANGE_WD = ALL_EVENTS & ~CHANGE_WD FILE_CHANGES = FILE_ADD | FILE_DEL | FILE_MOD PATCH_CHANGES = PATCH_PUSH | PATCH_POP | PATCH_REFRESH | PATCH_CREATE | PATCH_DELETE | PATCH_MODIFY _NOTIFICATION_CBS = [] def add_notification_cb(events, callback): """ Register a callback for notification of the specified events. Arguments: events -- the set of events for which the callback should be callded. callback -- the procedure to be called. Return a token that identifies the callback to facilitate deletion. """ cb_token = (events, callback) _NOTIFICATION_CBS.append(cb_token) return cb_token def del_notification_cb(cb_token): """ Cancel the registration of a notification callback. Argument: cb_token -- the token that specifies the callback to be cancelled. """ index = _NOTIFICATION_CBS.index(cb_token) if index >= 0: del _NOTIFICATION_CBS[index] def notify_events(events, data=None): """ Notify interested parties of events that have occured. Argument: events -- a set of events that have just occured. Keyword Argument: data -- extra data the notifier thinks may be of use to the callback. """ invalid_cbs = [] for registered_events, callback in _NOTIFICATION_CBS: if registered_events & events: try: if data: callback(data) else: callback() except Exception: invalid_cbs.append((registered_events, callback)) for cb_token in invalid_cbs: del_notification_cb(cb_token) class Listener(gobject.GObject): """A base class for transient GTK object classes that wish to register event callbacks so that their callbacks are deleted when they are destroyed. """ def __init__(self): gobject.GObject.__init__(self) self._listener_cbs = [] self.connect('destroy', self._listener_destroy_cb) def add_notification_cb(self, events, callback): """ Register a callback for notification of the specified events. Record a token to facilitate deletion at a later time. Arguments: events -- the set of events for which the callback should be callded. callback -- the procedure to be called. Return a token that identifies the callback to facilitate deletion. """ self._listener_cbs.append(add_notification_cb(events, callback)) def _listener_destroy_cb(self, widget): """Remove all of my callbacks from the notification database""" for cb_token in self._listener_cbs: del_notification_cb(cb_token) # this callback seems to get called twice, so ... self._listener_cbs = [] gquilt-0.25/gquilt_pkg/actions.py0000664000076400007640000002433111476544760017440 0ustar peterpeter00000000000000### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ON_PGND_INDEP = 'ag_on_pgnd_indep' ON_IN_PGND = 'ag_on_in_pgnd' ON_NOT_IN_PGND = 'ag_on_not_in_pgnd' ON_IN_PGND_PMIC = ON_IN_PGND + '_pmic' ON_IN_PGND_NOT_PMIC = ON_IN_PGND + '_not_pmic' CLASS_INDEP_CONDS = [ ON_PGND_INDEP, ON_IN_PGND, ON_NOT_IN_PGND, ON_IN_PGND_PMIC, ON_IN_PGND_NOT_PMIC, ] ON_PGND_INDEP_SELN_INDEP = ON_PGND_INDEP + '_seln_indep' ON_IN_PGND_SELN_INDEP = ON_IN_PGND + '_seln_indep' ON_NOT_IN_PGND_SELN_INDEP = ON_NOT_IN_PGND + '_seln_indep' ON_IN_PGND_PMIC_SELN_INDEP = ON_IN_PGND_PMIC + '_seln_indep' ON_IN_PGND_NOT_PMIC_SELN_INDEP = ON_IN_PGND_NOT_PMIC + '_seln_indep' ON_PGND_INDEP_SELN = ON_PGND_INDEP + '_seln' ON_IN_PGND_SELN = ON_IN_PGND + '_seln' ON_NOT_IN_PGND_SELN = ON_NOT_IN_PGND + '_seln' ON_IN_PGND_PMIC_SELN = ON_IN_PGND_PMIC + '_seln' ON_IN_PGND_NOT_PMIC_SELN = ON_IN_PGND_NOT_PMIC + '_seln' ON_PGND_INDEP_NO_SELN = ON_PGND_INDEP + '_no_seln' ON_IN_PGND_NO_SELN = ON_IN_PGND + '_no_seln' ON_NOT_IN_PGND_NO_SELN = ON_NOT_IN_PGND + '_no_seln' ON_IN_PGND_PMIC_NO_SELN = ON_IN_PGND_PMIC + '_no_seln' ON_IN_PGND_NOT_PMIC_NO_SELN = ON_IN_PGND_NOT_PMIC + '_no_seln' ON_PGND_INDEP_UNIQUE_SELN = ON_PGND_INDEP + '_unique_seln' ON_IN_PGND_UNIQUE_SELN = ON_IN_PGND + '_unique_seln' ON_NOT_IN_PGND_UNIQUE_SELN = ON_NOT_IN_PGND + '_unique_seln' ON_IN_PGND_PMIC_UNIQUE_SELN = ON_IN_PGND_PMIC + '_unique_seln' ON_IN_PGND_NOT_PMIC_UNIQUE_SELN = ON_IN_PGND_NOT_PMIC + '_unique_seln' CLASS_DEP_SELN_INDEP_CONDS = [ ON_PGND_INDEP_SELN_INDEP, ON_IN_PGND_SELN_INDEP, ON_NOT_IN_PGND_SELN_INDEP, ON_IN_PGND_PMIC_SELN_INDEP, ON_IN_PGND_NOT_PMIC_SELN_INDEP, ] CLASS_DEP_SELN_DEP_CONDS = [ ON_PGND_INDEP_SELN, ON_IN_PGND_SELN, ON_NOT_IN_PGND_SELN, ON_IN_PGND_PMIC_SELN, ON_IN_PGND_NOT_PMIC_SELN, ON_PGND_INDEP_NO_SELN, ON_IN_PGND_NO_SELN, ON_NOT_IN_PGND_NO_SELN, ON_IN_PGND_PMIC_NO_SELN, ON_IN_PGND_NOT_PMIC_NO_SELN, ON_PGND_INDEP_UNIQUE_SELN, ON_IN_PGND_UNIQUE_SELN, ON_NOT_IN_PGND_UNIQUE_SELN, ON_IN_PGND_PMIC_UNIQUE_SELN, ON_IN_PGND_NOT_PMIC_UNIQUE_SELN, ] class_indep_ags = {} import gtk for condition in CLASS_INDEP_CONDS: class_indep_ags[condition] = gtk.ActionGroup(condition) from gquilt_pkg import ifce, ws_event, gutils def update_class_indep_sensitivities(_arg=None): in_pgnd = ifce.in_valid_pgnd pmic = in_pgnd and ifce.ifce.get_in_progress() class_indep_ags[ON_IN_PGND].set_sensitive(in_pgnd) class_indep_ags[ON_NOT_IN_PGND].set_sensitive(not in_pgnd) class_indep_ags[ON_IN_PGND_PMIC].set_sensitive(pmic) class_indep_ags[ON_IN_PGND_NOT_PMIC].set_sensitive(in_pgnd and not pmic) def add_class_indep_action(cond, action): class_indep_ags[cond].add_action(action) def add_class_indep_actions(cond, actions): class_indep_ags[cond].add_actions(actions) def get_class_indep_action(action_name): for cond in CLASS_INDEP_CONDS: action = class_indep_ags[cond].get_action(action_name) if action: return action return None update_class_indep_sensitivities() ws_event.add_notification_cb(ws_event.CHANGE_WD|ws_event.PMIC_CHANGE, update_class_indep_sensitivities) class AGandUIManager(ws_event.Listener): def __init__(self, selection=None): ws_event.Listener.__init__(self) self.ui_manager = gutils.UIManager() for cond in CLASS_INDEP_CONDS: self.ui_manager.insert_action_group(class_indep_ags[cond], -1) self.seln = selection self._action_groups = {} for cond in CLASS_DEP_SELN_INDEP_CONDS: self._action_groups[cond] = gtk.ActionGroup(cond) self.ui_manager.insert_action_group(self._action_groups[cond], -1) if self.seln: for cond in CLASS_DEP_SELN_DEP_CONDS: self._action_groups[cond] = gtk.ActionGroup(cond) self.ui_manager.insert_action_group(self._action_groups[cond], -1) self.seln.connect('changed', self._seln_cond_change_cb) self.add_notification_cb(ws_event.CHANGE_WD|ws_event.PMIC_CHANGE, self._event_cond_change_cb) self.init_action_states() def _seln_cond_change_update(self, seln, in_pgnd, pmic): selsz = seln.count_selected_rows() self._action_groups[ON_PGND_INDEP_SELN].set_sensitive(selsz > 0) self._action_groups[ON_PGND_INDEP_NO_SELN].set_sensitive(selsz == 0) self._action_groups[ON_PGND_INDEP_UNIQUE_SELN].set_sensitive(selsz == 1) if in_pgnd: self._action_groups[ON_IN_PGND_SELN].set_sensitive(selsz > 0) self._action_groups[ON_IN_PGND_NO_SELN].set_sensitive(selsz == 0) self._action_groups[ON_IN_PGND_UNIQUE_SELN].set_sensitive(selsz == 1) for cond in [ON_NOT_IN_PGND_SELN, ON_NOT_IN_PGND_NO_SELN, ON_NOT_IN_PGND_UNIQUE_SELN]: self._action_groups[cond].set_sensitive(False) if pmic: self._action_groups[ON_IN_PGND_PMIC_SELN].set_sensitive(selsz > 0) self._action_groups[ON_IN_PGND_PMIC_NO_SELN].set_sensitive(selsz == 0) self._action_groups[ON_IN_PGND_PMIC_UNIQUE_SELN].set_sensitive(selsz == 1) for cond in [ON_IN_PGND_NOT_PMIC_SELN, ON_IN_PGND_NOT_PMIC_NO_SELN, ON_IN_PGND_NOT_PMIC_UNIQUE_SELN]: self._action_groups[cond].set_sensitive(False) else: self._action_groups[ON_IN_PGND_NOT_PMIC_SELN].set_sensitive(selsz > 0) self._action_groups[ON_IN_PGND_NOT_PMIC_NO_SELN].set_sensitive(selsz == 0) self._action_groups[ON_IN_PGND_NOT_PMIC_UNIQUE_SELN].set_sensitive(selsz == 1) for cond in [ON_IN_PGND_PMIC_SELN, ON_IN_PGND_PMIC_NO_SELN, ON_IN_PGND_PMIC_UNIQUE_SELN]: self._action_groups[cond].set_sensitive(False) else: self._action_groups[ON_NOT_IN_PGND_SELN].set_sensitive(selsz > 0) self._action_groups[ON_NOT_IN_PGND_NO_SELN].set_sensitive(selsz == 0) self._action_groups[ON_NOT_IN_PGND_UNIQUE_SELN].set_sensitive(selsz == 1) for cond in [ON_IN_PGND_SELN, ON_IN_PGND_NO_SELN, ON_IN_PGND_UNIQUE_SELN]: self._action_groups[cond].set_sensitive(False) for cond in [ON_IN_PGND_NOT_PMIC_SELN, ON_IN_PGND_NOT_PMIC_NO_SELN, ON_IN_PGND_NOT_PMIC_UNIQUE_SELN]: self._action_groups[cond].set_sensitive(False) for cond in [ON_IN_PGND_PMIC_SELN, ON_IN_PGND_PMIC_NO_SELN, ON_IN_PGND_PMIC_UNIQUE_SELN]: self._action_groups[cond].set_sensitive(False) def _seln_cond_change_cb(self, seln): in_pgnd = ifce.in_valid_pgnd pmic = in_pgnd and ifce.ifce.get_in_progress() self._seln_cond_change_update(seln, in_pgnd, pmic) def _event_cond_change_cb(self, arg=None): in_pgnd = ifce.in_valid_pgnd pmic = in_pgnd and ifce.ifce.get_in_progress() self._action_groups[ON_IN_PGND_SELN_INDEP].set_sensitive(in_pgnd) self._action_groups[ON_NOT_IN_PGND_SELN_INDEP].set_sensitive(not in_pgnd) self._action_groups[ON_IN_PGND_PMIC_SELN_INDEP].set_sensitive(pmic) self._action_groups[ON_IN_PGND_NOT_PMIC_SELN_INDEP].set_sensitive(in_pgnd and not pmic) if self.seln: self._seln_cond_change_update(self.seln, in_pgnd, pmic) def add_new_action_group(self, cond): self._action_groups[cond] = gtk.ActionGroup(cond) self.ui_manager.insert_action_group(self._action_groups[cond], -1) def add_conditional_action(self, cond, action): self._action_groups[cond].add_action(action) def add_conditional_actions(self, cond, actions): self._action_groups[cond].add_actions(actions) def get_conditional_action(self, action_name): conditions = CLASS_DEP_SELN_INDEP_CONDS[:] if self.seln: conditions += CLASS_DEP_SELN_DEP_CONDS for cond in conditions: action = self._action_groups[cond].get_action(action_name) if action: return action def copy_conditional_action(self, action_name, new_cond): conditions = CLASS_DEP_SELN_INDEP_CONDS[:] if self.seln: conditions += CLASS_DEP_SELN_DEP_CONDS for cond in conditions: action = self._action_groups[cond].get_action(action_name) if action: self._action_groups[new_cond].add_action(action) return def move_conditional_action(self, action_name, new_cond): conditions = CLASS_DEP_SELN_INDEP_CONDS[:] if self.seln: conditions += CLASS_DEP_SELN_DEP_CONDS for cond in conditions: action = self._action_groups[cond].get_action(action_name) if action: self._action_groups[cond].remove_action(action) self._action_groups[new_cond].add_action(action) return def init_action_states(self): self._event_cond_change_cb() def create_action_button(self, action_name, use_underline=True): action = self.get_conditional_action(action_name) return gutils.ActionButton(action, use_underline=use_underline) def create_action_button_box(self, action_name_list, use_underline=True, horizontal=True, expand=True, fill=True, padding=0): if horizontal: box = gtk.HBox() else: box = gtk.VBox() for action_name in action_name_list: button = self.create_action_button(action_name, use_underline) box.pack_start(button, expand, fill, padding) return box gquilt-0.25/gquilt_pkg/__init__.py0000664000076400007640000000000011450502642017503 0ustar peterpeter00000000000000gquilt-0.25/gquilt_pkg/gquilt_quilt.py0000664000076400007640000005357211516667750020534 0ustar peterpeter00000000000000# -*- python -*- ### Copyright (C) 2005 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # This file provides access to "quilt" functionality required by "gquilt" import sys, os, os.path, re, pango from gquilt_pkg import gquilt_tool from gquilt_pkg import console, fsdb, ws_event, cmd_result from gquilt_pkg import const from gquilt_pkg import putils from gquilt_pkg import utils FSTATUS_MODIFIED = ' ' FSTATUS_ADDED = '+' FSTATUS_REMOVED = '-' FSTATUS_IGNORED = 'I' PatchStatusMap = putils.StatusMap(extant=FSTATUS_MODIFIED, added=FSTATUS_ADDED, deleted=FSTATUS_REMOVED) FSTATUS_MODIFIED_SET = set([FSTATUS_MODIFIED, FSTATUS_ADDED, FSTATUS_REMOVED]) class PatchDir(fsdb.GenDir): def __init__(self): fsdb.GenDir.__init__(self) def _new_dir(self): return PatchDir() def _update_own_status(self): if self.status_set & FSTATUS_MODIFIED_SET: self.status = FSTATUS_MODIFIED def _is_hidden_dir(self, dkey): status = self.subdirs[dkey].status if status not in [FSTATUS_MODIFIED]: return dkey[0] == '.' or status == FSTATUS_IGNORED return False def _is_hidden_file(self, fdata): if fdata.status not in FSTATUS_MODIFIED_SET: return fdata.name[0] == '.' or fdata.status == FSTATUS_IGNORED return False class PatchFileDb(fsdb.GenFileDb): def __init__(self, file_list): fsdb.GenFileDb.__init__(self, PatchDir) for item in file_list: self.base_dir.add_file(item[2:].split(os.sep), item[0], None) class QuiltCommands: def __init__(self): self.cmds = self._get_commands() def _get_commands(self): _res, sout, _serr = utils.run_cmd("quilt") if sout == "": return None lines = sout.splitlines() index = 2 cmds = [] while True: lcmds = lines[index].split() if len(lcmds) > 0: cmds += lcmds else: cmds.sort() return cmds index += 1 def has_cmd(self, cmd): return cmd in self.cmds def get_list(self): return self.cmds # Find the value of a specified quilt environment variable. These can be set # in as environment variables or in /etc/quilt.quiltrc, ~/.quiltrc or the file # specified by the QUILTRC environment variable (if they exist). Only one of # the files will be read with precedence in the reverse of the order listed. # Any variable set in the file that is read will override the value for the # same variable that has been set as an environment variable. # So we need to do the same so we know what quilt will see. _DEFAULT_ENV_VAR_VALUE = { 'QUILT_PATCHES': 'patches', 'QUILT_SERIES': 'series' } def _get_quilt_env_var_value(vname, default_ok=False): if default_ok: try: val = _DEFAULT_ENV_VAR_VALUE[vname] except LookupError: val = None else: val = None try: quiltrc = os.environ['QUILTRC'] except LookupError: for fname in [os.path.expanduser("~/.quiltrc"), "/etc/quilt.quiltrc"]: if os.path.exists(fname): quiltrc = fname break else: quiltrc = None if quiltrc: regex = re.compile("^\w*%s=(.*)$" % vname) quiltrc_f = open(quiltrc, 'r') for line in quiltrc_f.readlines(): m = regex.match(line.strip()) if m: val = m.group(1) break quiltrc_f.close() else: try: val = os.environ[vname] except LookupError: pass return val def find_patchfns(): binloc = utils.which("quilt") if binloc is None: return None bindex = binloc.find("bin" + os.path.sep + "quilt") if bindex == -1: return None res = os.path.join(binloc[0:bindex], "share", "quilt", "scripts", "patchfns") if not os.path.exists(res) or not os.path.isfile(res): return None return res def _find_gquilt_lib_dir(): try: result = os.environ['GQUILT_LIB_DIR'] except LookupError: result = None for path in sys.path: tempth = os.path.join(path, 'gquilt_pkg') if os.path.exists(tempth) and os.path.isdir(tempth): result = tempth break return result _QBSFE_FILE_NAME = os.path.join(os.sep, 'etc', 'gquilt.d', 'qbsfe' + os.extsep + 'sh') if not os.path.exists(_QBSFE_FILE_NAME): _QBSFE_FILE_NAME = os.path.join(_find_gquilt_lib_dir(), 'qbsfe' + os.extsep + 'sh') if not os.path.exists(_QBSFE_FILE_NAME): raise Exception('Could not find "qbsfe.sh"') _PATCHFNS = find_patchfns() if _PATCHFNS is None: _QBSFE = None else: _QBSFE = 'bash -c ". ' + _QBSFE_FILE_NAME + '" gquilt ' + _PATCHFNS class QuiltError(Exception): """A quilt specific error""" def internal(cmd, cmd_input=None): if _QBSFE is None: raise QuiltError("Couldn't find patchfuns") return utils.run_cmd(" ".join([_QBSFE, cmd]), cmd_input) # Some useful private quilt functions (that shouldn't fail) def _find_patch(patch): res, sout, serr = internal(" ".join(["find_patch", patch])) if res != 0: raise QuiltError(serr) return sout[:-1] def _patch_file_name(patch): res, sout, serr = internal(" ".join(["patch_file_name", patch])) if res != 0: raise QuiltError(serr) return sout[:-1] def _patch_basename(patchfile): res, sout, serr = internal(" ".join(["basename", patchfile])) if res != 0: raise QuiltError(serr) return sout[:-1] def _convert_pop_res(res, sout, serr): if res != cmd_result.OK: if re.search("needs to be refreshed first", sout + serr): res |= cmd_result.SUGGEST_REFRESH elif re.search("\(refresh it or enforce with -f\)", sout + serr): res |= cmd_result.SUGGEST_FORCE_OR_REFRESH return cmd_result.Result(res, sout, serr) def _convert_push_res(res, sout, serr): if res != cmd_result.OK: if re.search("\(forced; needs refresh\)", sout + serr): res = cmd_result.OK elif re.search("needs to be refreshed first", sout + serr): res |= cmd_result.SUGGEST_REFRESH elif re.search("\(enforce with -f\)", sout + serr): res |= cmd_result.SUGGEST_FORCE return cmd_result.Result(res, sout, serr) def _convert_import_res(res, sout, serr): if res != cmd_result.OK and re.search("Replace with -f", sout + serr): res |= cmd_result.SUGGEST_FORCE_OR_RENAME return cmd_result.Result(res, sout, serr) def _quilt_version(): res, sout, _serr = utils.run_cmd("quilt --version") if res != 0: return None return sout def _quilt_version_ge(rmajor, rminor): vstr = _quilt_version() if not vstr: return False vstrs = vstr.split(".") amajor = int(vstrs[0]) if amajor > rmajor: return True elif amajor < rmajor: return False else: return int(vstrs[1]) >= rminor def _patch_dir(fdir=None): pdir = _get_quilt_env_var_value('QUILT_PATCHES', default_ok=True) if fdir is None: fdir = os.getcwd() return os.path.join(fdir, pdir) def _series_file(fdir=None): sname = _get_quilt_env_var_value('QUILT_SERIES', default_ok=True) return os.path.join(_patch_dir(fdir), sname) # Now implement the tool interface for quilt class Interface(gquilt_tool.Interface): def __init__(self): gquilt_tool.Interface.__init__(self, "quilt") self.status_deco_map = { None: gquilt_tool.Deco(pango.STYLE_NORMAL, "black"), FSTATUS_MODIFIED: gquilt_tool.Deco(pango.STYLE_NORMAL, "blue"), FSTATUS_ADDED: gquilt_tool.Deco(pango.STYLE_NORMAL, "darkgreen"), FSTATUS_REMOVED: gquilt_tool.Deco(pango.STYLE_NORMAL, "red"), FSTATUS_IGNORED: gquilt_tool.Deco(pango.STYLE_ITALIC, "grey"), } def _map_cmd_result(self, result, ignore_err_re=None): if result.eflags == 0: return cmd_result.map_cmd_result(result, ignore_err_re=ignore_err_re) else: flags = cmd_result.ERROR return cmd_result.Result(flags, result.stdout, result.serr) def display_files_diff_in_viewer(self, viewer, files, patch=None): difffld = "--diff=" + viewer if patch is None: pid = os.spawnlp(os.P_NOWAIT, "quilt", "quilt", "diff", difffld, files[0]) else: pid = os.spawnlp(os.P_NOWAIT, "quilt", "quilt", "diff", difffld, "-P", patch, files[0]) def do_add_files_to_patch(self, filelist): cmd = "quilt add" # quilt will screw up semi silently if you try to add directories to a patch for f in filelist: if os.path.isdir(f): ws_event.notify_events(ws_event.FILE_ADD) return cmd_result.Result(cmd_result.ERROR, "", f + ' is a directory.\n "quilt" does not handle adding directories to patches\n') result = console.exec_console_cmd(" ".join([cmd, " ".join(filelist)])) ws_event.notify_events(ws_event.FILE_ADD) return result def do_delete_patch(self, patch): result = console.exec_console_cmd(" ".join(["quilt", "delete", patch])) ws_event.notify_events(ws_event.PATCH_DELETE) return result def do_exec_tool_cmd(self, cmd): result = console.exec_console_cmd(" ".join(["quilt", cmd])) ws_event.notify_events(ws_event.FILE_MOD|ws_event.FILE_ADD|ws_event.ALL_EVENTS) return result def do_finish_patch(self, patch): return cmd_result.Result(cmd_result.ERROR, "", '"quilt" does not have this feature') def do_fold_patch(self, patch): filename = self.get_patch_file_name(patch) res, sout, serr = self.do_fold_patch_file(filename) if res == 0: self.do_delete_patch(patch) return cmd_result.Result(res, sout, serr) def do_fold_patch_file(self, filename): result = console.exec_console_cmd(" < ".join(["quilt fold", filename])) ws_event.notify_events(ws_event.FILE_CHANGES) return result def do_import_patch(self, filename, patchname=None, force=False): if patchname: cmd = "quilt import -P %s " % patchname else: cmd = "quilt import " if force: cmd += "-f -d n " res, sout, serr = console.exec_console_cmd(cmd + filename) ws_event.notify_events(ws_event.PATCH_CREATE) return _convert_import_res(res, sout, serr) def do_new_patch(self, name, force=False): if force: return cmd_result.Result(cmd_result.ERROR, "", "\"quilt\" cannot force \"new\"") result = console.exec_console_cmd(" ".join(["quilt", "new", name])) ws_event.notify_events(ws_event.PATCH_CREATE|ws_event.PATCH_PUSH) return result def do_pop_to(self, patch=None): cmd = "quilt pop" if patch is not None: if patch is "": cmd += " -a" else: cmd += " " + patch res, sout, serr = console.exec_console_cmd(cmd) events = ws_event.PATCH_POP if not self.get_in_progress(): events |= ws_event.PMIC_CHANGE ws_event.notify_events(events) return _convert_pop_res(res, sout, serr) def do_push_to(self, patch=None, force=False, merge=False): in_charge = self.get_in_progress() cmd = "quilt push" if force: cmd += " -f" if patch is not None: if patch is "": cmd += " -a" else: cmd += " " + patch res, sout, serr = console.exec_console_cmd(cmd) events = ws_event.PATCH_PUSH if not in_charge: events |= ws_event.PMIC_CHANGE ws_event.notify_events(events) return _convert_push_res(res, sout, serr) def do_refresh(self, patch=None, force=False, notify=True): cmd = "quilt refresh" if force: cmd += " -f" if patch is not None: cmd = " ".join([cmd, patch]) res, sout, serr = console.exec_console_cmd(cmd) if res != cmd_result.OK: if re.search("Enforce refresh with -f", sout + serr): res = cmd_result.ERROR_SUGGEST_FORCE else: res = cmd_result.ERROR if notify: ws_event.notify_events(ws_event.PATCH_REFRESH) return cmd_result.Result(res, sout, serr) def do_remove_files_from_patch(self, filelist, patch=None): if QuiltCommands().has_cmd("remove"): if patch == None: cmd = "quilt remove" else: if _quilt_version_ge(0, 43): cmd = "quilt remove -P " + patch else: cmd = "quilt remove -p " + patch # quilt will screw up semi silently if you try to remove directories from a patch for f in filelist: if os.path.isdir(f): return cmd_result.Result(cmd_result.ERROR, "", f + ' is a directory.\n "quilt" does not handle removing directories from patches\n') result = console.exec_console_cmd(" ".join([cmd, " ".join(filelist)])) else: result = (cmd_result.ERROR, "", '"quilt" does not handle removing files from patches\n') ws_event.notify_events(ws_event.FILE_MOD|ws_event.FILE_ADD|ws_event.FILE_DEL) return result def do_rename_patch(self, patch, newname): cmd = "quilt rename " if patch is not None: if _quilt_version_ge(0, 43): cmd += "-P " + patch + " " else: cmd += "-p " + patch + " " cmd += newname res, sout, serr = console.exec_console_cmd(cmd) ws_event.notify_events(ws_event.PATCH_CREATE|ws_event.PATCH_DELETE) if res != 0: return cmd_result.Result(cmd_result.ERROR, sout, serr) else: return cmd_result.Result(cmd_result.OK, sout, serr) def do_revert_files_in_patch(self, filelist, patch=None): if QuiltCommands().has_cmd("revert"): if patch == None: cmd = "quilt revert" else: cmd = "quilt revert -P " + patch # quilt will screw up semi silently if you try to revert directories in a patch for f in filelist: if os.path.isdir(f): return cmd_result.Result(cmd_result.ERROR, "", f + ' is a directory.' + os.linesep + '"quilt" does not handle reversion for directories in patches' + os.linesep) result = console.exec_console_cmd(" ".join([cmd, " ".join(filelist)])) else: result = (cmd_result.ERROR, "", '"quilt" does not handle reverting changes to in from patches\n') ws_event.notify_events(ws_event.FILE_MOD|ws_event.FILE_ADD|ws_event.FILE_DEL) return result def do_select_guards(self, guards): return cmd_result.Result(cmd_result.ERROR, '', 'quilt does not support guards') def do_set_patch_guards(self, patch_name, guards): return cmd_result.Result(cmd_result.ERROR, '', 'quilt does not support guards') def extdiff_and_full_patch_ok(self): return False def get_all_patches_data(self): output = [] res, sout, serr = utils.run_cmd('quilt series -v') for line in sout.splitlines(): pname = line[2:] if line[0] == " ": output.append(gquilt_tool.PatchData(pname, const.NOT_APPLIED, [])) else: output.append(gquilt_tool.PatchData(pname, const.APPLIED, [])) return output def get_applied_patches(self): res, sout, err = utils.run_cmd('quilt applied') if res != 0: return [] return sout.splitlines() def get_combined_diff(self, start_patch=None, end_patch=None): cmd = "quilt diff --sort --combine " if start_patch is None: cmd += "-" else: cmd += start_patch if end_patch is not None: cmd += " -P " + end_patch res, sout, serr = utils.run_cmd(cmd) if res != 0: return cmd_result.Result(cmd_result.ERROR, sout, serr) return cmd_result.Result(res, sout, serr) def get_diff(self, filelist=list(), patch=None): if patch is None: cmd = "quilt diff" else: cmd = "quilt diff -P " + patch res, sout, serr = utils.run_cmd(" ".join([cmd, " ".join(filelist)])) if res != 0: if not self.is_patch_applied(patch): res, diff = putils.get_patch_diff_lines(self.get_patch_file_name(patch)) if res: return cmd_result.Result(cmd_result.OK, os.linesep.join(diff), []) return cmd_result.Result(cmd_result.ERROR, sout, serr) return cmd_result.Result(res, sout, serr) def get_diff_for_files(self, file_list=None, patch=None): return self.get_diff(filelist=file_list, patch=patch) def get_in_progress(self): return self.get_top_patch() is not '' def get_next_patch(self): res, sout, serr = utils.run_cmd("quilt next") if res == 0 or (serr.strip() == "" and sout.strip() == ""): return sout.strip() elif (res == 512 or res == 2) and sout.strip() == "": return "" else: raise QuiltError(serr) def get_patch_file_db(self, patch=None): if patch and not self.is_patch_applied(patch): pfn = self.get_patch_file_name(patch) return putils.get_patch_file_db(pfn, PatchStatusMap) top = self.get_top_patch() if not top: # either we're not in an mq playground or no patches are applied return PatchFileDb([]) cmd = 'quilt files -v' if patch is not None: cmd += " " + patch res, sout, serr = utils.run_cmd(cmd) return PatchFileDb(sout.splitlines()) def get_patch_file_name(self, patch): return _patch_file_name(_find_patch(patch)) def get_patch_files(self, patch=None, withstatus=True): cmd = "quilt files" if withstatus: cmd += " -v" if patch is not None: cmd += " " + patch elif self.get_top_patch() == "": return cmd_result.Result(cmd_result.OK, "", "") res, sout, serr = utils.run_cmd(cmd) if res != 0: if patch and not self.is_patch_applied(patch): patchfile = self.get_patch_file_name(patch) is_ok, filelist = putils.get_patch_files(patchfile, withstatus) if is_ok: return cmd_result.Result(cmd_result.OK, filelist, "") else: return cmd_result.Result(cmd_result.ERROR, "", filelist) return cmd_result.Result(cmd_result.ERROR, sout, serr) if withstatus: filelist = [] for line in sout.splitlines(): if line[0] == "+": filelist.append((line[2:], const.ADDED)) elif line[0] == "-": filelist.append((line[2:], const.DELETED)) else: filelist.append((line[2:], const.EXTANT)) else: filelist = sout.splitlines() return cmd_result.Result(res, filelist, serr) def get_patch_guards(self, patch): return [] def get_playground_root(self, fdir=None): pdir = _get_quilt_env_var_value('QUILT_PATCHES', default_ok=True) if not fdir: fdir = os.getcwd() root = fdir while True: apd = os.path.join(root, pdir) if os.path.exists(apd) and os.path.isdir(apd): return root newroot = os.path.dirname(root) if root == newroot: break root = newroot return None def get_selected_guards(self): return [] def get_top_patch(self): res, sout, serr = utils.run_cmd("quilt top") if res == 0 or (serr.strip() == "" and sout.strip() == ""): return sout.strip() elif (res == 512 or res == 2) and sout.strip() == "": return "" else: raise QuiltError(serr) def get_ws_file_db(self): return fsdb.OsFileDb() def has_add_files(self): return True def has_finish_patch(self): return False def has_refresh_non_top(self): return True def is_available(self): if _quilt_version() is None: return False return True def is_patch_applied(self, patch): res, sout, serr = internal("is_applied " + patch) return res == 0 def is_playground(self, fdir=None): root = self.get_playground_root(fdir) if not root: return False return os.path.exists(_series_file(root)) def last_patch_in_series(self): res, sout, serr = utils.run_cmd("quilt series") if res != 0: raise QuiltError(serr) return sout.splitlines()[-1] def new_playground(self, fdir=None): patch_dir = _patch_dir(fdir) if not os.path.exists(patch_dir): try: os.makedirs(patch_dir) except os.error as value: return cmd_result.Result(cmd_result.ERROR, "", value[1]) return utils.run_cmd("touch " + _series_file(fdir)) def requires(self): return "\"quilt\" " gquilt-0.25/gquilt_pkg/icons.py0000664000076400007640000000750411476555211017107 0ustar peterpeter00000000000000### Copyright (C) 2005 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import gtk, gtk.gdk, os, os.path, sys, collections # find the icons directory # first look in the source directory (so that we can run uninstalled) _ICON_DIR = os.path.join(sys.path[0],'pixmaps') if not os.path.exists(_ICON_DIR) or not os.path.isdir(_ICON_DIR): _TAILEND = os.path.join('share', 'pixmaps', 'gquilt') _prefix = sys.path[0] while _prefix: _ICON_DIR = os.path.join(_prefix, _TAILEND) if os.path.exists(_ICON_DIR) and os.path.isdir(_ICON_DIR): break _prefix = os.path.dirname(_prefix) _APP_ICON = "gquilt" APP_ICON_FILE = os.path.join(os.path.dirname(_ICON_DIR), _APP_ICON + os.extsep + "png") POP = "gquilt_stock_pop" PUSH = "gquilt_stock_push" FOLD = "gquilt_stock_fold" IMPORT_PATCH = "gquilt_stock_import" DIFF = "gquilt_stock_diff" MELD = "gquilt_stock_meld" APPLIED_OK = "gquilt_stock_tick" APPLIED_NOT_OK = "gquilt_stock_cross" TOP_OK = "gquilt_stock_tick" TOP_NOT_OK = "gquilt_stock_cross" FINISH = "gquilt_stock_finish" STOCK_NEW_PATCH = 'gquilt_stock_new_patch' STOCK_PATCH_GUARD = 'gquilt_stock_patch_guard' STOCK_PATCH_GUARD_SELECT = 'gquilt_stock_patch_guard_select' STOCK_REFRESH_PATCH = 'gquilt_stock_refresh_patch' _STOCK_ITEMS_OWN_PNG = [ (APPLIED_NOT_OK, 'Applied (needs refresh)', 0, 0, None), (APPLIED_OK, 'Applied', 0, 0, None), (DIFF, 'Diff', 0, 0, None), (IMPORT_PATCH, 'Import', 0, 0, None), (FINISH, 'Finish', 0, 0, None), (FOLD, 'Fold', 0, 0, None), (MELD, 'Meld', 0, 0, None), (POP, 'Pop', 0, 0, None), (PUSH, 'Push', 0, 0, None), (STOCK_NEW_PATCH, 'New', 0, 0, None), (STOCK_PATCH_GUARD, 'Guard', 0, 0, None), (STOCK_PATCH_GUARD_SELECT, 'Select', 0, 0, None), (STOCK_REFRESH_PATCH, 'Refresh', 0, 0, None), (TOP_OK, 'Top', 0, 0, None), (TOP_NOT_OK, 'Top (needs refresh)', 0, 0, None), ] gtk.stock_add(_STOCK_ITEMS_OWN_PNG) _FACTORY = gtk.IconFactory() _FACTORY.add_default() def _png_file_name(item_name): return os.path.join(_ICON_DIR, item_name[len('gquilt_'):] + os.extsep + 'png') def make_pixbuf(name): return gtk.gdk.pixbuf_new_from_file(_png_file_name(name)) for _item in _STOCK_ITEMS_OWN_PNG: _name = _item[0] _FACTORY.add(_name, gtk.IconSet(make_pixbuf(_name))) StockAlias = collections.namedtuple('StockAlias', ['name', 'alias', 'text']) # Icons that are aliased to Gtk or other stock items STOCK_DIFF = 'gquilt_stock_diff' STOCK_INIT = 'gquilt_stock_init' STOCK_INSERT = 'gwsmhg_stock_insert' STOCK_NEW_PLAYGROUND = 'gquilt_stock_new_playground' STOCK_SYNCH = 'gquilt_stock_synch' _STOCK_ALIAS_LIST = [ StockAlias(name=STOCK_DIFF, alias=DIFF, text='Diff'), StockAlias(name=STOCK_INIT, alias=APPLIED_OK, text='Init'), StockAlias(name=STOCK_INSERT, alias=gtk.STOCK_ADD, text='_Insert'), StockAlias(name=STOCK_NEW_PLAYGROUND, alias=gtk.STOCK_NEW, text='New Playground'), StockAlias(name=STOCK_SYNCH, alias=gtk.STOCK_REFRESH, text='Synchronize'), ] _STYLE = gtk.Frame().get_style() for _item in _STOCK_ALIAS_LIST: _FACTORY.add(_item.name, _STYLE.lookup_icon_set(_item.alias)) gtk.stock_add([(item.name, item.text, 0, 0, None) for item in _STOCK_ALIAS_LIST]) gquilt-0.25/gquilt_pkg/urlops.py0000664000076400007640000000226611453465212017314 0ustar peterpeter00000000000000### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """Provide URL operations in a way compatible with both Python 2 and 3""" _USE_URLPARSE = True try: import urlparse except ImportError: _USE_URLPARSE = False import urllib.parse def parse_url(path, scheme="", allow_fragments=True): """Return ParseResult for the given path""" if _USE_URLPARSE: return urlparse.urlparse(path, scheme, allow_fragments) else: return urllib.parse.urlparse(path, scheme, allow_fragments) gquilt-0.25/gquilt_pkg/fsdb.py0000664000076400007640000001064311476544760016717 0ustar peterpeter00000000000000### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import collections, os Data = collections.namedtuple('Data', ['name', 'status', 'origin']) class NullFileDb: def __init__(self): pass def dir_contents(self, dirpath, show_hidden=False): return ([], []) class OsFileDb: def __init__(self): pass def _is_not_hidden_file(self, filename): return filename[0] != '.' def dir_contents(self, dirpath, show_hidden=False): files = [] dirs = [] if not dirpath: dirpath = os.curdir elements = os.listdir(dirpath) for element in elements: if os.path.isdir(os.path.join(dirpath, element)): if self._is_not_hidden_file(element) or show_hidden: dirs.append(Data(element, None, None)) elif self._is_not_hidden_file(element) or show_hidden: files.append(Data(element, None, None)) dirs.sort() files.sort() return (dirs, files) class GenDir: def __init__(self): self.status = None self.status_set = set() self.subdirs = {} self.files = {} def _new_dir(self): return GenDir() def add_file(self, path_parts, status, origin=None): self.status_set.add(status) name = path_parts[0] if len(path_parts) == 1: self.files[name] = Data(name=name, status=status, origin=origin) else: if name not in self.subdirs: self.subdirs[name] = self._new_dir() self.subdirs[name].add_file(path_parts[1:], status, origin) def _update_own_status(self): if len(self.status_set) > 0: self.status = self.status_set.pop() self.status_set.add(self.status) else: self.status = None def update_status(self): self._update_own_status() for key in list(self.subdirs.keys()): self.subdirs[key].update_status() def _find_dir(self, dirpath_parts): if not dirpath_parts: return self elif dirpath_parts[0] in self.subdirs: return self.subdirs[dirpath_parts[0]]._find_dir(dirpath_parts[1:]) else: return None def find_dir(self, dirpath): if not dirpath: return self return self._find_dir(dirpath.split(os.sep)) def _is_hidden_dir(self, dkey): return dkey[0] == '.' def _is_hidden_file(self, fdata): return fdata.name[0] == '.' def dirs_and_files(self, show_hidden=False): dkeys = list(self.subdirs.keys()) dkeys.sort() dirs = [] for dkey in dkeys: if not show_hidden and self._is_hidden_dir(dkey): continue dirs.append(Data(name=dkey, status=self.subdirs[dkey].status, origin=None)) files = [] fkeys = list(self.files.keys()) fkeys.sort() for fkey in fkeys: fdata = self.files[fkey] if not show_hidden and self._is_hidden_file(fdata): continue files.append(fdata) return (dirs, files) class GenFileDb: def __init__(self, dir_type=GenDir): self.base_dir = dir_type() def _set_contents(self, file_list, unresolved_file_list=list()): for item in file_list: self.base_dir.add_file(item.split(os.sep), status=None, origin=None) def add_file(self, filepath, status, origin=None): self.base_dir.add_file(filepath.split(os.sep), status, origin) def decorate_dirs(self): self.base_dir.update_status() def dir_contents(self, dirpath='', show_hidden=False): tdir = self.base_dir.find_dir(dirpath) if not tdir: return ([], []) return tdir.dirs_and_files(show_hidden) gquilt-0.25/gquilt_pkg/gquilt.py0000664000076400007640000003157611507476422017310 0ustar peterpeter00000000000000#!/usr/bin/env python ### Copyright (C) 2005 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import gtk, os, sys from gquilt_pkg import icons from gquilt_pkg import config, dialogue, ifce, ws_event, diff from gquilt_pkg import console, file_tree, patch_list from gquilt_pkg import actions, gutils from gquilt_pkg import cmd_result _GQUILT_UI_DESCR = \ ''' ''' class GQuilt(gtk.Window, actions.AGandUIManager): def _create_tool_bar(self): tbar = self.patches.ui_manager.get_widget('/patches_toolbar') tbar.set_orientation(gtk.ORIENTATION_HORIZONTAL) tbar.set_style(gtk.TOOLBAR_BOTH) tbcl = gtk.ToolItem() tbcl.set_tooltip(self.tooltips, "Type in a back end command for execution") tbcl.set_expand(True) tbcl.add(self.cmd_line) tbar.insert(tbcl, -1) return tbar def __init__(self, dir_specified=False): open_dialog = None # we need this later if not dir_specified: if ifce.playground_type() is None: open_dialog = config.WSOpenDialog() if open_dialog.run() == gtk.RESPONSE_OK: wspath = open_dialog.get_path() if wspath: open_dialog.show_busy() result = ifce.chdir(wspath) open_dialog.unshow_busy() dialogue.report_any_problems(result) else: sys.exit() open_dialog.show_busy() gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL) dialogue.init(self) actions.AGandUIManager.__init__(self) actions.add_class_indep_actions(actions.ON_PGND_INDEP, [ ("gquilt_playground_actions", None, "_Playground"), ("gquilt_configuration", None, "_Configuration"), ("change_working_directory", gtk.STOCK_OPEN, "_Open", "", "Change current working directory", self._change_wd_acb), ("new_playground", icons.STOCK_NEW_PLAYGROUND, "_New", "", "Create a new intitialized playground", self._new_playground_acb), ("gquilt_config_editors", gtk.STOCK_PREFERENCES, "_Editor Allocation", "", "Allocate editors to file types", self._config_editors_acb), ("gquilt_config_perusers", gtk.STOCK_PREFERENCES, "_Peruser Allocation", "", "Allocate perusers to file types", self._config_perusers_acb), ("gquilt_quit", gtk.STOCK_QUIT, "_Quit", "", "Quit", self._quit), ]) actions.add_class_indep_actions(actions.ON_NOT_IN_PGND, [ ("init_playground", icons.STOCK_INIT, "_Initialise", "", "Initialise the current working directory as a playground", self._init_pgnd_acb), ]) actions.add_class_indep_actions(actions.ON_IN_PGND, [ ("gquilt_patch_actions", None, "_Actions"), ("gquilt_view_actions", None, "_Views"), ("views_update_all", icons.STOCK_SYNCH, "Update All", "", "Update all views. Useful after external actions change workspace/playground state.", self._update_views_acb), ("views_update_playground", icons.STOCK_SYNCH, "Update Playground", "", "Update the working directory file tree display.", self._update_files_view_acb), ("views_update_files", icons.STOCK_SYNCH, "Update Files", "", "Update the top patch file tree display.", self._update_files_view_acb), ("views_update_patches", icons.STOCK_SYNCH, "Update Patches", "", "Update the patch list display.", self._update_patches_view_acb), ("pm_push_all", icons.PUSH, "Push All", "", "Push all unapplied patches", self._push_all_patches_acb), ("pm_import_patch_series", icons.PUSH, "Import Patch Series", "", "Import a series of quilt/mq patches", self._import_patch_series_acb), ]) actions.add_class_indep_actions(actions.ON_IN_PGND_PMIC, [ ("pm_combined_diff", icons.STOCK_DIFF, "Combined Diff", "", "View the combined diff for all currently applied patches", self._combined_diff_acb), ("pm_pop_all", icons.POP, "Pop All", "", "Pop all applied patches", self._pop_all_patches_acb), ]) self.ui_manager.add_ui_from_string(_GQUILT_UI_DESCR) self.set_icon_from_file(icons.APP_ICON_FILE) self.last_import_dir = None self.connect("destroy", self._quit) self.set_title("gquilt: " + os.getcwd()) self.tooltips = gtk.Tooltips() console.LOG.set_size_request(720, 160) self.playground_files = file_tree.WSTree() self.playground_files.set_size_request(240, 320) self.patch_files = file_tree.MutablePatchTree() self.patch_files.set_size_request(240, 320) self.patch_files.get_conditional_action('menu_files').set_label('Files (top patch)') self.patches = patch_list.List() self.patches.set_size_request(240, 320) self.cmd_line = gutils.LabelledEntryWithHistory('quilt ') self.cmd_line.entry.connect("activate", self._exec_typed_cmd) vbox = gtk.VBox() self.add(vbox) hbox = gtk.HBox() hbox.pack_start(self.ui_manager.get_widget("/gquilt_menubar"), expand=False) hbox.pack_end(self.ui_manager.get_widget("/gquilt_right_side_menubar"), expand=False) vbox.pack_start(hbox, expand=False) vbox.pack_start(self._create_tool_bar(), False) vpane = gtk.VPaned() vbox.pack_start(vpane) hpane_outer = gtk.HPaned() hpane_inner = gtk.HPaned() hpane_outer.add1(self.playground_files) hpane_outer.add2(hpane_inner) hpane_inner.add1(self.patch_files) hpane_inner.add2(self.patches) vpane.add1(hpane_outer) vpane.add2(console.LOG) self.show_all() self.show() self._set_playground(os.getcwd()) self.set_focus(self.cmd_line.entry) if open_dialog: open_dialog.unshow_busy() open_dialog.destroy() def _quit(self, _arg): gtk.main_quit() def _add_playground_to_title(self): self.set_title("gquilt: " + os.getcwd()) def show_busy(self): self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) while gtk.events_pending(): gtk.main_iteration() def unshow_busy(self): self.window.set_cursor(None) def _update_patches_view_acb(self, _arg=None): ws_event.notify_events(ws_event.PATCH_CHANGES) def _update_files_view_acb(self, _arg=None): ws_event.notify_events(ws_event.FILE_CHANGES) def _update_views_acb(self, _arg=None): ws_event.notify_events(ws_event.ALL_BUT_CHANGE_WD) def _combined_diff_acb(self, _arg): self.show_busy() dialog = diff.CombinedDiffTextDialog(parent=dialogue.main_window) self.unshow_busy() dialog.show() def _set_playground(self, newpg): result = ifce.chdir(newpg) dialogue.report_any_problems(result) newpg = ifce.ifce.get_playground_root() if ifce.playground_type() is None: msg = os.linesep.join(["Directory %s is not repository." % newpg, "Do you wish to create one there?"]) if dialogue.ask_yes_no(msg, self): is_ok, req_backend = ifce.choose_backend() if is_ok: res, sout, serr = ifce.new_playground(newpg, req_backend) if res != cmd_result.OK: dialogue.inform_user(os.linesep.join([sout, serr])) self.show_busy() self._add_playground_to_title() self.cmd_line.set_label(ifce.ifce.cmd_label) console.LOG.log_entry_bold("playground: " + newpg) self.unshow_busy() def _init_pgnd_acb(self, _arg): is_ok, req_backend = ifce.choose_backend() if not is_ok: return result = ifce.new_playground(os.getcwd(), req_backend) dialogue.report_any_problems(result) self._set_playground(os.getcwd()) def _new_playground_acb(self, _arg): is_ok, req_backend = ifce.choose_backend() if not is_ok: return newpg = dialogue.ask_dir_name("Select/create playground ..") if newpg is not None: res, sout, serr = ifce.new_playground(newpg, req_backend) if res != cmd_result.OK: dialogue.inform_user(os.linesep.join([sout, serr])) return self._set_playground(newpg) def _change_wd_acb(self, _arg): open_dialog = config.WSOpenDialog(parent=self) if open_dialog.run() == gtk.RESPONSE_OK: newpg = open_dialog.get_path() if newpg: self._set_playground(newpg) open_dialog.destroy() def _config_editors_acb(self, _arg): config.EditorAllocationDialog(parent=self).show() def _config_perusers_acb(self, _arg): config.PeruserAllocationDialog(parent=self).show() def _import_patch_series_acb(self, _arg=None): dirname = dialogue.ask_dir_name("Select directory to import patch series from ..", suggestion=self.last_import_dir) if dirname is not None: self.last_import_dir = dirname sfname = os.path.join(dirname, "series") if not os.path.exists(sfname): dialogue.inform_user("Series file not found") return fobj = open(sfname, 'r') series = fobj.readlines() series.reverse() fobj.close() for name_raw in series: name = name_raw.strip() if name == "" or name[0] == "#": continue pfname = os.path.join(dirname, name) self.show_busy() res, sout, serr = ifce.ifce.do_import_patch(pfname) self.unshow_busy() if res == cmd_result.ERROR_SUGGEST_FORCE: ans = dialogue.ask_force_skip_or_cancel((res, sout, serr), parent=gutils.get_gtk_window(self)) if ans == dialogue.RESPONSE_FORCE: res, sout, serr = ifce.ifce.do_import_patch(pfname, force=True) elif ans == dialogue.RESPONSE_SKIP: continue else: return if res != cmd_result.OK: dialogue.inform_user(os.linesep.join([sout, serr])) def _pop_all_patches_acb(self, arg): self.patches.do_pop_all(arg) def _push_all_patches_acb(self, arg): self.patches.do_push_all(arg) def _exec_typed_cmd(self, entry): self.show_busy() text = entry.get_text_and_clear_to_history() if text: res, sout, serr = ifce.ifce.do_exec_tool_cmd(text) else: res = cmd_result.OK self.unshow_busy() if res != cmd_result.OK: dialogue.inform_user(os.linesep.join([sout, serr])) self._update_views() gquilt-0.25/gquilt_pkg/file_tree.py0000664000076400007640000011046611516472634017736 0ustar peterpeter00000000000000### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import gtk, gobject, collections, os from gquilt_pkg import tlview, dialogue, actions, gutils, ifce, utils from gquilt_pkg import console, cmd_result, ws_event, icons, diff from gquilt_pkg import text_edit Row = collections.namedtuple('Row', ['name', 'is_dir', 'style', 'foreground', 'icon', 'status', 'origin']) _MODEL_TEMPLATE = Row( name=gobject.TYPE_STRING, is_dir=gobject.TYPE_BOOLEAN, style=gobject.TYPE_INT, foreground=gobject.TYPE_STRING, icon=gobject.TYPE_STRING, status=gobject.TYPE_STRING, origin=gobject.TYPE_STRING ) _NAME = tlview.model_col(_MODEL_TEMPLATE, 'name') _IS_DIR = tlview.model_col(_MODEL_TEMPLATE, 'is_dir') _STYLE = tlview.model_col(_MODEL_TEMPLATE, 'style') _FOREGROUND = tlview.model_col(_MODEL_TEMPLATE, 'foreground') _ICON = tlview.model_col(_MODEL_TEMPLATE, 'icon') _STATUS = tlview.model_col(_MODEL_TEMPLATE, 'status') _ORIGIN = tlview.model_col(_MODEL_TEMPLATE, 'origin') _FILE_ICON = {True : gtk.STOCK_DIRECTORY, False : gtk.STOCK_FILE} def _get_status_deco(status=None): try: return ifce.ifce.status_deco_map[status] except KeyError: return ifce.ifce.status_deco_map[None] def _generate_row_tuple(data, isdir=None): deco = _get_status_deco(data.status) row = Row( name=data.name, is_dir=isdir, icon=_FILE_ICON[isdir], status=data.status, origin=data.origin, style=deco.style, foreground=deco.foreground ) return row class Store(tlview.TreeStore): def __init__(self, show_hidden=False, populate_all=False, auto_expand=False): tlview.TreeStore.__init__(self, _MODEL_TEMPLATE) self._file_db = None # If 'view' this isn't set explicitly it will be set automatically # when any row is expanded self.view = None self._populate_all = populate_all self._auto_expand = auto_expand self.show_hidden_action = gtk.ToggleAction('show_hidden_files', 'Show Hidden Files', 'Show/hide ignored files and those beginning with "."', None) self.show_hidden_action.set_active(show_hidden) self.show_hidden_action.connect('toggled', self._toggle_show_hidden_cb) self.show_hidden_action.set_menu_item_type(gtk.CheckMenuItem) self.show_hidden_action.set_tool_item_type(gtk.ToggleToolButton) def set_view(self, view): self.view = view if self._expand_new_rows(): self.view.expand_all() def _expand_new_rows(self): return self._auto_expand and self.view is not None def _row_expanded(self, dir_iter): # if view isn't set then assume that we aren't connexted to a view # so the row can't be expanded if self.view is None: return False else: return self.view.row_expanded(self.get_path(dir_iter)) def _update_iter_row_tuple(self, fsobj_iter, to_tuple): for index in [_STYLE, _FOREGROUND, _STATUS, _ORIGIN]: self.set_value(fsobj_iter, index, to_tuple[index]) def _toggle_show_hidden_cb(self, toggleaction): self._update_dir('', None) def fs_path(self, fsobj_iter): if fsobj_iter is None: return None parent_iter = self.iter_parent(fsobj_iter) name = self.get_value(fsobj_iter, _NAME) if parent_iter is None: return name else: if name is None: return os.path.join(self.fs_path(parent_iter), '') return os.path.join(self.fs_path(parent_iter), name) def _get_file_paths(self, fsobj_iter, path_list): while fsobj_iter != None: if not self.get_value(fsobj_iter, _IS_DIR): path_list.append(self.fs_path(fsobj_iter)) else: child_iter = self.iter_children(fsobj_iter) if child_iter != None: self._get_file_paths(child_iter, path_list) fsobj_iter = self.iter_next(fsobj_iter) def get_file_paths(self): path_list = [] self._get_file_paths(self.get_iter_first(), path_list) return path_list def _recursive_remove(self, fsobj_iter): child_iter = self.iter_children(fsobj_iter) if child_iter != None: while self._recursive_remove(child_iter): pass return self.remove(fsobj_iter) def _remove_place_holder(self, dir_iter): child_iter = self.iter_children(dir_iter) if child_iter and self.get_value(child_iter, _NAME) is None: self.remove(child_iter) def _insert_place_holder(self, dir_iter): self.append(dir_iter) def _insert_place_holder_if_needed(self, dir_iter): if self.iter_n_children(dir_iter) == 0: self._insert_place_holder(dir_iter) def _populate(self, dirpath, parent_iter): dirs, files = self._file_db.dir_contents(dirpath, self.show_hidden_action.get_active()) for dirdata in dirs: row_tuple = _generate_row_tuple(dirdata, True) dir_iter = self.append(parent_iter, row_tuple) if self._populate_all: self._populate(os.path.join(dirpath, dirdata.name), dir_iter) if self._expand_new_rows(): self.view.expand_row(self.get_path(dir_iter), True) else: self._insert_place_holder(dir_iter) for filedata in files: row_tuple = _generate_row_tuple(filedata, False) dummy = self.append(parent_iter, row_tuple) if parent_iter is not None: self._insert_place_holder_if_needed(parent_iter) def _update_dir(self, dirpath, parent_iter=None): if parent_iter is None: child_iter = self.get_iter_first() else: child_iter = self.iter_children(parent_iter) if child_iter: if self.get_value(child_iter, _NAME) is None: child_iter = self.iter_next(child_iter) dirs, files = self._file_db.dir_contents(dirpath, self.show_hidden_action.get_active()) dead_entries = [] for dirdata in dirs: row_tuple = _generate_row_tuple(dirdata, True) while (child_iter is not None) and self.get_value(child_iter, _IS_DIR) and (self.get_value(child_iter, _NAME) < dirdata.name): dead_entries.append(child_iter) child_iter = self.iter_next(child_iter) if child_iter is None: dir_iter = self.append(parent_iter, row_tuple) if self._populate_all: self._update_dir(os.path.join(dirpath, dirdata.name), dir_iter) if self._expand_new_rows(): self.view.expand_row(self.get_path(dir_iter), True) else: self._insert_place_holder(dir_iter) continue name = self.get_value(child_iter, _NAME) if (not self.get_value(child_iter, _IS_DIR)) or (name > dirdata.name): dir_iter = self.insert_before(parent_iter, child_iter, row_tuple) if self._populate_all: self._update_dir(os.path.join(dirpath, dirdata.name), dir_iter) if self._expand_new_rows(): self.view.expand_row(self.get_path(dir_iter), True) else: self._insert_place_holder(dir_iter) continue self._update_iter_row_tuple(child_iter, row_tuple) if self._populate_all or self._row_expanded(child_iter): self._update_dir(os.path.join(dirpath, name), child_iter) child_iter = self.iter_next(child_iter) while (child_iter is not None) and self.get_value(child_iter, _IS_DIR): dead_entries.append(child_iter) child_iter = self.iter_next(child_iter) for filedata in files: row_tuple = _generate_row_tuple(filedata, False) while (child_iter is not None) and (self.get_value(child_iter, _NAME) < filedata.name): dead_entries.append(child_iter) child_iter = self.iter_next(child_iter) if child_iter is None: dummy = self.append(parent_iter, row_tuple) continue if self.get_value(child_iter, _NAME) > filedata.name: dummy = self.insert_before(parent_iter, child_iter, row_tuple) continue self._update_iter_row_tuple(child_iter, row_tuple) child_iter = self.iter_next(child_iter) while child_iter is not None: dead_entries.append(child_iter) child_iter = self.iter_next(child_iter) for dead_entry in dead_entries: self._recursive_remove(dead_entry) if parent_iter is not None: self._insert_place_holder_if_needed(parent_iter) def _get_file_db(self): assert 0, '_get_file_db() must be defined in descendants' def repopulate(self): self._file_db = self._get_file_db() self.clear() self._populate('', self.get_iter_first()) def update(self): self._file_db = self._get_file_db() self._update_dir('', None) def on_row_expanded_cb(self, view, dir_iter, dummy): self.view = view if not self._populate_all: self._update_dir(self.fs_path(dir_iter), dir_iter) if self.iter_n_children(dir_iter) > 1: self._remove_place_holder(dir_iter) def on_row_collapsed_cb(self, view, dir_iter, dummy): self._insert_place_holder_if_needed(dir_iter) def _format_file_name_crcb(_column, cell_renderer, store, tree_iter, _arg=None): name = store.get_value(tree_iter, _NAME) xinfo = store.get_value(tree_iter, _ORIGIN) if xinfo: name += ' <- %s' % xinfo cell_renderer.set_property('text', name) _VIEW_TEMPLATE = tlview.ViewTemplate( properties={'headers-visible' : False}, selection_mode=gtk.SELECTION_MULTIPLE, columns=[ tlview.Column( title='File Name', properties={}, cells=[ tlview.Cell( creator=tlview.CellCreator( function=gtk.CellRendererPixbuf, expand=False, start=True ), properties={}, renderer=None, attributes={'stock-id' : _ICON} ), tlview.Cell( creator=tlview.CellCreator( function=gtk.CellRendererText, expand=False, start=True ), properties={}, renderer=None, attributes={'text' : _STATUS, 'style' : _STYLE, 'foreground' : _FOREGROUND} ), tlview.Cell( creator=tlview.CellCreator( function=gtk.CellRendererText, expand=False, start=True ), properties={}, renderer=tlview.Renderer(function=_format_file_name_crcb, user_data=None), attributes={'style' : _STYLE, 'foreground' : _FOREGROUND} ) ] ) ] ) UI_DESCR = \ ''' ''' _KEYVAL_c = gtk.gdk.keyval_from_name('c') _KEYVAL_C = gtk.gdk.keyval_from_name('C') _KEYVAL_ESCAPE = gtk.gdk.keyval_from_name('Escape') class Tree(gtk.VBox, dialogue.BusyIndicatorUser, actions.AGandUIManager, ws_event.Listener): def __init__(self, model, busy_indicator=None, auto_refresh=False, show_hidden=False): gtk.VBox.__init__(self) if model: self.model = model self.model.show_hidden_action.set_active(show_hidden) else: self.model = Store(show_hidden=show_hidden) self.view = tlview.View(_VIEW_TEMPLATE, self.model) self.model.set_view(self.view) self.seln = self.view.get_selection() dialogue.BusyIndicatorUser.__init__(self, busy_indicator) actions.AGandUIManager.__init__(self, self.seln) ws_event.Listener.__init__(self) self._refresh_interval = 60000 # milliseconds self.auto_refresh_action = gtk.ToggleAction('auto_refresh_files', 'Auto Refresh', 'Automatically/periodically refresh file display', None) self.auto_refresh_action.set_active(auto_refresh) self.auto_refresh_action.connect('toggled', self._toggle_auto_refresh_cb) self.auto_refresh_action.set_menu_item_type(gtk.CheckMenuItem) self.auto_refresh_action.set_tool_item_type(gtk.ToggleToolButton) self.add_conditional_action(actions.ON_PGND_INDEP_SELN_INDEP, self.auto_refresh_action) self.add_conditional_actions(actions.ON_PGND_INDEP_SELN_INDEP, [ ('menu_files', None, '_Files'), ('refresh_files', gtk.STOCK_REFRESH, '_Refresh', None, 'Refresh/update the file tree display', self._update_tree_cb), ] ) self.add_conditional_action(actions.ON_PGND_INDEP_SELN_INDEP, self.model.show_hidden_action) self.ui_manager.add_ui_from_string(UI_DESCR) hbox = gtk.HBox() hbox.pack_start(self.ui_manager.get_widget('/files_left_menubar')) hbox.pack_end(self.ui_manager.get_widget('/files_right_menubar')) self.pack_start(hbox, False, False) self.pack_start(gutils.wrap_in_scrolled_window(self.view)) self.view.connect('row-expanded', self.model.on_row_expanded_cb) self.view.connect('row-collapsed', self.model.on_row_collapsed_cb) self.view.connect('button_press_event', self._handle_button_press_cb) self.view.connect('key_press_event', self._key_press_cb) self.show_all() self.model.repopulate() def _do_auto_refresh(self): if self.auto_refresh_action.get_active(): self.model.update() return True else: return False def _toggle_auto_refresh_cb(self, action=None): if self.auto_refresh_action.get_active(): gobject.timeout_add(self._refresh_interval, self._do_auto_refresh) def _update_action_visibility(self): pass def _handle_button_press_cb(self, widget, event): if event.type == gtk.gdk.BUTTON_PRESS: if event.button == 3: self._update_action_visibility() menu = self.ui_manager.get_widget('/files_popup') menu.popup(None, None, None, event.button, event.time) return True elif event.button == 2: self.get_selection().unselect_all() return True elif event.type == gtk.gdk._2BUTTON_PRESS: if event.button == 1: self._handle_double_click() return True return False def _handle_double_click(self): pass def _key_press_cb(self, widget, event): if gtk.gdk.CONTROL_MASK & event.state: if event.keyval in [_KEYVAL_c, _KEYVAL_C]: self.add_selected_files_to_clipboard() return True elif event.keyval == _KEYVAL_ESCAPE: self.seln.unselect_all() return True def get_selected_files(self): store, selection = self.seln.get_selected_rows() return [store.fs_path(store.get_iter(x)) for x in selection] def add_selected_files_to_clipboard(self, clipboard=None): if not clipboard: clipboard = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD) sel = utils.file_list_to_string(self.get_selected_files()) clipboard.set_text(sel) return False def _update_tree_cb(self, _arg=None): self.show_busy() self.model.update() self.unshow_busy() def _repopulate_tree_cb(self, _arg=None): self.show_busy() self.model.repopulate() self.unshow_busy() WS_FILES_UI_DESCR = \ ''' ''' class WSStore(Store): def __init__(self): Store.__init__(self) def _get_file_db(self): return ifce.ifce.get_ws_file_db() class WSTree(Tree): def __init__(self): Tree.__init__(self, WSStore()) hbox = gtk.HBox() button = gtk.CheckButton() action = self.get_conditional_action('show_hidden_files') action.connect_proxy(button) gutils.set_widget_tooltip_text(button, action.get_property('tooltip')) hbox.show_all() hbox.pack_start(button) self.pack_start(hbox, expand=False) self.add_conditional_actions(actions.ON_IN_PGND_PMIC_SELN_INDEP, [ ('new_file', gtk.STOCK_NEW, '_New', None, 'Add a new file to the top patch', self._add_new_file_to_top_patch), ] ) self.add_conditional_actions(actions.ON_IN_PGND_PMIC_SELN, [ ('add_files', gtk.STOCK_ADD, '_Add', None, 'Add the selected files to the top patch', self._add_selection_to_top_patch), ('copy_files', gtk.STOCK_COPY, '_Copy', None, 'Make copies of the selected files in the top patch', self._copy_selection_to_top_patch), ('move_files', gtk.STOCK_DND, '_Move/Rename', None, 'Move/rename the selected files in the top patch', self._move_selection_to_top_patch), ('edit_files', gtk.STOCK_EDIT, '_Edit', None, 'Edit the selected files after adding them to the top patch', self._edit_selection_in_top_patch), ] ) self.add_conditional_actions(actions.ON_PGND_INDEP_SELN, [ ('peruse_files', gtk.STOCK_FILE, '_Peruse', None, 'Peruse the selected files', self._peruse_selection), ] ) self.ui_manager.add_ui_from_string(WS_FILES_UI_DESCR) self.add_notification_cb(ws_event.CHANGE_WD, self._repopulate_tree_cb) self.add_notification_cb(ws_event.FILE_CHANGES|ws_event.PATCH_REFRESH|ws_event.PATCH_POP|ws_event.PATCH_PUSH, self._update_tree_cb) def _handle_double_click(self): self._edit_selection_in_top_patch() def _update_action_visibility(self): self.get_conditional_action('add_files').set_visible(ifce.ifce.has_add_files()) def _add_new_file_to_top_patch(self, _action=None): _add_new_file_to_patch(self) def _add_selection_to_top_patch(self, _action=None): files = self.get_selected_files() if len(files) == 0: return filelist = [] dirs = "" for f in files: if os.path.isdir(f): dirs += os.linesep + f else: filelist.append(f) if dirs != "": emsg = os.linesep.join(["The directories:", dirs, "", "have been removed from addition"]) if not dialogue.ask_ok_cancel(emsg, parent=gutils.get_gtk_window(self)): return if len(filelist) > 0: self.show_busy() res, sout, serr = ifce.ifce.do_add_files_to_patch(filelist) self.unshow_busy() if res != cmd_result.OK: dialogue.inform_user(os.linesep.join([sout, serr])) def _copy_selection_to_top_patch(self, _action=None): _copy_files_to_top_patch(self, self.get_selected_files(), move=False) def _move_selection_to_top_patch(self, _action=None): _copy_files_to_top_patch(self, self.get_selected_files(), move=True) def _edit_selection_in_top_patch(self, _action=None): files = self.get_selected_files() if len(files) == 0: return filelist = [] dirs = "" for f in files: if os.path.isdir(f): dirs += os.linesep + f else: filelist.append(f) if dirs != "": emsg = os.linesep.join(["The directories:", dirs, "", "have been removed from selection"]) if not dialogue.ask_ok_cancel(emsg, parent=gutils.get_gtk_window(self)): return if len(filelist) > 0: res, sout, serr = ifce.ifce.get_patch_files(None, False) if res != cmd_result.OK: dialogue.inform_user(os.linesep.join([sout, serr])) return added_files = [] for f in filelist: if not f in sout: added_files.append(f) if len(added_files) > 0: res, sout, serr = ifce.ifce.do_add_files_to_patch(added_files) if res is not cmd_result.OK: dialogue.inform_user(os.linesep.join([sout, serr])) text_edit.edit_files_extern(filelist) def _peruse_selection(self, _action=None): files = self.get_selected_files() if len(files) == 0: return text_edit.peruse_files_extern(files) def _add_new_file_to_patch(caller): name = dialogue.ask_file_name('New file ...', existing=False) if name is None: return if os.path.isdir(name): dialogue.inform_user("Cannot add directories to patch", parent=gutils.get_gtk_window(caller)) return lname = utils.path_relative_to_playground(name) if lname == None: dialogue.inform_user("File is outside playground", parent=gutils.get_gtk_window(caller)) return if not os.path.exists(lname): created = True if console.LOG: console.LOG.exec_cmd("touch " + lname) else: utils.run_cmd("touch " + lname) else: created = False caller.show_busy() res, sout, serr = ifce.ifce.do_add_files_to_patch([lname]) caller.unshow_busy() if res is not cmd_result.OK: if created: try: os.remove(lname) except OSError: pass dialogue.inform_user(os.linesep.join([sout, serr]), parent=gutils.get_gtk_window(caller)) def _copy_files_to_top_patch(caller, files, move=False): if len(files) == 0: return elif len(files) == 1: dest = dialogue.ask_file_name("Destination", existing=False, suggestion=files[0]) else: dest = dialogue.ask_dir_name("Destination") if dest is None: return ldest = utils.path_relative_to_playground(dest) if ldest == None: dialogue.inform_user("Destination is outside playground", parent=gutils.get_gtk_window(caller)) return for fname in files: caller.show_busy() if move: res, sout, serr = ifce.ifce.do_move_file(fname, ldest) else: res, sout, serr = ifce.ifce.do_copy_file(fname, ldest) caller.unshow_busy() if res & cmd_result.SUGGEST_FORCE: if dialogue.ask_force_or_cancel((res, sout, serr), parent=gutils.get_gtk_window(caller)) == dialogue.RESPONSE_FORCE: caller.show_busy() res, sout, serr = ifce.ifce.do_copy_file(fname, ldest, True) caller.unshow_busy() elif res != cmd_result.OK: if not dialogue.ask_ok_cancel(os.linesep.join([sout, serr]), parent=gutils.get_gtk_window(caller)): break PATCH_FILES_UI_DESCR = \ ''' ''' class PatchStore(Store): def __init__(self, patch=None): Store.__init__(self, show_hidden=True, populate_all=True, auto_expand=True) self.patch = patch def _get_file_db(self): return ifce.ifce.get_patch_file_db(patch=self.patch) class PatchTree(Tree): def __init__(self, patch=None, busy_indicator=None): Tree.__init__(self, PatchStore(patch=patch), show_hidden=True, busy_indicator=busy_indicator) self.add_conditional_actions(actions.ON_IN_PGND_PMIC_SELN, [ ('diff_files', icons.DIFF, '_Diff', None, 'Display diff for selected files', self._display_diff), ('ediff_files', icons.MELD, '_Meld', None, 'Display diff for selected files using "meld"', self._display_diff_with_meld), ] ) self.ui_manager.add_ui_from_string(PATCH_FILES_UI_DESCR) self.add_notification_cb(ws_event.CHANGE_WD|ws_event.PATCH_POP|ws_event.PATCH_PUSH, self._repopulate_tree_cb) self.add_notification_cb(ws_event.FILE_CHANGES|ws_event.PATCH_REFRESH, self._update_tree_cb) def _update_action_visibility(self): self.get_conditional_action('ediff_files').set_visible(utils.which("meld") is not None) def _display_diff(self, _arg): files = self.get_selected_files() _parent_window = gutils.get_gtk_window(self) dialog = diff.PmDiffTextDialog(parent=_parent_window, patch=self.model.patch, file_list=files) dialog.show() def _display_diff_with_meld(self, _arg): files = self.get_selected_files() ifce.ifce.display_files_diff_in_viewer("meld", files, self.model.patch) class PatchFilesDialog(dialogue.AmodalDialog): def __init__(self, patch): dialogue.AmodalDialog.__init__(self, None, None, gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)) self.set_title('patch: %s files: %s' % (patch, utils.cwd_rel_home())) # file tree view wrapped in scrolled window self.file_tree = PatchTree(busy_indicator=self, patch=patch) self.file_tree.seln.set_mode(gtk.SELECTION_MULTIPLE) self.file_tree.view.set_headers_visible(False) self.file_tree.view.set_size_request(240, 320) self.vbox.pack_start(self.file_tree) self.connect("response", self._close_cb) self.show_all() def _close_cb(self, dialog, response_id): self.destroy() MUTABLE_PATCH_FILES_UI_DESCR = \ ''' ''' class MutablePatchTree(PatchTree): def __init__(self, patch=None): PatchTree.__init__(self, patch=patch) self.patchname = patch self.add_conditional_actions(actions.ON_IN_PGND_PMIC_SELN_INDEP, [ ('new_file', gtk.STOCK_NEW, '_New', None, 'Add a new file to the patch', self._add_new_file_to_patch), ] ) self.add_conditional_actions(actions.ON_IN_PGND_PMIC_SELN, [ ('remove_files', gtk.STOCK_REMOVE, '_Remove', None, 'Remove the selected files from the top patch', self._remove_selection_from_patch), ('copy_files', gtk.STOCK_COPY, '_Copy', None, 'Make copies of the selected files in the patch', self._copy_selection), ('move_files', gtk.STOCK_DND, '_Move/Rename', None, 'Move/rename the selected files in the patch', self._move_selection), ('edit_files', gtk.STOCK_EDIT, '_Edit', None, 'Edit the selected files', self._edit_selected_files), ('resolve_files', gtk.STOCK_EDIT, 'Re_solve', None, 'Resolve problems with selected files in the top patch', self._resolve_selected_files), ('revert_files', gtk.STOCK_UNDO, 'Re_vert', None, 'Revert the selected files to their state before the last refresh', self._revert_selection_in_patch), ('delete_files', gtk.STOCK_DELETE, '_Delete', None, 'Delete the selected files', self._delete_selection), ] ) self.ui_manager.add_ui_from_string(MUTABLE_PATCH_FILES_UI_DESCR) def _update_action_visibility(self): self.get_conditional_action('ediff_files').set_visible(utils.which("meld") is not None) def _display_diff(self, _arg): files = self.get_selected_files() _parent_window = gutils.get_gtk_window(self) dialog = diff.PmDiffTextDialog(parent=_parent_window, patch=self.model.patch, file_list=files) dialog.show() def _display_diff_with_meld(self, _arg): files = self.get_selected_files() ifce.ifce.display_files_diff_in_viewer("meld", files, self.model.patch) def _add_new_file_to_patch(self, _action=None): _add_new_file_to_patch(self) def _remove_selection_from_patch(self, _arg): files = self.get_selected_files() if len(files) == 0: return elif len(files) == 1: emsg = os.linesep.join([files[0], "", "Confirm remove selected file from patch?"]) else: emsg = os.linesep.join(files + ["", "Confirm remove selected files from patch?"]) if not dialogue.ask_ok_cancel(emsg, parent=gutils.get_gtk_window(self)): return self.show_busy() res, sout, serr = ifce.ifce.do_remove_files_from_patch(files, self.patchname) self.unshow_busy() if res is not cmd_result.OK: dialogue.inform_user(os.linesep.join([sout, serr])) def _copy_selection(self, _arg): _copy_files_to_top_patch(self, self.get_selected_files(), move=False) def _move_selection(self, _arg): _copy_files_to_top_patch(self, self.get_selected_files(), move=True) def _edit_selected_files(self, _arg=None): files = self.get_selected_files() text_edit.edit_files_extern(files) def _resolve_selected_files(self, _arg=None): files = self.get_selected_files() if len(files) == 0: res, files, serr = ifce.ifce.get_patch_files(None, False) if res != cmd_result.OK: dialogue.inform_user(serr) return files_plus_rej = [] for f in files: files_plus_rej.append(f) rejf = f + os.extsep + "rej" if os.path.exists(rejf): files_plus_rej.append(rejf) text_edit.edit_files_extern(files_plus_rej) def _revert_selection_in_patch(self, _arg): files = self.get_selected_files() if len(files) == 0: return elif len(files) == 1: emsg = os.linesep.join([files[0], "", "Confirm revert changes to selected file?"]) else: emsg = os.linesep.join(files + ["", "Confirm revert changes to selected files?"]) if not dialogue.ask_ok_cancel(emsg, parent=gutils.get_gtk_window(self)): return self.show_busy() res, sout, serr = ifce.ifce.do_revert_files_in_patch(files, self.patchname) self.unshow_busy() if res is not cmd_result.OK: dialogue.inform_user(os.linesep.join([sout, serr])) def _delete_selection(self, _arg): files = self.get_selected_files() if len(files) == 0: return elif len(files) == 1: emsg = os.linesep.join([files[0], "", "Confirm delete selected file?"]) else: emsg = os.linesep.join(files + ["", "Confirm delete selected files?"]) if not dialogue.ask_ok_cancel(emsg, parent=gutils.get_gtk_window(self)): return for fname in files: try: os.remove(fname) except OSError as edata: _errno, error_message = edata.args emsg = os.linesep.join([fname + ": " + error_message, "Continue?"]) if dialogue.ask_ok_cancel(emsg, parent=gutils.get_gtk_window(self)): continue ws_event.notify_events(ws_event.FILE_DEL) gquilt-0.25/gquilt_pkg/const.py0000664000076400007640000000167011507025131017104 0ustar peterpeter00000000000000# -*- python -*- ### Copyright (C) 2005 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA '''Some constants to be used within gquilt''' NOT_APPLIED = " " APPLIED = "+" APPLIED_NEEDS_REFRESH = "?" TOP_PATCH = "=" TOP_PATCH_NEEDS_REFRESH = "!" EXTANT = " " ADDED = "+" DELETED = "-" gquilt-0.25/gquilt_pkg/gquilt_tool.py0000664000076400007640000003221011516160233020315 0ustar peterpeter00000000000000# -*- python -*- ### Copyright (C) 2005 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # This file describes the interface that must be implemented for each # underlying tool in order for gquilt to provide an interface to for the tool import os, os.path, shutil, pango, collections from gquilt_pkg import console, fsdb from gquilt_pkg import cmd_result from gquilt_pkg import putils from gquilt_pkg import ws_event DEFAULT_NAME_EVARS = ["GIT_AUTHOR_NAME", "GECOS"] DEFAULT_EMAIL_VARS = ["GIT_AUTHOR_EMAIL", "EMAIL_ADDRESS"] Deco = collections.namedtuple('Deco', ['style', 'foreground']) PatchData = collections.namedtuple('PatchData', ['name', 'state', 'guards']) class Interface: def __init__(self, name, cmd=None): self._name_envars = DEFAULT_NAME_EVARS self._email_envars = DEFAULT_EMAIL_VARS self.name = name self.cmd_label = cmd if cmd else '%s ' % name self.status_deco_map = { None: Deco(pango.STYLE_NORMAL, "black"), } def _map_cmd_result(self, result, ignore_err_re=None): assert False, "Must be defined in child" def _run_cmd_on_console(self, cmd, input_text=None, ignore_err_re=None): result = console.exec_console_cmd(cmd) return self._map_cmd_result(result, ignore_err_re=ignore_err_re) def count_ok_meld(self, count): # override in back end if there are restrictions on its use of meld return True def display_files_diff_in_viewer(self, viewer, file_name, patch=None): assert False, 'display_file_diff_in_viewer() must be provided in child class!!!' def do_add_files_to_patch(self, filelist): assert False, 'do_add_files_to_patch() must be provided in child class!!!' def do_copy_file(self, file_path, dest, force=False): # back end independent implementation needs to be overridden by a # back end if the back end includes the concept of copy if os.path.isdir(dest): dest = os.path.join(dest, os.path.basename(file_path)) if not force and os.path.exists(dest): return cmd_result.Result(cmd_result.ERROR_SUGGEST_FORCE, "", "File \"%s\" already exists. Select \"force\" to overwrite." % dest) res, patch_files, sout = self.get_patch_files(None, False) if not dest in patch_files: res, sout, serr = self.do_add_files_to_patch([dest]) if res is not cmd_result.OK: return cmd_result.Result(res, sout, serr) try: shutil.copy(file_path, dest) result = (cmd_result.OK, "", "") except (IOError, os.error, shutil.Error) as why: serr = "Copy %s to %s failed. %s." % (file_path, dest, str(why)) console.LOG.append_stderr(serr) result = (cmd_result.ERROR, "", serr) ws_event.notify_events(ws_event.FILE_ADD|ws_event.FILE_DEL) return result def do_delete_patch(self, patch): assert False, 'do_delete_patch() must be provided in child class!!!' def do_exec_tool_cmd(self, cmd): assert False, 'do_exec_tool_cmd() must be provided in child class!!!' def do_finish_patch(self, patch): assert False, 'do_merge_patch() must be provided in child class!!!' def do_fold_patch(self, patch): assert False, 'do_fold_patch() must be provided in child class!!!' def do_fold_patch_file(self, filename): assert False, 'do_fold_patch_file() must be provided in child class!!!' def do_import_patch(self, filename, patchname=None, force=False): assert False, 'do_import_patch() must be provided in child class!!!' def do_move_file(self, file_path, dest, force=False): # back end independent implementation needs to be overridden by a # back end if the back end includes the concept of move if os.path.isdir(dest): dest = os.path.join(dest, os.path.basename(file_path)) if not force and os.path.exists(dest): return cmd_result.Result(cmd_result.ERROR_SUGGEST_FORCE, "", "File \"%s\" already exists. Select \"force\" to overwrite." % dest) res, patch_files, sout = self.get_patch_files(None, False) if file_path not in patch_files: res, sout, serr = self.do_add_files_to_patch([file_path]) if res is not cmd_result.OK: return cmd_result.Result(res, sout, serr) if dest not in patch_files: res, sout, serr = self.do_add_files_to_patch([dest]) if res is not cmd_result.OK: return cmd_result.Result(res, sout, serr) try: os.rename(file_path, dest) result = (cmd_result.OK, "", "") except (IOError, os.error, shutil.Error) as why: serr = "Copy %s to %s failed. %s." % (file_path, dest, str(why)) console.LOG.append_stderr(serr) result = (cmd_result.ERROR, "", serr) ws_event.notify_events(ws_event.FILE_ADD|ws_event.FILE_DEL) return result def do_new_patch(self, name, force=False): assert False, 'do_new_patch() must be provided in child class!!!' def do_pop_to(self, patch=None): assert False, 'do_pop_to() must be provided in child class!!!' def do_push_to(self, patch=None, force=False, merge=False): assert False, 'do_push_to() must be provided in child class!!!' def do_refresh(self, patch=None, force=False, notify=True): assert False, 'do_refresh() must be provided in child class!!!' def do_remove_files_from_patch(self, filelist, patch=None): assert False, 'do_remove_files_from_patch() must be provided in child class!!!' def do_rename_patch(self, patch, newname): assert False, 'do_rename_patch() must be provided in child class!!!' def do_revert_files_in_patch(self, filelist, patch=None): assert False, 'do_revert_files_in_patch() must be provided in child class!!!' def do_select_guards(self, guards): assert False, 'do_select_guards() must be provided in child class!!!' def do_set_patch_guards(self, patch_name, guards): assert False, 'do_set_patch_guards() must be provided in child class!!!' def do_set_patch_description(self, patch, description): # (almost) back end independent implementation needs to be overridden # by a back end if desired pfn = self.get_patch_file_name(patch) res = putils.set_patch_descr_lines(pfn, description.splitlines()) if res: res = cmd_result.OK serr = "" else: res = cmd_result.ERROR serr = "Error reading patch description\n" if console.LOG is not None: console.LOG.log_entry('set description for "' + patch + '"') return cmd_result.Result(res, "", serr) def extdiff_and_full_patch_ok(self): return False def get_all_patches_data(self): assert False, 'get_all_patches_data() must be provided in child class!!!' def _get_first_in_envar(self, envar_list): for envar in envar_list: try: value = os.environ[envar] if value is not "": return value except KeyError: continue return "" def get_applied_patches(self): return [] def get_author_name_and_email(self): name = self._get_first_in_envar(self._name_envars) if not name: name = "UNKNOWN" email = self._get_first_in_envar(self._email_envars) if not email: email = "UNKNOWN" return "%s <%s>" % (name, email) def get_combined_diff(self, start_patch=None, end_patch=None): assert False, 'get_combined_diff() must be provided in child class!!!' def get_description_is_finish_ready(self, patch): # make sure we don't get into an infinite loop if this isn't defined # in the derived back end return True def get_diff(self, filelist=list(), patch=None): assert False, 'get_diff() must be provided in child class!!!' def get_in_progress(self): assert False, 'get_in_progress() must be provided in child class!!!' def get_next_patch(self): assert False, 'get_next_patch() must be provided in child class!!!' def get_patch_description(self, patch): # (almost) back end independent implementation needs to be overridden # by a back end if desired pfn = self.get_patch_file_name(patch) if os.path.exists(pfn): res, lines = putils.get_patch_descr_lines(pfn) if res: return cmd_result.Result(cmd_result.OK, os.linesep.join(lines) + os.linesep, "") else: return cmd_result.Result(cmd_result.ERROR, "", "Error reading patch description\n") else: return cmd_result.Result(cmd_result.OK, "", "") def get_patch_file_name(self, patch): assert False, 'get_patch_file_name() must be provided in child class!!!' def get_patch_files(self, patch=None, withstatus=True): assert False, 'get_patch_files() must be provided in child class!!!' def get_patch_guards(self, patch): return '' def get_playground_root(self, fdir=None): if fdir: return fdir return os.getcwd() def get_selected_guards(self): return [] def get_top_patch(self): assert False, 'get_top_patch() must be provided in child class!!!' def has_add_files(self): # override in back end if the tool does not have an "add file" function return True def has_finish_patch(self): # override in back end if the tool has a "qfinish patch" function return False def has_guards(self): # override in back end if the tool supports patch guards return False def has_refresh_non_top(self): return False def is_available(self): return False def is_patch_applied(self, patch): assert False, 'is_patch_applied() must be provided in child class!!!' def is_playground(self, fdir=None): assert False, 'is_playground() must be provided in child class!!!' def last_patch_in_series(self): assert False, 'last_patch_in_series() must be provided in child class!!!' def new_playground(self, fdir=None): assert False, 'new_playground() must be provided in child class!!!' def requires(self): assert False, 'requires() must be provided in child class!!!' # create a null back end to use when the working directory is not a valid # playground class NullInterface(Interface): def __init__(self): Interface.__init__(self, "null") def _map_cmd_result(self, result, ignore_err_re=None): return result def _message(self): return os.getcwd() + ' is not a valid playground\n' def do_add_files_to_patch(self, filelist): return cmd_result.Result(cmd_result.ERROR, "", self._message()) def do_copy_file(self, file_name, dest, force=False): return cmd_result.Result(cmd_result.ERROR, "", self._message()) def do_new_patch(self, name, force=False): return cmd_result.Result(cmd_result.ERROR, "", self._message()) def do_exec_tool_cmd(self, cmd): return cmd_result.Result(cmd_result.ERROR, "", self._message()) def do_import_patch(self, filename, patchname=None, force=False): return cmd_result.Result(cmd_result.ERROR, "", self._message()) def do_fold_patch_file(self, patch): return cmd_result.Result(cmd_result.ERROR, "", self._message()) def do_move_file(self, file_name, dest, force=False): return cmd_result.Result(cmd_result.ERROR, "", self._message()) def do_pop_to(self, patch=None): return cmd_result.Result(cmd_result.ERROR, "", self._message()) def do_push_to(self, patch=None): return cmd_result.Result(cmd_result.ERROR, "", self._message()) def do_refresh(self, patch=None, force=False): return cmd_result.Result(cmd_result.ERROR, "", self._message()) def get_all_patches_data(self): return [] def get_combined_diff(self, start_patch=None, end_patch=None): return cmd_result.Result(cmd_result.ERROR, "", self._message()) def get_in_progress(self): return False def get_patch_file_db(self, patch=None): return fsdb.NullFileDb() def get_patch_files(self, patch=None, withstatus=True): return cmd_result.Result(cmd_result.OK, "", "") def get_top_patch(self): return "" def get_ws_file_db(self, patch=None): return fsdb.OsFileDb() def is_available(self): return True def last_patch_in_series(self): return "" def requires(self): return "Nothing!" gquilt-0.25/gquilt_pkg/diff.py0000664000076400007640000003703311507473020016674 0ustar peterpeter00000000000000### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import gtk, re, pango from gquilt_pkg import ifce, sourceview, dialogue, utils, gutils from gquilt_pkg import putils STATES = [gtk.STATE_NORMAL, gtk.STATE_ACTIVE, gtk.STATE_PRELIGHT, gtk.STATE_INSENSITIVE] class tws_line_count_display(gtk.HBox): def __init__(self): gtk.HBox.__init__(self) self.pack_start(gtk.Label("Added TWS lines:"), expand=False, fill=False) self._entry = gtk.Entry() self._entry.set_width_chars(1) self._entry.set_text(str(0)) self._entry.set_editable(False) self.pack_start(self._entry, expand=False, fill=False) self.show_all() def set_value(self, val): sval = str(val) self._entry.set_width_chars(len(sval)) self._entry.set_text(sval) if val: for state in STATES: self._entry.modify_base(state, gtk.gdk.color_parse("#FF0000")) else: for state in STATES: self._entry.modify_base(state, gtk.gdk.color_parse("#00FF00")) class DiffTextBuffer(sourceview.SourceBuffer): def __init__(self, file_list=None, table=None): if not table: table = sourceview.SourceTagTable() sourceview.SourceBuffer.__init__(self, table) if file_list is None: self._file_list = [] else: self._file_list = file_list self._tws_change_cbs = [] self.tws_check = re.compile('^(\+.*\S)(\s+\n)$') self.tws_list = [] self.tws_index = 0 self._action_group = gtk.ActionGroup("diff_text") self._action_group.add_actions( [ ("diff_save", gtk.STOCK_SAVE, "_Save", None, "Save the diff to previously nominated file", self._save_acb), ("diff_save_as", gtk.STOCK_SAVE_AS, "Save _as", None, "Save the diff to a nominated file", self._save_as_acb), ("diff_refresh", gtk.STOCK_REFRESH, "_Refresh", None, "Refresh contents of the diff", self._refresh_acb), ]) self._save_file = None self.check_set_save_sensitive() self.tws_display = tws_line_count_display() self.index_tag = self.create_tag("INDEX", weight=pango.WEIGHT_BOLD, foreground="#0000AA", family="monospace") self.sep_tag = self.create_tag("SEP", weight=pango.WEIGHT_BOLD, foreground="#0000AA", family="monospace") self.minus_tag = self.create_tag("MINUS", foreground="#AA0000", family="monospace") self.lab_tag = self.create_tag("LAB", foreground="#AA0000", family="monospace") self.plus_tag = self.create_tag("PLUS", foreground="#006600", family="monospace") self.added_tws_tag = self.create_tag("ADDED_TWS", background="#006600", family="monospace") self.star_tag = self.create_tag("STAR", foreground="#006600", family="monospace") self.rab_tag = self.create_tag("RAB", foreground="#006600", family="monospace") self.change_tag = self.create_tag("CHANGED", foreground="#AA6600", family="monospace") self.stats_tag = self.create_tag("STATS", foreground="#AA00AA", family="monospace") self.func_tag = self.create_tag("FUNC", foreground="#00AAAA", family="monospace") self.unchanged_tag = self.create_tag("UNCHANGED", foreground="black", family="monospace") def register_tws_change_cb(self, func): self._tws_change_cbs.append(func) def _append_tagged_text(self, text, tag): self.insert_with_tags(self.get_end_iter(), text, tag) def _append_patch_line(self, line): first_char = line[0] if first_char == " ": self._append_tagged_text(line, self.unchanged_tag) elif first_char == "+": match = self.tws_check.match(line) if match: self._append_tagged_text(match.group(1), self.plus_tag) self._append_tagged_text(match.group(2), self.added_tws_tag) return len(match.group(1)) else: self._append_tagged_text(line, self.plus_tag) elif first_char == "-": self._append_tagged_text(line, self.minus_tag) elif first_char == "!": self._append_tagged_text(line, self.change_tag) elif first_char == "@": i = line.find("@@", 2) if i == -1: self._append_tagged_text(line, self.stats_tag) else: self._append_tagged_text(line[:i+2], self.stats_tag) self._append_tagged_text(line[i+2:], self.func_tag) elif first_char == "=": self._append_tagged_text(line, self.sep_tag) elif first_char == "*": self._append_tagged_text(line, self.star_tag) elif first_char == "<": self._append_tagged_text(line, self.lab_tag) elif first_char == ">": self._append_tagged_text(line, self.rab_tag) else: self._append_tagged_text(line, self.index_tag) return 0 def _get_diff_text(self): return "" def set_contents(self): text = self._get_diff_text() old_count = len(self.tws_list) self.begin_not_undoable_action() self.set_text("") self.tws_list = [] line_no = 0 for line in text.splitlines(): offset = self._append_patch_line(line + '\n') if offset: self.tws_list.append((line_no, offset - 2)) line_no += 1 self.end_not_undoable_action() new_count = len(self.tws_list) self.tws_display.set_value(new_count) if not (new_count == old_count): for func in self._tws_change_cbs: func(new_count) def _tws_index_iter(self): pos = self.tws_list[self.tws_index] model_iter = self.get_iter_at_line_offset(pos[0], pos[1]) self.place_cursor(model_iter) return model_iter def get_tws_first_iter(self): self.tws_index = 0 return self._tws_index_iter() def get_tws_prev_iter(self): if self.tws_index: self.tws_index -= 1 return self._tws_index_iter() def get_tws_next_iter(self): self.tws_index += 1 if self.tws_index >= len(self.tws_list): self.tws_index = len(self.tws_list) - 1 return self._tws_index_iter() def get_tws_last_iter(self): self.tws_index = len(self.tws_list) - 1 return self._tws_index_iter() def _save_to_file(self): if not self._save_file: return try: fobj = open(self._save_file, 'w') except IOError as edata: strerror = edata[1] dialogue.report_any_problems((cmd_result.ERROR, "", strerror)) self.check_set_save_sensitive() return text = self.get_text(self.get_start_iter(), self.get_end_iter()) fobj.write(text) fobj.close() self.check_set_save_sensitive() def check_save_sensitive(self): return self._save_file is not None and os.path.exists(self._save_file) def check_set_save_sensitive(self): set_sensitive = self.check_save_sensitive() self._action_group.get_action("diff_save").set_sensitive(set_sensitive) def _refresh_acb(self, _action): self.set_contents() def _save_acb(self, _action): self._save_to_file() def _save_as_acb(self, _action): if self._save_file: suggestion = self._save_file else: suggestion = os.getcwd() self._save_file = dialogue.ask_file_name("Save as ...", suggestion=suggestion, existing=False) self._save_to_file() def get_action_button_box(self): return gutils.ActionHButtonBox([self._action_group], action_name_list=self.a_name_list) class DiffTextView(sourceview.SourceView): def __init__(self, buffer): sourceview.SourceView.__init__(self, buffer) fdesc = pango.FontDescription("mono, 10") self.modify_font(fdesc) self.set_margin(81) self.set_show_margin(True) context = self.get_pango_context() metrics = context.get_metrics(fdesc) width = pango.PIXELS(metrics.get_approximate_char_width() * 85) self.set_size_request(width, width / 2) self.set_cursor_visible(False) self.set_editable(False) self._action_group = gtk.ActionGroup("diff_tws_nav") self._action_group.add_actions( [ ("tws_nav_first", gtk.STOCK_GOTO_TOP, "_First", None, "Scroll to first line with added trailing white space", self._tws_nav_first_acb), ("tws_nav_prev", gtk.STOCK_GO_UP, "_Prev", None, "Scroll to previous line with added trailing white space", self._tws_nav_prev_acb), ("tws_nav_next", gtk.STOCK_GO_DOWN, "_Next", None, "Scroll to next line with added trailing white space", self._tws_nav_next_acb), ("tws_nav_last", gtk.STOCK_GOTO_BOTTOM, "_Last", None, "Scroll to last line with added trailing white space", self._tws_nav_last_acb), ]) self.tws_nav_buttonbox = gutils.ActionHButtonBox([self._action_group], ["tws_nav_first", "tws_nav_prev", "tws_nav_next", "tws_nav_last"]) def _tws_nav_first_acb(self, _action): self.scroll_to_iter(self.get_buffer().get_tws_first_iter(), 0.01, True) def _tws_nav_prev_acb(self, _action): self.scroll_to_iter(self.get_buffer().get_tws_prev_iter(), 0.01, True) def _tws_nav_next_acb(self, _action): self.scroll_to_iter(self.get_buffer().get_tws_next_iter(), 0.01, True) def _tws_nav_last_acb(self, _action): self.scroll_to_iter(self.get_buffer().get_tws_last_iter(), 0.01, True) class DiffTextWidget(gtk.VBox): def __init__(self, diff_view): gtk.VBox.__init__(self) self.diff_view = diff_view self.pack_start(gutils.wrap_in_scrolled_window(self.diff_view)) self._tws_nav_buttons_packed = False buffer = self.diff_view.get_buffer() buffer.register_tws_change_cb(self._tws_change_cb) buffer.set_contents() self.show_all() def _tws_change_cb(self, new_count): if self._tws_nav_buttons_packed and not new_count: self.remove(self.diff_view.tws_nav_buttonbox) self.diff_view.set_cursor_visible(False) self._tws_nav_buttons_packed = False elif not self._tws_nav_buttons_packed and new_count: self.pack_start(self.diff_view.tws_nav_buttonbox, expand=False, fill=True) self.diff_view.set_cursor_visible(True) self._tws_nav_buttons_packed = True class PmDiffTextBuffer(DiffTextBuffer): def __init__(self, file_list=None, patch=None, table=None): DiffTextBuffer.__init__(self, file_list=file_list, table=table) self._patch = patch if not patch: self.a_name_list = ["diff_save", "diff_save_as", "diff_refresh"] else: # the diff between two revs is immutable so refresh is redundant self.a_name_list = ["diff_save", "diff_save_as"] self.diff_buttons = gutils.ActionButtonList([self._action_group], self.a_name_list) def _get_diff_text(self): res, text, serr = ifce.ifce.get_diff_for_files(self._file_list, self._patch) dialogue.report_any_problems((res, text, serr)) return text class PmDiffTextView(DiffTextView): def __init__(self, file_list=None, patch=None): buffer = PmDiffTextBuffer(file_list, patch=patch) DiffTextView.__init__(self, buffer=buffer) class PmDiffTextWidget(DiffTextWidget): def __init__(self, file_list=None, patch=None): diff_view = PmDiffTextView(file_list=file_list, patch=patch) DiffTextWidget.__init__(self, diff_view=diff_view) class PmDiffTextDialog(dialogue.AmodalDialog): def __init__(self, parent, file_list=None, patch=None): flags = gtk.DIALOG_DESTROY_WITH_PARENT dialogue.AmodalDialog.__init__(self, None, parent, flags, ()) title = 'diff: %s' % utils.cwd_rel_home() if patch: title += " Patch: %s" % patch else: title += " Patch: []" self.set_title(title) dtw = PmDiffTextWidget(file_list, patch=patch) self.vbox.pack_start(dtw) tws_display = dtw.diff_view.get_buffer().tws_display self.action_area.pack_end(tws_display, expand=False, fill=False) for button in dtw.diff_view.get_buffer().diff_buttons.list: self.action_area.pack_start(button) self.add_buttons(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE) self.connect("response", self._close_cb) self.show_all() def _close_cb(self, dialog, response_id): dialog.destroy() class CombinedDiffTextBuffer(DiffTextBuffer): def __init__(self, start_patch=None, end_patch=None, table=None): DiffTextBuffer.__init__(self, file_list=None, table=table) self.start_patch = start_patch self.end_patch = end_patch self.a_name_list = ["diff_save", "diff_save_as", "diff_refresh"] self.diff_buttons = gutils.ActionButtonList([self._action_group], self.a_name_list) def _get_diff_text(self): res, text, serr = ifce.ifce.get_combined_diff(self.start_patch, self.end_patch) dialogue.report_any_problems((res, text, serr)) return text class CombinedDiffTextView(DiffTextView): def __init__(self, start_patch=None, end_patch=None): buffer = CombinedDiffTextBuffer(start_patch=start_patch, end_patch=end_patch) DiffTextView.__init__(self, buffer=buffer) class CombinedDiffTextWidget(DiffTextWidget): def __init__(self, start_patch=None, end_patch=None): diff_view = CombinedDiffTextView(start_patch=start_patch, end_patch=end_patch) DiffTextWidget.__init__(self, diff_view=diff_view) class CombinedDiffTextDialog(dialogue.AmodalDialog): def __init__(self, parent, start_patch=None, end_patch=None): flags = gtk.DIALOG_DESTROY_WITH_PARENT dialogue.AmodalDialog.__init__(self, None, parent, flags, ()) title = 'combined diff: ' if start_patch is None: if end_patch is None: title += ' (all applied patches)' else: title += ' from patch "' + end_patch + '"' if end_patch is not None: title += ' to patch "' + end_patch + '"' self.set_title(title) dtw = CombinedDiffTextWidget(start_patch=start_patch, end_patch=end_patch) self.vbox.pack_start(dtw) tws_display = dtw.diff_view.get_buffer().tws_display self.action_area.pack_end(tws_display, expand=False, fill=False) for button in dtw.diff_view.get_buffer().diff_buttons.list: self.action_area.pack_start(button) self.add_buttons(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE) self.connect("response", self._close_cb) self.show_all() def _close_cb(self, dialog, response_id): dialog.destroy() gquilt-0.25/gquilt_pkg/patch_list.py0000664000076400007640000011471011507276575020134 0ustar peterpeter00000000000000### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import collections, gobject, gtk, os, tempfile import re from gquilt_pkg import tlview, dialogue, ws_event, gutils, ifce, icons from gquilt_pkg import cmd_result, utils, text_edit, file_tree, diff from gquilt_pkg import const Row = collections.namedtuple('Row', ['name', 'icon', 'markup']) _MODEL_TEMPLATE = Row( name=gobject.TYPE_STRING, icon=gobject.TYPE_STRING, markup=gobject.TYPE_STRING, ) _NAME = tlview.model_col(_MODEL_TEMPLATE, 'name') _MARKUP = tlview.model_col(_MODEL_TEMPLATE, 'markup') _ICON = tlview.model_col(_MODEL_TEMPLATE, 'icon') class Store(tlview.ListStore): def __init__(self): tlview.ListStore.__init__(self, _MODEL_TEMPLATE) def get_patch_name(self, plist_iter): return self.get_labelled_value(plist_iter, 'name') def _markup_applied_patch(patch_name, guards, selected): markup = patch_name appliable = True if guards: unselected_pluses = False selected_pluses = False selected_minuses = False markup += " :" for guard in guards: if guard[1:] in selected: markup += " %s" % guard if guard[0] == "-": selected_minuses = True else: selected_pluses = True else: markup += " %s" % guard unselected_pluses = unselected_pluses or guard[0] == "+" if selected_minuses: appliable = False elif selected_pluses: appliable = True else: appliable = not unselected_pluses return (markup, appliable) def _markup_unapplied_patch(patch_name, guards, selected): amarkup, appliable = _markup_applied_patch(patch_name, guards, selected) markup = '' + amarkup + '' return (markup, appliable) def _patch_status_icon(status): if status == const.APPLIED: return icons.APPLIED_OK elif status == const.TOP_PATCH: return icons.TOP_OK elif status == const.APPLIED_NEEDS_REFRESH: return icons.APPLIED_NOT_OK elif status == const.TOP_PATCH_NEEDS_REFRESH: return icons.TOP_NOT_OK else: return None _UI_DESCR = \ ''' ''' APPLIED = "pm_sel_applied" UNAPPLIED = "pm_sel_unapplied" UNAPPLIED_AND_INTERDIFF = "pm_sel_unapplied_interdiff" APPLIED_INDIFFERENT = "pm_sel_indifferent" PUSH_POSSIBLE = "pm_push_possible" PUSH_NOT_POSSIBLE = "pm_push_not_possible" POP_POSSIBLE = "pm_pop_possible" POP_NOT_POSSIBLE = "pm_pop_not_possible" PUSH_POP_INDIFFERENT = "pm_push_pop_indifferent" APPLIED_CONDITIONS = [ APPLIED, UNAPPLIED, UNAPPLIED_AND_INTERDIFF, APPLIED_INDIFFERENT, ] PUSH_POP_CONDITIONS = [ PUSH_POSSIBLE, PUSH_NOT_POSSIBLE, POP_POSSIBLE, POP_NOT_POSSIBLE, PUSH_POP_INDIFFERENT, ] _VIEW_TEMPLATE = tlview.ViewTemplate( properties={ 'enable-grid-lines' : False, 'reorderable' : False, 'rules_hint' : False, 'headers-visible' : False, }, selection_mode=gtk.SELECTION_SINGLE, columns=[ tlview.Column( title='Patch List', properties={'expand': False, 'resizable' : True}, cells=[ tlview.Cell( creator=tlview.CellCreator( function=gtk.CellRendererPixbuf, expand=False, start=True ), properties={}, renderer=None, attributes = {'stock_id' : tlview.model_col(_MODEL_TEMPLATE, 'icon')} ), tlview.Cell( creator=tlview.CellCreator( function=gtk.CellRendererText, expand=False, start=True ), properties={'editable' : False}, renderer=None, attributes = {'markup' : tlview.model_col(_MODEL_TEMPLATE, 'markup')} ), ], ), ] ) _finish_empty_msg_prompt = os.linesep.join( ["Do you wish to:", "\tcancel,", "\tedit the description and retry, or", "\tforce the finish operation?" ]) class List(gtk.VBox, dialogue.BusyIndicatorUser, ws_event.Listener): def __init__(self, busy_indicator=None): gtk.VBox.__init__(self) self.store = Store() self.view = tlview.View(_VIEW_TEMPLATE, self.store) self.seln = self.view.get_selection() dialogue.BusyIndicatorUser.__init__(self, busy_indicator) ws_event.Listener.__init__(self) self.ui_manager = gutils.UIManager() self.unapplied_count = 0 self.applied_count = 0 self.last_import_dir = None self._action_group = {} for condition in APPLIED_CONDITIONS + PUSH_POP_CONDITIONS: self._action_group[condition] = gtk.ActionGroup(condition) self.ui_manager.insert_action_group(self._action_group[condition], -1) self._action_group[APPLIED].add_actions( [ ("pm_pop_to_patch", icons.POP, "Pop To", None, "Pop to the selected patch", self.do_pop_to), ("pm_finish_to", icons.FINISH, "Finish To", None, "Move patches up to the selected patch into main repository", self.do_finish_to), ("pm_refresh_selected", icons.STOCK_REFRESH_PATCH, "Refresh", None, "Refresh the selected patch", self.do_refresh_selected_patch), ("pm_view_patch_extdiff", icons.DIFF, "External Diff", None, "Launch external diff viewer for the selected patch", self.show_extdiff_acb), ]) self._action_group[APPLIED_INDIFFERENT].add_actions( [ ("pm_edit_patch_descr", gtk.STOCK_EDIT, "Description", None, "Edit the selected patch's description", self.do_edit_description), ("pm_view_patch_files", gtk.STOCK_FILE, "Files", None, "Show files affected by the selected patch", self.show_files), ("pm_view_patch_diff", icons.DIFF, "Diff", None, "Show diff for the selected patch", self.show_diff_acb), ("pm_rename_patch", None, "Rename", None, "Rename the selected patch", self.do_rename), ("pm_set_patch_guards", icons.STOCK_PATCH_GUARD, "Guard", None, "Set guards on the selected patch", self.do_set_guards), ]) self._action_group[UNAPPLIED].add_actions( [ ("pm_delete_patch", gtk.STOCK_DELETE, "Delete", None, "Delete the selected patch", self.do_delete), ("pm_push_to", icons.PUSH, "Push To", None, "Push to the selected patch", self.do_push_to), ("pm_fold", icons.FOLD, "Fold", None, "Fold the selected patch into the top patch", self.do_fold), ("pm_fold_to", icons.FOLD, "Fold To", None, "Fold patches up to the selected patch into the top patch", self.do_fold_to), ("pm_duplicate", gtk.STOCK_COPY, "Duplicate", None, "Duplicate the selected patch behind the top patch", self.do_duplicate), ]) self._action_group[UNAPPLIED_AND_INTERDIFF].add_actions( [ ("pm_interdiff", gtk.STOCK_PASTE, "Interdiff", None, 'Place the "interdiff" of the selected patch and the top patch behind the top patch', self.do_interdiff), ]) self._action_group[PUSH_POSSIBLE].add_actions( [ ("pm_push", icons.PUSH, "Push", None, "Apply the next unapplied patch", self.do_push), ("pm_push_all", icons.PUSH, "Push All", None, "Apply all remaining unapplied patches", self.do_push_all), ]) self._action_group[POP_POSSIBLE].add_actions( [ ("pm_pop", icons.POP, "Pop", None, "Pop the top applied patch", self.do_pop), ("pm_pop_all", icons.POP, "Pop All", None, "Pop all remaining applied patches", self.do_pop_all), ("pm_refresh_top_patch", icons.STOCK_REFRESH_PATCH, "Refresh", None, "Refresh the top patch", self.do_refresh_top_patch), ("pm_fold_external_patch", icons.FOLD, "Fold", None, "Merge an external patch into the top patch", self.do_fold_external_patch), ]) self._action_group[PUSH_POP_INDIFFERENT].add_actions( [ ("menu_patches", None, "_Patches"), ("menu_patch_list", None, "Patch _List"), ("refresh_patch_list", gtk.STOCK_REFRESH, "Update Patch List", None, "Refresh/update the patch list display", self._update_list_cb), ("pm_new", icons.STOCK_NEW_PATCH, "New", None, "Create a new patch", self.do_new_patch), ("pm_import_external_patch", icons.IMPORT_PATCH, "Import", None, "Import an external patch", self.do_import_external_patch), ("pm_import_patch_series", icons.IMPORT_PATCH, "Import Patch Series", None, "Import an external patch (mq/quilt style) series", self.do_import_external_patch_series), ("pm_select_guards", icons.STOCK_PATCH_GUARD_SELECT, "Select", None, "Select which guards are in force", self.do_select_guards), ]) self.ui_manager.add_ui_from_string(_UI_DESCR) hbox = gtk.HBox() self.backend_tag = gtk.Label() hbox.pack_start(self.backend_tag, False, False) hbox.pack_start(self.ui_manager.get_widget('/patch_list_menubar')) self.pack_start(hbox, False, False) self.seln.connect("changed", self._selection_changed_cb) self.pack_start(gutils.wrap_in_scrolled_window(self.view)) self.view.connect('button_press_event', self._handle_button_press_cb) self.view.connect("key_press_event", self._handle_key_press_cb) self.add_notification_cb(ws_event.CHANGE_WD, self._repopulate_list_cb) self.add_notification_cb(ws_event.PATCH_CHANGES, self._update_list_cb) self.show_all() self.repopulate_list() self._selection_changed_cb(self.seln) def _update_action_visibility(self): self._action_group[APPLIED].get_action('pm_refresh_selected').set_visible(ifce.ifce.has_refresh_non_top()) self._action_group[APPLIED].get_action('pm_view_patch_extdiff').set_visible(ifce.ifce.extdiff_and_full_patch_ok()) self._action_group[APPLIED].get_action('pm_finish_to').set_visible(ifce.ifce.has_finish_patch()) self._action_group[APPLIED_INDIFFERENT].get_action('pm_set_patch_guards').set_visible(ifce.ifce.has_guards()) def _selection_changed_cb(self, selection): if selection.count_selected_rows() == 0: for index in APPLIED, UNAPPLIED, APPLIED_INDIFFERENT, UNAPPLIED_AND_INTERDIFF: self._action_group[index].set_sensitive(False) else: model, model_iter = self.seln.get_selected() applied = model.get_value(model_iter, 1) != None self._action_group[APPLIED_INDIFFERENT].set_sensitive(True) self._action_group[APPLIED].set_sensitive(applied) self._action_group[UNAPPLIED].set_sensitive(not applied) interdiff_avail = utils.which("interdiff") is not None self._action_group[UNAPPLIED_AND_INTERDIFF].set_sensitive(interdiff_avail and not applied) def _handle_button_press_cb(self, widget, event): if event.type == gtk.gdk.BUTTON_PRESS: if event.button == 3: self._update_action_visibility() menu = self.ui_manager.get_widget('/patches_popup') menu.popup(None, None, None, event.button, event.time) return True elif event.button == 2: self.seln.unselect_all() return True return False def _handle_key_press_cb(self, widget, event): if event.keyval == gtk.gdk.keyval_from_name('Escape'): self.seln.unselect_all() return True return False def get_selected_patch(self): store, selection = self.seln.get_selected_rows() return store.get_patch_name(store.get_iter(selection[0])) def update_in_repo_sensitivity(self): if ifce.in_valid_pgnd: self._selection_changed_cb(self.seln) self._action_group[PUSH_POP_INDIFFERENT].set_sensitive(True) self._action_group[PUSH_POP_INDIFFERENT].get_action("pm_select_guards").set_sensitive(ifce.ifce.has_guards()) else: for condition in APPLIED_CONDITIONS + PUSH_POP_CONDITIONS: self._action_group[condition].set_sensitive(False) def _update_list_cb(self, _arg=None): self._repopulate_list_cb(_arg) def repopulate_list(self): patch_data_list = ifce.ifce.get_all_patches_data() selected = ifce.ifce.get_selected_guards() self.unapplied_count = 0 self.applied_count = 0 self.store.clear() for patch_data in patch_data_list: if patch_data.state is not const.NOT_APPLIED: icon = _patch_status_icon(patch_data.state) markup, dummy = _markup_applied_patch(patch_data.name, patch_data.guards, selected) self.store.append([patch_data.name, icon, markup]) self.applied_count += 1 else: markup, appliable = _markup_unapplied_patch(patch_data.name, patch_data.guards, selected) self.store.append([patch_data.name, None, markup]) if appliable: self.unapplied_count += 1 self._action_group[POP_POSSIBLE].set_sensitive(self.applied_count > 0) self._action_group[POP_NOT_POSSIBLE].set_sensitive(self.applied_count == 0) self._action_group[PUSH_POSSIBLE].set_sensitive(self.unapplied_count > 0) self._action_group[PUSH_NOT_POSSIBLE].set_sensitive(self.unapplied_count == 0) self.seln.unselect_all() self.backend_tag.set_text(ifce.ifce.name) def _repopulate_list_cb(self, _arg=None): self.show_busy() self.update_in_repo_sensitivity() self.repopulate_list() self.unshow_busy() def _do_refresh(self, patch=None, notify=True): self.show_busy() res = ifce.ifce.do_refresh(patch=patch, notify=notify) self.unshow_busy() if res.eflags != cmd_result.OK: if res.eflags & cmd_result.SUGGEST_FORCE: if dialogue.ask_force_or_cancel(res) == dialogue.RESPONSE_FORCE: self.show_busy() res = ifce.ifce.do_refresh(patch=patch, notify=notify, force=True) self.unshow_busy() elif res.eflags & cmd_result.SUGGEST_RECOVER: if dialogue.ask_recover_or_cancel(res) == dialogue.RESPONSE_RECOVER: self.show_busy() res = ifce.ifce.do_recover_interrupted_refresh() if res.eflags == cmd_result.OK: res = ifce.ifce.do_refresh(patch=patch, notify=notify) self.unshow_busy() if res.eflags != cmd_result.OK: # There're may still be problems dialogue.report_any_problems(res) return def do_refresh_top_patch(self, _action=None): self._do_refresh(notify=True) def do_refresh_selected_patch(self, _action=None): self._do_refresh(patch=self.get_selected_patch(), notify=True) def _do_pop_to(self, patch=None): while True: self.show_busy() res = ifce.ifce.do_pop_to(patch=patch) self.unshow_busy() if res.eflags != cmd_result.OK: if res.eflags & cmd_result.SUGGEST_FORCE_OR_REFRESH: ans = dialogue.ask_force_refresh_or_cancel(res) if ans == gtk.RESPONSE_CANCEL: return False elif ans == dialogue.RESPONSE_REFRESH: self._do_refresh(notify=False) continue elif ans == dialogue.RESPONSE_FORCE: self.show_busy() res = ifce.ifce.do_pop_to(force=True) self.unshow_busy() if res.eflags != cmd_result.OK: # there're are still problems dialogue.report_any_problems(res) return False return True def do_pop_to(self, _action=None): patch = self.get_selected_patch() while ifce.ifce.get_top_patch() != patch: if not self._do_pop_to(patch): break def do_pop(self, _action=None): self._do_pop_to() def do_pop_all(self, _action=None): self._do_pop_to("") def _do_push_to(self, patch=None, merge=False): while True: self.show_busy() res = ifce.ifce.do_push_to(patch=patch, merge=merge) self.unshow_busy() if res.eflags != cmd_result.OK: if res.eflags & cmd_result.SUGGEST_FORCE_OR_REFRESH: ans = dialogue.ask_force_refresh_or_cancel(res, parent=None) if ans == gtk.RESPONSE_CANCEL: return False if ans == dialogue.RESPONSE_REFRESH: self._do_refresh(notify=False) continue elif ans == dialogue.RESPONSE_FORCE: self.show_busy() res = ifce.ifce.do_push_to(force=True, merge=merge) self.unshow_busy() if res.eflags != cmd_result.OK: # there're are still problems dialogue.report_any_problems(res) return False return True def do_push_to(self, _action=None): patch = self.get_selected_patch() while ifce.ifce.get_top_patch() != patch: if not self._do_push_to(patch=patch): break def do_push(self, _action=None): self._do_push_to() def do_push_merge(self, _action=None): self._do_push_to(merge=True) def do_push_all(self, _action=None): self._do_push_to(patch="", merge=False) def do_push_all_with_merge(self, _action=None): self._do_push_to(patch="", merge=True) def do_finish_to(self, _action=None): patch = self.get_selected_patch() while True: next_patch = ifce.ifce.get_base_patch() if not next_patch: break while True: self.show_busy() is_ok = ifce.ifce.get_description_is_finish_ready(next_patch) self.unshow_busy() if is_ok: break dummyres = cmd_result.Result(eflags=cmd_result.SUGGEST_ALL, stdout = '"%s" has an empty description.' % next_patch, stderr = '') ans = dialogue.ask_edit_force_or_cancel(dummyres, clarification=_finish_empty_msg_prompt, parent=None) if ans == gtk.RESPONSE_CANCEL: return elif ans == dialogue.RESPONSE_FORCE: break self.do_edit_description_wait(next_patch) self.show_busy() res = ifce.ifce.do_finish_patch(next_patch) self.unshow_busy() if res.eflags != cmd_result.OK: dialogue.report_any_problems(res) break if patch == next_patch: break def do_edit_description_wait(self, patch=None): if not patch: patch = self.get_selected_patch() PatchDescrEditDialog(patch, parent=None).run() def do_edit_description(self, _action=None): patch = self.get_selected_patch() PatchDescrEditDialog(patch, parent=None).show() def show_files(self, _action=None): patch = self.get_selected_patch() dialog = file_tree.PatchFilesDialog(patch=patch) dialog.show() def show_diff_acb(self, _action=None): patch = self.get_selected_patch() dialog = diff.PmDiffTextDialog(parent=dialogue.main_window, patch=patch) dialog.show() def show_extdiff_acb(self, _action=None): _patch = self.get_selected_patch() # TODO: impliment extdiff for the patch list dialogue.inform_user("extdiff for patch list not yet implemented") def do_rename(self, _action=None): patch = self.get_selected_patch() dialog = dialogue.ReadTextDialog("Rename Patch: %s" % patch, "New Name:", patch) response = dialog.run() if response == gtk.RESPONSE_OK: new_name = dialog.entry.get_text() dialog.destroy() if patch == new_name: return res = ifce.ifce.do_rename_patch(patch, new_name) dialogue.report_any_problems(res) else: dialog.destroy() def do_set_guards(self, _action=None): patch = self.get_selected_patch() cguards = ' '.join(ifce.ifce.get_patch_guards(patch)) dialog = dialogue.ReadTextDialog("Set Guards: %s" % patch, "Guards:", cguards) response = dialog.run() if response == gtk.RESPONSE_OK: guards = dialog.entry.get_text() dialog.destroy() res = ifce.ifce.do_set_patch_guards(patch, guards) dialogue.report_any_problems(res) else: dialog.destroy() def do_select_guards(self, _action=None): cselected_guards = ' '.join(ifce.ifce.get_selected_guards()) dialog = dialogue.ReadTextDialog("Select Guards: %s" % os.getcwd(), "Guards:", cselected_guards) response = dialog.run() if response == gtk.RESPONSE_OK: selected_guards = dialog.entry.get_text() dialog.destroy() res = ifce.ifce.do_select_guards(selected_guards) dialogue.report_any_problems(res) else: dialog.destroy() def do_delete(self, _action=None): patch = self.get_selected_patch() if dialogue.ask_ok_cancel('Confirm delete "%s" patch?' % patch): res = ifce.ifce.do_delete_patch(patch) dialogue.report_any_problems(res) def do_fold(self, _action=None): patch = self.get_selected_patch() res = ifce.ifce.do_fold_patch(patch) dialogue.report_any_problems(res) def do_fold_to(self, _action=None): patch = self.get_selected_patch() while True: next_patch = ifce.ifce.get_next_patch() if not next_patch: return res = ifce.ifce.do_fold_patch(next_patch) if res.eflags != cmd_result.OK: dialogue.report_any_problems(res) return if patch == next_patch: return def do_duplicate(self, _action=None): patch = self.get_selected_patch() dialog = DuplicatePatchDialog(patch, parent=None) if dialog.run() == gtk.RESPONSE_CANCEL: dialog.destroy() return duplicate_patch_name = dialog.get_duplicate_patch_name() duplicate_patch_descr = dialog.get_duplicate_patch_descr() dialog.destroy() if not duplicate_patch_name: return self.show_busy() old_pfname = ifce.ifce.get_patch_file_name(patch) res = ifce.ifce.do_import_patch(old_pfname, duplicate_patch_name) self.unshow_busy() if res.eflags == cmd_result.ERROR_SUGGEST_FORCE: if dialogue.ask_force_refresh_or_cancel(res) == dialogue.RESPONSE_FORCE: self.show_busy() res = ifce.ifce.do_import_patch(old_pfname, duplicate_patch_name, force=True) self.unshow_busy() else: return if res.eflags != cmd_result.OK: dialogue.report_any_problems(res) if res.eflags & cmd_result.ERROR: return self.show_busy() res = ifce.ifce.do_set_patch_description(duplicate_patch_name, duplicate_patch_descr) self.unshow_busy() dialogue.report_any_problems(res) def do_interdiff(self, _action=None): patch = self.get_selected_patch() dialog = DuplicatePatchDialog(patch, verb="Interdiff", parent=None) if dialog.run() == gtk.RESPONSE_CANCEL: dialog.destroy() return interdiff_patch_name = dialog.get_duplicate_patch_name() interdiff_patch_descr = dialog.get_duplicate_patch_descr() dialog.destroy() if not interdiff_patch_name: return self.show_busy() top_patch = ifce.ifce.get_top_patch() if top_patch: top_pfname = ifce.ifce.get_patch_file_name(top_patch) old_pfname = ifce.ifce.get_patch_file_name(patch) res = utils.run_cmd("interdiff %s %s" % (top_pfname, old_pfname)) if res.eflags != cmd_result.OK: dialogue.report_any_problems(res) return temp_pfname = tempfile.mktemp() tfobj = open(temp_pfname, 'w') tfobj.write(os.linesep.join([interdiff_patch_descr, res.stdout])) tfobj.close() else: temp_pfname = ifce.ifce.get_patch_file_name(patch) res = ifce.ifce.do_import_patch(temp_pfname, interdiff_patch_name) self.unshow_busy() if res.eflags == cmd_result.ERROR_SUGGEST_FORCE: if dialogue.ask_force_refresh_or_cancel(res) == dialogue.RESPONSE_FORCE: self.show_busy() res = ifce.ifce.do_import_patch(temp_pfname, interdiff_patch_name, force=True) self.unshow_busy() if top_patch: os.remove(temp_pfname) if res.eflags != cmd_result.OK: dialogue.report_any_problems(res) def do_new_patch(self, _action=None): dialog = NewPatchDialog(parent=None) if dialog.run() == gtk.RESPONSE_CANCEL: dialog.destroy() return new_patch_name = dialog.get_new_patch_name() new_patch_descr = dialog.get_new_patch_descr() dialog.destroy() if not new_patch_name: return force = False while True: self.show_busy() res = ifce.ifce.do_new_patch(new_patch_name, force=force) self.unshow_busy() if res.eflags & cmd_result.SUGGEST_FORCE_OR_REFRESH: ans = dialogue.ask_force_refresh_or_cancel(res, parent=None) if ans == gtk.RESPONSE_CANCEL: return if ans == dialogue.RESPONSE_REFRESH: self._do_refresh(notify=False) elif ans == dialogue.RESPONSE_FORCE: force = True else: dialogue.report_any_problems(res) break if new_patch_descr and res.eflags != cmd_result.ERROR: self.show_busy() res = ifce.ifce.do_set_patch_description(new_patch_name, new_patch_descr) self.unshow_busy() dialogue.report_any_problems(res) def do_import_external_patch(self, _action=None): patch_file_name = dialogue.ask_file_name("Select patch file to be imported") if not patch_file_name: return force = False patch_name = None while True: self.show_busy() res = ifce.ifce.do_import_patch(patch_file_name, patch_name, force) self.unshow_busy() if res.eflags & cmd_result.SUGGEST_FORCE_OR_RENAME: ans = dialogue.ask_rename_force_or_cancel(res, clarification='Force import of patch, rename patch or cancel import?') if ans == gtk.RESPONSE_CANCEL: return elif ans == dialogue.RESPONSE_FORCE: force = True continue elif ans == dialogue.RESPONSE_RENAME: if not patch_name: patch_name = os.path.basename(patch_file_name) patch_name = dialogue.get_modified_string("Rename Patch", "New Name :", patch_name) continue dialogue.report_any_problems(res) break def do_import_external_patch_series(self, _action=None): patch_series_dir = dialogue.ask_dir_name("Select patch series to be imported") if not patch_series_dir: return series_fn = os.sep.join([patch_series_dir, "series"]) if (not os.path.exists(series_fn) and os.path.isfile(series_fn)): dialogue.report_any_problems(cmd_result.Result(cmd_result.ERROR, "", "Series file not found.")) return sfobj = open(series_fn, 'r') series = sfobj.readlines() sfobj.close() series.reverse() index = 0 patch_name_re = re.compile("\s*([^\s#]+)[\s#]*.*$") while index < len(series): match = patch_name_re.match(series[index]) if not match: index += 1 continue base_name = match.group(1) patch_file_name = os.sep.join([patch_series_dir, base_name]) force = False patch_name = None while True: self.show_busy() res = ifce.ifce.do_import_patch(patch_file_name, patch_name, force) self.unshow_busy() if res.eflags & cmd_result.SUGGEST_FORCE_OR_RENAME: question = os.linesep.join([res.stdout, res.stderr, "Force import of patch, rename patch or skip patch?"]) ans = dialogue.ask_rename_force_or_skip(question) if ans == dialogue.RESPONSE_SKIP_ALL: index = len(series) break elif ans == dialogue.RESPONSE_SKIP: break elif ans == dialogue.RESPONSE_FORCE: force = True continue elif ans == dialogue.RESPONSE_RENAME: if not patch_name: patch_name = base_name patch_name = dialogue.get_modified_string("Rename Patch", "New Name :", patch_name) continue dialogue.report_any_problems(res) break index += 1 def do_fold_external_patch(self, _action=None): name = dialogue.ask_file_name('Select patch to merge ..', suggestion=self.last_import_dir) if name is not None: self.last_import_dir = os.path.dirname(name) self._show_busy() result = ifce.do_fold_patch_file(self.console, name) self._unshow_busy() dialogue.report_any_problems(result) class NewPatchDescrEditWidget(gtk.VBox): def __init__(self, view=None): gtk.VBox.__init__(self) # TextView for change message if view: self.view = view else: self.view = text_edit.NewPatchSummaryView() hbox = gtk.HBox() menubar = self.view.ui_manager.get_widget("/patch_summary_menubar") hbox.pack_start(menubar, fill=True, expand=False) toolbar = self.view.ui_manager.get_widget("/patch_summary_toolbar") toolbar.set_style(gtk.TOOLBAR_BOTH) toolbar.set_orientation(gtk.ORIENTATION_HORIZONTAL) hbox.pack_end(toolbar, fill=False, expand=False) self.pack_start(hbox, expand=False) self.pack_start(gutils.wrap_in_scrolled_window(self.view)) self.show_all() self.set_focus_child(self.view) class PatchDescrEditWidget(NewPatchDescrEditWidget): def __init__(self, get_summary, set_summary, patch=None): self.view = text_edit.PatchSummaryView(get_summary, set_summary, patch) NewPatchDescrEditWidget.__init__(self, view=self.view) self.view.load_summary() def get_save_button(self): return self.view.save_button class GenericPatchDescrEditDialog(dialogue.AmodalDialog): def __init__(self, get_summary, set_summary, parent, patch=None): flags = gtk.DIALOG_DESTROY_WITH_PARENT dialogue.AmodalDialog.__init__(self, None, parent, flags, None) self.set_title('"%s" Description: %s' % (patch, utils.cwd_rel_home())) self.edit_descr_widget = PatchDescrEditWidget(get_summary, set_summary, patch) self.vbox.pack_start(self.edit_descr_widget) self.action_area.pack_start(self.edit_descr_widget.get_save_button()) self.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE) self.connect("response", self._handle_response_cb) self.set_focus_child(self.edit_descr_widget.view) def _handle_response_cb(self, dialog, response_id): if response_id == gtk.RESPONSE_CLOSE: if self.edit_descr_widget.view.get_buffer().get_modified(): qtn = os.linesep.join(["Unsaved changes to summary will be lost.", "Close anyway?"]) if dialogue.ask_yes_no(qtn): self.destroy() else: self.destroy() class PatchDescrEditDialog(GenericPatchDescrEditDialog): def __init__(self, patch, parent): GenericPatchDescrEditDialog.__init__(self, get_summary=ifce.ifce.get_patch_description, set_summary=ifce.ifce.do_set_patch_description, parent=parent, patch=patch) class DuplicatePatchDialog(dialogue.Dialog): def __init__(self, patch, parent, verb="Duplicate"): flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT title = '%s "%s": %s' % (verb, patch, utils.path_rel_home(os.getcwd())) dialogue.Dialog.__init__(self, title, parent, flags, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) hbox = gtk.HBox() vbox = gtk.VBox() vbox.pack_start(gtk.Label('%s Patch:' % verb)) vbox.pack_start(gtk.Label(' As Patch Named:')) hbox.pack_start(vbox, fill=False, expand=False) vbox = gtk.VBox() entry = gtk.Entry() entry.set_text(patch) entry.set_editable(False) vbox.pack_start(entry) self.new_name_entry = gtk.Entry() self.new_name_entry.set_width_chars(32) vbox.pack_start(self.new_name_entry) hbox.pack_start(vbox) hbox.show_all() self.vbox.pack_start(hbox) self.edit_descr_widget = NewPatchDescrEditWidget() res, old_descr, _serr = ifce.ifce.get_patch_description(patch) if not res: self.edit_descr_widget.view.get_buffer().set_text(old_descr) self.vbox.pack_start(self.edit_descr_widget) self.set_focus_child(self.new_name_entry) def get_duplicate_patch_name(self): return self.new_name_entry.get_text() def get_duplicate_patch_descr(self): return self.edit_descr_widget.view.get_msg() class NewPatchDialog(dialogue.Dialog): def __init__(self, parent, objname="Patch"): flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT title = 'New %s: %s -- gquilt' % (objname, utils.path_rel_home(os.getcwd())) dialogue.Dialog.__init__(self, title, parent, flags, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) if not parent: self.set_icon_from_file(icons.APP_ICON_FILE) self.hbox = gtk.HBox() self.hbox.pack_start(gtk.Label('New %s Name:' % objname), fill=False, expand=False) self.new_name_entry = gtk.Entry() self.new_name_entry.set_width_chars(32) self.hbox.pack_start(self.new_name_entry) self.hbox.show_all() self.vbox.pack_start(self.hbox) self.edit_descr_widget = NewPatchDescrEditWidget() self.vbox.pack_start(self.edit_descr_widget) self.set_focus_child(self.new_name_entry) def get_new_patch_name(self): return self.new_name_entry.get_text() def get_new_patch_descr(self): return self.edit_descr_widget.view.get_msg() gquilt-0.25/gquilt_pkg/sourceview.py0000664000076400007640000000354711476544765020206 0ustar peterpeter00000000000000### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA try: import gtksourceview class SourceView(gtksourceview.SourceView): def __init__(self, buffer=None): gtksourceview.SourceView.__init__(self, buffer=buffer) class SourceTagTable(gtksourceview.SourceTagTable): def __init__(self): gtksourceview.SourceTagTable.__init__(self) class SourceBuffer(gtksourceview.SourceBuffer): def __init__(self, table=None): gtksourceview.SourceBuffer.__init__(self, table=table) except ImportError: import gtk class SourceView(gtk.TextView): def __init__(self, buffer=None): gtk.TextView.__init__(self, buffer=buffer) def set_margin(self, val): pass def set_show_margin(self, val): pass class SourceTagTable(gtk.TextTagTable): def __init__(self): gtk.TextTagTable.__init__(self) class SourceBuffer(gtk.TextBuffer): def __init__(self, table=None): gtk.TextBuffer.__init__(self, table=table) def begin_not_undoable_action(self): pass def end_not_undoable_action(self): pass gquilt-0.25/gquilt_pkg/dialogue.py0000664000076400007640000003361511507277272017572 0ustar peterpeter00000000000000### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import gtk, os from gquilt_pkg import icons, cmd_result, gutils, ws_event main_window = None def init(window): global main_window main_window = window class BusyIndicator: def __init__(self): pass def show_busy(self): if self.window: self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) while gtk.events_pending(): gtk.main_iteration() def unshow_busy(self): if self.window: self.window.set_cursor(None) class BusyIndicatorUser: def __init__(self, busy_indicator): if busy_indicator: self._busy_indicator = busy_indicator else: self._busy_indicator = main_window def show_busy(self): self._busy_indicator.show_busy() def unshow_busy(self): self._busy_indicator.unshow_busy() class Dialog(gtk.Dialog, BusyIndicator): def __init__(self, title=None, parent=None, flags=0, buttons=None): if not parent: parent = main_window gtk.Dialog.__init__(self, title=title, parent=parent, flags=flags, buttons=buttons) if not parent: self.set_icon_from_file(icons.APP_ICON_FILE) BusyIndicator.__init__(self) def report_any_problems(self, result): report_any_problems(result, self) def inform_user(self, msg): inform_user(msg, parent=self) def warn_user(self, msg): warn_user(msg, parent=self) def alert_user(self, msg): alert_user(msg, parent=self) class AmodalDialog(Dialog, ws_event.Listener): def __init__(self, title=None, parent=None, flags=0, buttons=None): flags &= ~gtk.DIALOG_MODAL Dialog.__init__(self, title=title, parent=parent, flags=flags, buttons=buttons) ws_event.Listener.__init__(self) self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_NORMAL) self.add_notification_cb(ws_event.CHANGE_WD, self._change_wd_cb) def _change_wd_cb(self, arg=None): self.destroy() class MessageDialog(gtk.MessageDialog): def __init__(self, parent=None, flags=0, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_NONE, message_format=None): if not parent: parent = main_window gtk.MessageDialog.__init__(self, parent=parent, flags=flags, type=type, buttons=buttons, message_format=message_format) class FileChooserDialog(gtk.FileChooserDialog): def __init__(self, title=None, parent=None, action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=None, backend=None): if not parent: parent = main_window gtk.FileChooserDialog.__init__(self, title=title, parent=parent, action=action, buttons=buttons, backend=backend) class SelectFromListDialog(Dialog): def __init__(self, olist=list(), prompt="Select from list:", parent=None): Dialog.__init__(self, 'gquilt: %s' % os.getcwd(), parent, gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) if parent is None: self.set_position(gtk.WIN_POS_MOUSE) else: self.set_position(gtk.WIN_POS_CENTER_ON_PARENT) self.cbox = gtk.combo_box_new_text() self.cbox.append_text(prompt) for i in olist: self.cbox.append_text(i) self.cbox.set_active(0) self.vbox.add(self.cbox) self.cbox.show() def make_selection(self): res = self.run() index = self.cbox.get_active() is_ok = index > 0 if is_ok: text = self.cbox.get_model()[index][0] else: text = '' self.destroy() return (is_ok, text) class QuestionDialog(Dialog): def __init__(self, title=None, parent=None, flags=0, buttons=None, question=""): Dialog.__init__(self, title=title, parent=parent, flags=flags, buttons=buttons) hbox = gtk.HBox() self.vbox.add(hbox) hbox.show() self.image = gtk.Image() self.image.set_from_stock(gtk.STOCK_DIALOG_QUESTION, gtk.ICON_SIZE_DIALOG) hbox.pack_start(self.image, expand=False) self.image.show() self.tview = gtk.TextView() self.tview.set_cursor_visible(False) self.tview.set_editable(False) self.tview.set_size_request(320, 80) self.tview.show() self.tview.get_buffer().set_text(question) hbox.add(gutils.wrap_in_scrolled_window(self.tview)) def set_question(self, question): self.tview.get_buffer().set_text(question) def ask_question(question, parent=None, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)): dialog = QuestionDialog(parent=parent, flags=gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, buttons=buttons, question=question) response = dialog.run() dialog.destroy() return response def ask_ok_cancel(question, parent=None): return ask_question(question, parent) == gtk.RESPONSE_OK def ask_yes_no(question, parent=None): buttons = (gtk.STOCK_NO, gtk.RESPONSE_NO, gtk.STOCK_YES, gtk.RESPONSE_YES) return ask_question(question, parent, buttons) == gtk.RESPONSE_YES def confirm_list_action(alist, question, parent=None): return ask_ok_cancel('\n'.join(alist + ['\n', question]), parent) RESPONSE_SKIP = 1 RESPONSE_SKIP_ALL = 2 RESPONSE_FORCE = 3 RESPONSE_REFRESH = 4 RESPONSE_RECOVER = 5 RESPONSE_RENAME = 6 RESPONSE_DISCARD = 7 RESPONSE_EDIT = 8 RESPONSE_MERGE = 9 def _form_question(result, clarification): if clarification: return os.linesep.join(list(result[1:]) + [clarification]) else: return os.linesep.join(result[1:]) def ask_force_refresh_or_cancel(result, clarification=None, parent=None): buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) if result.eflags & cmd_result.SUGGEST_REFRESH: buttons += ("_Refresh and Retry", RESPONSE_REFRESH) if result.eflags & cmd_result.SUGGEST_FORCE: buttons += ("_Force", RESPONSE_FORCE) question = _form_question(result, clarification) return ask_question(question, parent, buttons) def ask_force_or_cancel(result, clarification=None, parent=None): buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, "_Force", RESPONSE_FORCE) question = _form_question(result, clarification) return ask_question(question, parent, buttons) def ask_force_skip_or_cancel(result, clarification=None, parent=None): buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, "_Skip", RESPONSE_SKIP, "_Force", RESPONSE_FORCE) question = _form_question(result, clarification) return ask_question(question, parent, buttons) def ask_merge_discard_or_cancel(result, clarification=None, parent=None): buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) if result.eflags & cmd_result.SUGGEST_MERGE: buttons += ("_Merge", RESPONSE_MERGE) if result.eflags & cmd_result.SUGGEST_DISCARD: buttons += ("_Discard Changes", RESPONSE_DISCARD) question = _form_question(result, clarification) return ask_question(question, parent, buttons) def ask_recover_or_cancel(result, clarification=None, parent=None): buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, "_Recover", RESPONSE_RECOVER) question = _form_question(result, clarification) return ask_question(question, parent, buttons) def ask_edit_force_or_cancel(result, clarification=None, parent=None): buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) if result.eflags & cmd_result.SUGGEST_EDIT: buttons += ("_Edit", RESPONSE_EDIT) if result.eflags & cmd_result.SUGGEST_FORCE: buttons += ("_Force", RESPONSE_FORCE) question = _form_question(result, clarification) return ask_question(question, parent, buttons) def ask_rename_force_or_cancel(result, clarification=None, parent=None): buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) if result.eflags & cmd_result.SUGGEST_RENAME: buttons += ("_Rename", RESPONSE_RENAME) if result.eflags & cmd_result.SUGGEST_FORCE: buttons += ("_Force", RESPONSE_FORCE) question = _form_question(result, clarification) return ask_question(question, parent, buttons) def ask_rename_force_or_skip(result, clarification=None, parent=None): buttons = () if result.eflags & cmd_result.SUGGEST_RENAME: buttons += ("_Rename", RESPONSE_RENAME) if result.eflags & cmd_result.SUGGEST_FORCE: buttons += ("_Force", RESPONSE_FORCE) buttons += ("_Skip", RESPONSE_SKIP, "Skip _All", RESPONSE_SKIP_ALL) question = _form_question(result, clarification) return ask_question(question, parent, buttons) def ask_file_name(prompt, suggestion=None, existing=True, parent=None): if existing: mode = gtk.FILE_CHOOSER_ACTION_OPEN if suggestion and not os.path.exists(suggestion): suggestion = None else: mode = gtk.FILE_CHOOSER_ACTION_SAVE dialog = FileChooserDialog(prompt, parent, mode, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) dialog.set_default_response(gtk.RESPONSE_OK) if suggestion: if os.path.isdir(suggestion): dialog.set_current_folder(suggestion) else: dirname, basename = os.path.split(suggestion) if dirname: dialog.set_current_folder(dirname) if basename: dialog.set_current_name(basename) response = dialog.run() if response == gtk.RESPONSE_OK: new_file_name = dialog.get_filename() else: new_file_name = None dialog.destroy() return new_file_name def ask_dir_name(prompt, suggestion=None, existing=True, parent=None): if existing: mode = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER if suggestion and not os.path.exists(suggestion): suggestion = None else: mode = gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER dialog = FileChooserDialog(prompt, parent, mode, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) dialog.set_default_response(gtk.RESPONSE_OK) if suggestion: if os.path.isdir(suggestion): dialog.set_current_folder(suggestion) else: dirname = os.path.dirname(suggestion) if dirname: dialog.set_current_folder(dirname) response = dialog.run() if response == gtk.RESPONSE_OK: new_dir_name = dialog.get_filename() else: new_dir_name = None dialog.destroy() return new_dir_name def inform_user(msg, parent=None, problem_type=gtk.MESSAGE_INFO): dialog = MessageDialog(parent=parent, flags=gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, type=problem_type, buttons=gtk.BUTTONS_CLOSE, message_format=msg) dialog.run() dialog.destroy() def warn_user(msg, parent=None): inform_user(msg, parent=parent, problem_type=gtk.MESSAGE_WARNING) def alert_user(msg, parent=None): inform_user(msg, parent=parent, problem_type=gtk.MESSAGE_ERROR) def report_any_problems(result, parent=None): if cmd_result.is_ok(result[0]): return elif cmd_result.is_error(result[0]): problem_type = gtk.MESSAGE_ERROR else: problem_type = gtk.MESSAGE_WARNING inform_user(os.linesep.join(result[1:]), parent, problem_type) _REPORT_REQUEST_MSG = \ ''' Please report this problem by either: submitting a bug report at or: e-mailing and including a copy of the details below this message. Thank you. ''' def report_exception(exc_data, parent=None): import traceback msg = ''.join(traceback.format_exception(exc_data[0], exc_data[1], exc_data[2])) dialog = MessageDialog(parent=parent, flags=gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_CLOSE, message_format=_REPORT_REQUEST_MSG) dialog.set_title('gquilt: Unexpected Exception') dialog.format_secondary_text(msg) dialog.run() dialog.destroy() class CancelOKDialog(Dialog): def __init__(self, title=None, parent=None): flags = gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK) Dialog.__init__(self, title, parent, flags, buttons) class ReadTextDialog(CancelOKDialog): def __init__(self, title=None, prompt=None, suggestion="", parent=None): CancelOKDialog.__init__(self, title, parent) self.hbox = gtk.HBox() self.vbox.add(self.hbox) self.hbox.show() if prompt: self.hbox.pack_start(gtk.Label(prompt), fill=False, expand=False) self.entry = gtk.Entry() self.entry.set_width_chars(32) self.entry.set_text(suggestion) self.hbox.pack_start(self.entry) self.show_all() def get_modified_string(title, prompt, string): dialog = ReadTextDialog(title, prompt, string) if dialog.run() == gtk.RESPONSE_OK: string = dialog.entry.get_text() dialog.destroy() return string gquilt-0.25/gquilt_pkg/gutils.py0000664000076400007640000001477611507472527017317 0ustar peterpeter00000000000000### Copyright (C) 2007 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import gtk def get_gtk_window(widget): gtk_window = widget while True: temp = gtk_window.get_parent() if temp: gtk_window = temp else: break return gtk_window def pygtk_version_ge(version): for index in range(len(version)): if gtk.pygtk_version[index] > version[index]: return True elif gtk.pygtk_version[index] < version[index]: return False return True if pygtk_version_ge((2, 12)): def set_widget_tooltip_text(widget, text): widget.set_tooltip_text(text) else: tooltips = gtk.Tooltips() tooltips.enable() def set_widget_tooltip_text(widget, text): tooltips.set_tip(widget, text) def wrap_in_scrolled_window(widget, policy=(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC), with_frame=True, label=None): scrw = gtk.ScrolledWindow() scrw.set_policy(policy[0], policy[1]) scrw.add(widget) if with_frame: frame = gtk.Frame(label) frame.add(scrw) frame.show_all() return frame else: scrw.show_all() return scrw _KEYVAL_UP_ARROW = gtk.gdk.keyval_from_name('Up') _KEYVAL_DOWN_ARROW = gtk.gdk.keyval_from_name('Down') class EntryWithHistory(gtk.Entry): def __init__(self, max_chars=0): gtk.Entry.__init__(self, max_chars) self._history_list = [] self._history_index = 0 self._history_len = 0 self._saved_text = '' self._key_press_cb_id = self.connect("key_press_event", self._key_press_cb) def _key_press_cb(self, widget, event): if event.keyval in [_KEYVAL_UP_ARROW, _KEYVAL_DOWN_ARROW]: if event.keyval == _KEYVAL_UP_ARROW: if self._history_index < self._history_len: if self._history_index == 0: self._saved_text = self.get_text() self._history_index += 1 self.set_text(self._history_list[-self._history_index]) self.set_position(-1) elif event.keyval == _KEYVAL_DOWN_ARROW: if self._history_index > 0: self._history_index -= 1 if self._history_index > 0: self.set_text(self._history_list[-self._history_index]) else: self.set_text(self._saved_text) self.set_position(-1) return True else: return False def clear_to_history(self): self._history_index = 0 # beware the empty command string text = self.get_text().rstrip() self.set_text("") # don't save empty entries or ones that start with white space if not text or text[0] in [' ', '\t']: return # no adjacent duplicate entries allowed if (self._history_len == 0) or (text != self._history_list[-1]): self._history_list.append(text) self._history_len = len(self._history_list) def get_text_and_clear_to_history(self): text = self.get_text().rstrip() self.clear_to_history() return text class LabelledEntryWithHistory(gtk.HBox): def __init__(self, label, max_chars=0): gtk.HBox.__init__(self) self.entry = EntryWithHistory(max_chars) self.label = gtk.Label(label) self.pack_start(self.label, expand=False) self.pack_start(self.entry) def get_text_and_clear_to_history(self): return self.entry.get_text_and_clear_to_history() def set_label(self, text): self.label.set_text(text) class ActionButton(gtk.Button): def __init__(self, action, use_underline=True): label = action.get_property("label") stock_id = action.get_property("stock-id") if label is "": # Empty (NB not None) label means use image only gtk.Button.__init__(self, use_underline=use_underline) image = gtk.Image() image.set_from_stock(stock_id, gtk.ICON_SIZE_BUTTON) self.add(image) else: gtk.Button.__init__(self, stock=stock_id, label=label, use_underline=use_underline) set_widget_tooltip_text(self, action.get_property("tooltip")) action.connect_proxy(self) class ActionButtonList: def __init__(self, action_group_list, action_name_list=None, use_underline=True): self.list = [] self.dict = {} if action_name_list: for a_name in action_name_list: for a_group in action_group_list: action = a_group.get_action(a_name) if action: button = ActionButton(action, use_underline) self.list.append(button) self.dict[a_name] = button break else: for a_group in action_group_list: for action in a_group.list_actions(): button = ActionButton(action, use_underline) self.list.append(button) self.dict[action.get_name()] = button class ActionHButtonBox(gtk.HBox): def __init__(self, action_group_list, action_name_list=None, use_underline=True, expand=True, fill=True, padding=0): gtk.HBox.__init__(self) self.button_list = ActionButtonList(action_group_list, action_name_list, use_underline) for button in self.button_list.list: self.pack_start(button, expand, fill, padding) def _ui_manager_connect_proxy(_ui_mgr, action, widget): tooltip = action.get_property('tooltip') if isinstance(widget, gtk.MenuItem) and tooltip: widget.set_tooltip_text(tooltip) class UIManager(gtk.UIManager): def __init__(self): gtk.UIManager.__init__(self) self.connect('connect-proxy', _ui_manager_connect_proxy) gquilt-0.25/gquilt_pkg/utils.py0000664000076400007640000001352411507264713017132 0ustar peterpeter00000000000000### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os import os.path import gobject import subprocess import signal from gquilt_pkg import urlops from gquilt_pkg import cmd_result def path_relative_to_dir(fdir, path): if not os.path.isdir(fdir): return None if fdir == path: return os.curdir lcwd = len(fdir) if len(path) <= lcwd + 1 or fdir != path[0:lcwd] or path[lcwd] != os.sep: return None return path[lcwd + 1:] def path_relative_to_playground(path): return path_relative_to_dir(os.getcwd(), path) HOME = os.path.expanduser("~") def path_rel_home(path): """Return the given path as a path relative to user's home directory.""" if urlops.parse_url(path).scheme: return path path = os.path.abspath(path) len_home = len(HOME) if len(path) >= len_home and HOME == path[:len_home]: path = "~" + path[len_home:] return path def cwd_rel_home(): """Return path of current working directory relative to user's home directory. """ return path_rel_home(os.getcwd()) def file_list_to_string(file_list): """Return the given list of file names as a single string: - using a single space as a separator, and - placing double quotes around file names that contain spaces. """ mod_file_list = [] for fname in file_list: if fname.count(' ') == 0: mod_file_list.append(fname) else: mod_file_list.append('"%s"' % fname) return ' '.join(mod_file_list) def string_to_file_list(string): """Return a list of the file names in the given string: - assuming names are separated by spaces, and - file names that contain spaces are inside double quotes. """ if string.count('"') == 0: return string.split() file_list = [] index = 0 lqi = string.rfind('"') while index < lqi: qib = string.find('"', index) file_list += string[index:qib].split() index = string.find('"', qib + 1) + 1 file_list.append(string[qib+1:index-1]) if index < len(string): file_list += string[index:].split() return file_list # handle the fact os.path.samefile is not available on all operating systems def samefile(filename1, filename2): """Return whether the given paths refer to the same file or not.""" try: return os.path.samefile(filename1, filename2) except AttributeError: return os.path.abspath(filename1) == os.path.abspath(filename2) def run_cmd(cmd, input_text=None): """Run the given command and report the outcome as a cmd_result tuple. If input_text is not None pas it to the command as standard input. """ try: oldterm = os.environ['TERM'] os.environ['TERM'] = "dumb" except LookupError: oldterm = None is_posix = os.name == 'posix' if is_posix: savedsh = signal.getsignal(signal.SIGPIPE) signal.signal(signal.SIGPIPE, signal.SIG_DFL) sub = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, close_fds=is_posix, bufsize=-1) outd, errd = sub.communicate(input_text) if is_posix: signal.signal(signal.SIGPIPE, savedsh) if oldterm: os.environ['TERM'] = oldterm return cmd_result.Result(sub.returncode, outd, errd) def _wait_for_bgnd_cmd_timeout(pid): """Callback to clean up after background tasks complete""" try: if os.name == 'nt' or os.name == 'dos': rpid, _ = os.waitpid(pid, 0) else: rpid, _ = os.waitpid(pid, os.WNOHANG) return rpid != pid except OSError: return False def run_cmd_in_bgnd(cmd): """Run the given command in the background and poll for its exit using _wait_for_bgnd_timeout() as a callback. """ if not cmd: return False pid = subprocess.Popen(string_to_file_list(cmd)).pid if not pid: return False gobject.timeout_add(2000, _wait_for_bgnd_cmd_timeout, pid) return True if os.name == 'nt' or os.name == 'dos': def _which(cmd): """Return the path of the executable for the given command""" for dirpath in os.environ['PATH'].split(os.pathsep): potential_path = os.path.join(dirpath, cmd) if os.path.isfile(potential_path) and \ os.access(potential_path, os.X_OK): return potential_path return None NT_EXTS = ['.bat', '.bin', '.exe'] def which(cmd): """Return the path of the executable for the given command""" path = _which(cmd) if path: return path _, ext = os.path.splitext(cmd) if ext in NT_EXTS: return None for ext in NT_EXTS: path = _which(cmd + ext) if path is not None: return path return None else: def which(cmd): """Return the path of the executable for the given command""" for dirpath in os.environ['PATH'].split(os.pathsep): potential_path = os.path.join(dirpath, cmd) if os.path.isfile(potential_path) and \ os.access(potential_path, os.X_OK): return potential_path return None gquilt-0.25/gquilt_pkg/text_edit.py0000664000076400007640000003510011476545014017756 0ustar peterpeter00000000000000### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import os, gtk, gquilt_pkg.sourceview, pango, gobject from gquilt_pkg import dialogue, ifce, utils, gutils, config def _edit_files_extern(filelist, edstr=config.DEFAULT_EDITOR): if not edstr.split()[0] in config.EDITORS_THAT_NEED_A_TERMINAL: cmd = '%s %s' % (edstr, utils.file_list_to_string(filelist)) else: if config.DEFAULT_TERMINAL == "gnome-terminal": flag = '-x' else: flag = '-e' cmd = '%s %s %s %s' % (config.DEFAULT_TERMINAL, flag, edstr, utils.file_list_to_string(filelist)) return utils.run_cmd_in_bgnd(cmd) def edit_files_extern(file_list): ed_assigns = config.assign_extern_editors(file_list) for edstr in list(ed_assigns.keys()): _edit_files_extern(ed_assigns[edstr], edstr) def peruse_files_extern(file_list): ed_assigns = config.assign_extern_perusers(file_list) for edstr in list(ed_assigns.keys()): _edit_files_extern(ed_assigns[edstr], edstr) class SummaryBuffer(gquilt_pkg.sourceview.SourceBuffer): def __init__(self, table=None): if not table: table = gquilt_pkg.sourceview.SourceTagTable() gquilt_pkg.sourceview.SourceBuffer.__init__(self, table=table) self.action_group = gtk.ActionGroup("summary") self.action_group.add_actions( [ ("summary_ack", None, "_Ack", None, "Insert Acked-by tag at cursor position", self._insert_ack_acb), ("summary_sign_off", None, "_Sign Off", None, "Insert Signed-off-by tag at cursor position", self._insert_sign_off_acb), ("summary_author", None, "A_uthor", None, "Insert Author tag at cursor position", self._insert_author_acb), ]) def _insert_sign_off_acb(self, _action=None): data = ifce.ifce.get_author_name_and_email() self.insert_at_cursor("Signed-off-by: %s\n" % data) def _insert_ack_acb(self, _action=None): data = ifce.ifce.get_author_name_and_email() self.insert_at_cursor("Acked-by: %s\n" % data) def _insert_author_acb(self, _action=None): data = ifce.ifce.get_author_name_and_email() self.insert_at_cursor("Author: %s\n" % data) class SummaryView(gquilt_pkg.sourceview.SourceView): def __init__(self, text_buffer=None, table=None): if not text_buffer: text_buffer = SummaryBuffer(table) gquilt_pkg.sourceview.SourceView.__init__(self, text_buffer) fdesc = pango.FontDescription("mono, 10") self.modify_font(fdesc) self.set_margin(72) self.set_show_margin(True) context = self.get_pango_context() metrics = context.get_metrics(fdesc) width = pango.PIXELS(metrics.get_approximate_char_width() * 81) x, y = self.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT, width, width / 3) self.set_size_request(x, y) self.set_cursor_visible(True) self.set_editable(True) self.ui_manager = gutils.UIManager() self.ui_manager.insert_action_group(text_buffer.action_group, -1) def get_action(self, action_name): for action_group in self.ui_manager.get_action_groups(): action = action_group.get_action(action_name) if action: return action return None def get_msg(self): text_buffer = self.get_buffer() return text_buffer.get_text(text_buffer.get_start_iter(), text_buffer.get_end_iter()) class ChangeSummaryBuffer(SummaryBuffer): def __init__(self, table=None, auto_save=True): if not table: table = gquilt_pkg.sourceview.SourceTagTable() SummaryBuffer.__init__(self, table=table) self._save_interval = 1000 # milliseconds self._save_file_name = ifce.ifce.get_default_commit_save_file() if not os.path.exists(self._save_file_name): self.save_summary(content="") self.action_group.add_actions( [ ("menu_summary", None, "_Summary"), ("change_summary_save", gtk.STOCK_SAVE, "_Save", "", "Save commit summary", self._save_summary_acb), ("change_summary_save_as", gtk.STOCK_SAVE_AS, "S_ave as", "", "Save commit summary to a file", self.save_summary_as), ("change_summary_load", gtk.STOCK_REVERT_TO_SAVED, "_Revert", "", "Load summary from saved file", self._load_summary_acb), ("change_summary_load_from", gtk.STOCK_REVERT_TO_SAVED, "_Load from", "", "Load summary from a file", self.load_summary_from), ("change_summary_insert_from", gtk.STOCK_PASTE, "_Insert from", "", "Insert contents of a file at cursor position", self.insert_summary_from), ]) self.save_toggle_action = gtk.ToggleAction( "summary_toggle_auto_save", "Auto Sa_ve", "Automatically/periodically save summary to file", gtk.STOCK_SAVE ) self.save_toggle_action.connect("toggled", self._toggle_auto_save_acb) self.save_toggle_action.set_active(auto_save) self.action_group.add_action(self.save_toggle_action) # Load the saved content before (possibly) turning auto save on or # contents of saved file could be wiped out before it's loaded self.load_summary(already_checked=True) self._toggle_auto_save_acb() def save_summary(self, file_name=None, content=None): if not file_name: file_name = self._save_file_name try: save_file = open(file_name, 'w') if content is None: save_file.write(self.get_text(self.get_start_iter(), self.get_end_iter())) else: save_file.write(content) save_file.close() self._save_file_name = file_name self.set_modified(False) except IOError: dialogue.alert_user('Save failed!') def _save_summary_acb(self, _action=None): self.save_summary() def save_summary_as(self, _action=None): fname = dialogue.ask_file_name("Enter file name", existing=False, suggestion=self._save_file_name) if fname and os.path.exists(fname) and not utils.samefile(fname, self._save_file_name): if not utils.samefile(fname, ifce.ifce.get_default_commit_save_file()): if not dialogue.ask_ok_cancel(os.linesep.join([fname, "\nFile exists. Overwrite?"])): return self.save_summary(file_name=fname) def _ok_to_overwrite_summary(self): if self.get_char_count(): return dialogue.ask_ok_cancel("Buffer contents will be destroyed. Continue?") return True def load_summary(self, file_name=None, already_checked=False): if not already_checked and not self._ok_to_overwrite_summary(): return if not file_name: file_name = self._save_file_name try: save_file = open(file_name, 'r') self.set_text(save_file.read()) save_file.close() self._save_file_name = file_name self.set_modified(False) except IOError: dialogue.alert_user('Load from file failed!') def _load_summary_acb(self, _action=None): self.load_summary() def load_summary_from(self, _action=None): if not self._ok_to_overwrite_summary(): return fname = dialogue.ask_file_name("Enter file name", existing=True) self.load_summary(file_name=fname, already_checked=True) def insert_summary_from(self, _action=None): file_name = dialogue.ask_file_name("Enter file name", existing=True) try: save_file = open(file_name, 'r') self.insert_at_cursor(save_file.read()) save_file.close() self.set_modified(True) except IOError: dialogue.alert_user('Insert at cursor from file failed!') def get_auto_save(self): return self.save_toggle_action.get_active() def set_auto_save(self, active=True): self.save_toggle_action.set_active(active) def get_auto_save_interval(self): return self._save_interval def set_auto_save_inerval(self, interval): self._save_interval = interval def do_auto_save(self): if self.get_modified(): self.save_summary() return self.get_auto_save() def _toggle_auto_save_acb(self, _action=None): if self.get_auto_save(): gobject.timeout_add(self._save_interval, self.do_auto_save) def finish_up(self, clear_save=False): if self.get_auto_save(): self.set_auto_save(False) self.do_auto_save() if clear_save: self.save_summary(file_name=ifce.ifce.get_default_commit_save_file(), content="") CHANGE_SUMMARY_UI_DESCR = \ ''' ''' class ChangeSummaryView(SummaryView): def __init__(self, text_buffer=None, auto_save=True, table=None): if not text_buffer: text_buffer = ChangeSummaryBuffer(table, auto_save) SummaryView.__init__(self, text_buffer, table) self.change_summary_merge_id = self.ui_manager.add_ui_from_string(CHANGE_SUMMARY_UI_DESCR) class NewPatchSummaryBuffer(SummaryBuffer): def __init__(self, table=None): if not table: table = gquilt_pkg.sourceview.SourceTagTable() SummaryBuffer.__init__(self, table=table) self.action_group.add_actions( [ ("menu_summary", None, "_Description"), ("patch_summary_insert_from", gtk.STOCK_PASTE, "_Insert from", "", "Insert contents of a file at cursor position", self._insert_summary_from_acb), ]) def _insert_summary_from_acb(self, _action=None): file_name = dialogue.ask_file_name("Enter file name", existing=True) if file_name is not None: try: save_file = open(file_name, 'r') self.insert_at_cursor(save_file.read()) save_file.close() self.set_modified(True) except IOError: dialogue.alert_user('Insert at cursor from file failed!') class PatchSummaryBuffer(NewPatchSummaryBuffer): def __init__(self, get_summary, set_summary, patch=None, table=None): self.patch = patch self._set_summary = set_summary self._get_summary = get_summary if not table: table = gquilt_pkg.sourceview.SourceTagTable() NewPatchSummaryBuffer.__init__(self, table=table) self.action_group.add_actions( [ ("patch_summary_save", gtk.STOCK_SAVE, "_Save", "", "Save commit summary", self._save_summary_acb), ("patch_summary_load", gtk.STOCK_REVERT_TO_SAVED, "_Reload", "", "Load summary from saved file", self._load_summary_acb), ]) def _save_summary_acb(self, _action=None): text = self.get_text(self.get_start_iter(), self.get_end_iter()) res, sout, serr = self._set_summary(self.patch, text) if res: dialogue.alert_user(os.linesep.join([sout, serr])) else: self.set_modified(False) def _ok_to_overwrite_summary(self): if self.get_char_count() and self.get_modified(): return dialogue.ask_ok_cancel("Buffer contents will be destroyed. Continue?") return True def load_summary(self): res, text, serr = self._get_summary(self.patch) if res: dialogue.alert_user(os.linesep.join([text, serr])) else: self.set_text(text) self.set_modified(False) def _load_summary_acb(self, _action=None): if self._ok_to_overwrite_summary(): self.load_summary() NEW_PATCH_SUMMARY_UI_DESCR = \ ''' ''' class NewPatchSummaryView(SummaryView): def __init__(self, text_buffer=None, table=None): if not text_buffer: text_buffer = NewPatchSummaryBuffer(table) SummaryView.__init__(self, text_buffer, table) self.patch_summary_merge_id = self.ui_manager.add_ui_from_string(NEW_PATCH_SUMMARY_UI_DESCR) PATCH_SUMMARY_UI_DESCR = \ ''' ''' class PatchSummaryView(NewPatchSummaryView): def __init__(self, get_summary, set_summary, patch=None, table=None): text_buffer = PatchSummaryBuffer(get_summary, set_summary, patch, table) NewPatchSummaryView.__init__(self, text_buffer, table) self.patch_summary_merge_id = self.ui_manager.add_ui_from_string(PATCH_SUMMARY_UI_DESCR) action = text_buffer.action_group.get_action("patch_summary_save") self.save_button = gutils.ActionButton(action, use_underline=False) def load_summary(self): self.get_buffer().load_summary() gquilt-0.25/gquilt_pkg/table.py0000664000076400007640000001256611476555270017074 0ustar peterpeter00000000000000### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ Provide generic enhancements to Textview widgets primarily to create them from templates and allow easier access to named contents. """ import gtk, collections from gquilt_pkg import gutils, tlview, icons class Model(tlview.ListStore): def __init__(self, descr): tlview.ListStore.__init__(self, descr) ALWAYS_ON = 'table_always_on' MODIFIED = 'table_modified' NOT_MODIFIED = 'table_not_modified' SELECTION = 'table_selection' NO_SELECTION = 'table_no_selection' UNIQUE_SELECTION = 'table_unique_selection' TABLE_STATES = \ [ALWAYS_ON, MODIFIED, NOT_MODIFIED, SELECTION, NO_SELECTION, UNIQUE_SELECTION] class Table(gtk.VBox): def __init__(self, model_descr, table_descr, size_req=None): gtk.VBox.__init__(self) self.model = Model(model_descr) self.view = tlview.View(table_descr, self.model) self.seln = self.view.get_selection() if size_req: self.view.set_size_request(*size_req) self.pack_start(gutils.wrap_in_scrolled_window(self.view)) self.action_groups = {} for key in TABLE_STATES: self.action_groups[key] = gtk.ActionGroup(key) self.action_groups[ALWAYS_ON].add_actions( [ ('table_add_row', gtk.STOCK_ADD, '_Add', None, 'Add a new entry to the table', self._add_row_acb), ]) self.action_groups[MODIFIED].add_actions( [ ('table_undo_changes', gtk.STOCK_UNDO, '_Undo', None, 'Undo unapplied changes', self._undo_changes_acb), ('table_apply_changes', gtk.STOCK_APPLY, '_Apply', None, 'Apply outstanding changes', self._apply_changes_acb), ]) self.action_groups[SELECTION].add_actions( [ ('table_delete_selection', gtk.STOCK_DELETE, '_Delete', None, 'Delete selected row(s)', self._delete_selection_acb), ('table_insert_row', icons.STOCK_INSERT, '_Insert', None, 'Insert a new entry before the selected row(s)', self._insert_row_acb), ]) self._modified = False self.model.connect('row-inserted', self._row_inserted_cb) self.seln.connect('changed', self._selection_changed_cb) self.view.register_modification_callback(self._set_modified, True) self.seln.unselect_all() self._selection_changed_cb(self.seln) def _set_modified(self, val): self._modified = val self.action_groups[MODIFIED].set_sensitive(val) self.action_groups[NOT_MODIFIED].set_sensitive(not val) def _fetch_contents(self): pass # define in child def set_contents(self): self.model.set_contents(self._fetch_contents()) self._set_modified(False) def get_contents(self): return self.model.get_contents() def apply_changes(self): pass # define in child def _row_inserted_cb(self, model, path, model_iter): self._set_modified(True) def _selection_changed_cb(self, selection): rows = selection.count_selected_rows() self.action_groups[SELECTION].set_sensitive(rows > 0) self.action_groups[NO_SELECTION].set_sensitive(rows == 0) self.action_groups[UNIQUE_SELECTION].set_sensitive(rows == 1) def _undo_changes_acb(self, _action=None): self.set_contents() def _apply_changes_acb(self, _action=None): self.apply_changes() def _add_row_acb(self, _action=None): model_iter = self.model.append(None) self.view.get_selection().select_iter(model_iter) return def _delete_selection_acb(self, _action=None): model, paths = self.seln.get_selected_rows() iters = [] for path in paths: iters.append(model.get_iter(path)) for model_iter in iters: model.remove(model_iter) def _insert_row_acb(self, _action=None): model, paths = self.seln.get_selected_rows() if not paths: return model_iter = self.model.insert_before(model.get_iter(paths[0]), None) self.view.get_selection().select_iter(model_iter) return def get_selected_data(self, columns=None): store, selected_rows = self.seln.get_selected_rows() if not columns: columns = list(range(store.get_n_columns())) result = [] for row in selected_rows: model_iter = store.get_iter(row) assert model_iter is not None result.append(store.get_values(model_iter, columns)) return result def get_selected_data_by_label(self, labels): columns = self.model.get_cols(labels) return self.get_selected_data(columns) gquilt-0.25/gquilt_pkg/console.py0000664000076400007640000000772211507216214017430 0ustar peterpeter00000000000000### Copyright (C) 2010 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import gtk, pango, time, os from gquilt_pkg import cmd_result, utils from gquilt_pkg import dialogue class Console(gtk.Frame): def __init__(self): gtk.Frame.__init__(self) self.view = gtk.TextView() self.text = self.view.get_buffer() self.dat_tag = self.text.create_tag("DAT", weight=pango.WEIGHT_BOLD) self.stderr_tag = self.text.create_tag("STDERR", foreground="red") self.stdout_tag = self.text.create_tag("STDOUT", foreground="black") self.cmd_tag = self.text.create_tag("CMD", foreground="black") self.eobuf = self.text.create_mark("eobuf", self.text.get_end_iter(), False) scr_win = gtk.ScrolledWindow() scr_win.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scr_win.add(self.view) self.add(scr_win) self.view.set_cursor_visible(False) self.view.set_editable(False) self.show_all() self._finish_entry() def _append_to_console_tagged(self, msg, tag): self.text.insert_with_tags(self.text.get_end_iter(), msg, tag) self.view.scroll_to_mark(self.eobuf, 0.001) def _append_dat(self): self._append_to_console_tagged(time.strftime("%Y-%m-%d %H:%M:%S") + ": ", self.dat_tag) def _finish_entry(self): self._append_to_console_tagged("%", self.dat_tag) def _log_entry(self, entry, tag): entry = entry.rstrip() if entry: self._append_dat() self._append_to_console_tagged(entry, tag) self._append_newline() self._finish_entry() def log_entry(self, entry): self._log_entry(entry, self.cmd_tag) def log_entry_bold(self, entry): self._log_entry(entry, self.dat_tag) def _append_with_newline(self, entry, tag): entry = entry.rstrip() if entry: self._append_to_console_tagged(entry, tag) self._append_newline() def _append_stdout(self, msg): self._append_with_newline(msg, self.stdout_tag) def _append_stderr(self, msg): self._append_with_newline(msg, self.stderr_tag) def append_stderr(self, msg): self._append_stderr(msg) self._finish_entry() def _append_newline(self): self._append_to_console_tagged(os.linesep, self.stdout_tag) def exec_cmd(self, cmd): self._append_dat() self._append_with_newline(cmd, self.cmd_tag) while gtk.events_pending(): gtk.main_iteration() res, sout, err = utils.run_cmd(cmd) self._append_stdout(sout) self._append_stderr(err) self._finish_entry() return cmd_result.Result(res, sout, err) def exec_cmd_with_alert(self, cmd): res, sout, err = self.exec_cmd(cmd) if res != 0: if len(err) > 0: dialogue.inform_user(err) else: dialogue.inform_user(sout) LOG = Console() # run a command and log the result to the provided console def exec_console_cmd(cmd, error=cmd_result.ERROR): if LOG is not None: res, sout, serr = LOG.exec_cmd(cmd) else: res, sout, serr = utils.run_cmd(cmd) if res != 0: return cmd_result.Result(error, sout, serr) else: return cmd_result.Result(cmd_result.OK, sout, serr) gquilt-0.25/gquilt_pkg/cmd_result.py0000664000076400007640000000527111507474430020132 0ustar peterpeter00000000000000### Copyright (C) 2007 Peter Williams ### 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; version 2 of the License only. ### 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, write to the Free Software ### Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ''' External command return values ''' import collections Result = collections.namedtuple('Result', ['eflags', 'stdout', 'stderr']) OK = 0 WARNING = 1 ERROR = 2 SUGGEST_FORCE = 4 SUGGEST_REFRESH = 8 SUGGEST_RECOVER = 16 SUGGEST_RENAME = 32 SUGGEST_DISCARD = 64 SUGGEST_EDIT = 128 SUGGEST_MERGE = 256 SUGGEST_ALL = (SUGGEST_MERGE * 2) - 1 - WARNING|ERROR SUGGEST_FORCE_OR_REFRESH = SUGGEST_FORCE | SUGGEST_REFRESH WARNING_SUGGEST_FORCE = WARNING | SUGGEST_FORCE ERROR_SUGGEST_FORCE = ERROR | SUGGEST_FORCE WARNING_SUGGEST_REFRESH = WARNING | SUGGEST_REFRESH ERROR_SUGGEST_REFRESH = ERROR | SUGGEST_REFRESH WARNING_SUGGEST_FORCE_OR_REFRESH = WARNING | SUGGEST_FORCE_OR_REFRESH ERROR_SUGGEST_FORCE_OR_REFRESH = ERROR | SUGGEST_FORCE_OR_REFRESH SUGGEST_FORCE_OR_RENAME = SUGGEST_FORCE | SUGGEST_RENAME SUGGEST_MERGE_OR_DISCARD = SUGGEST_MERGE | SUGGEST_DISCARD BASIC_VALUES_MASK = OK | WARNING | ERROR def basic_value(res): if type(res) is Result: return res.eflags & BASIC_VALUES_MASK else: return res & BASIC_VALUES_MASK def is_ok(res): return basic_value(res) == OK def is_warning(res): return basic_value(res) == WARNING def is_less_than_warning(res): return basic_value(res) < WARNING def is_error(res): return basic_value(res) == ERROR def is_less_than_error(res): return basic_value(res) < ERROR def suggests_force(res): if type(res) in [list, tuple]: res = Result._make(res) if type(res) is Result: return (res.eflags & SUGGEST_FORCE) == SUGGEST_FORCE else: return (res & SUGGEST_FORCE) == SUGGEST_FORCE def map_cmd_result(result, ignore_err_re=None): if type(result) in [list, tuple]: result = Result._make(result) if result.eflags == 0: if result.stdout and not (ignore_err_re and ignore_err_re.match(result.stdout)): outres = WARNING else: outres = OK else: outres = ERROR return Result(outres, result.stdout, result.stderr) gquilt-0.25/gquilt_pkg/qbsfe.sh0000664000076400007640000000116411413455575017055 0ustar peterpeter00000000000000#! /bin/bash # This script is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. if [ $# -lt 2 ] then echo "Usage: qbsfe.sh [ ...]" >&2 exit 1 fi if [ -z "$QUILTRC" ] then for QUILTRC in $HOME/.quiltrc /etc/quilt.quiltrc; do [ -e $QUILTRC ] && break done export QUILTRC fi QBSFE_PATCHFNS=$1 shift # Read in library functions if ! [ -r $QBSFE_PATCHFNS ] then echo "Cannot read library \"$QBSFE_PATCHFNS\"" >&2 exit 1 fi . $QBSFE_PATCHFNS QBSFE_CMD=$1 shift $QBSFE_CMD "$@"