dreampie-1.1.1/0000755000175000017500000000000011443646025011753 5ustar noamnoamdreampie-1.1.1/share/0000755000175000017500000000000011443646025013055 5ustar noamnoamdreampie-1.1.1/share/pixmaps/0000755000175000017500000000000011443646025014536 5ustar noamnoamdreampie-1.1.1/share/pixmaps/dreampie.png0000644000175000017500000000303711300026044017016 0ustar noamnoamPNG  IHDR00WsBIT|d pHYsItEXtSoftwarewww.inkscape.org<IDATh{LSgSKE/2(lQ}x-^XMđ%3ceLe[$^&Kbm:Q36 BmUG RX ҘI~;E^b$8 8G4hӀqp4]Dg0ܭl2L?<An4Ԫ /ؚk-LjJ~R)f3~(ƠL^OHdgh5Pc_sZeK܉N䵤e\iHG 3sLÑH$?BvCC1?, "gvR F]ƌ˜9Q*@vv6yyy("W-[޽c1.q<}U3ҬLv2O0y3hРG¥KYv-w :9ԫ!W`ܼO{3P{cQܬCp-zoB@ӱd2ΡT*5fo< Ix{H.s`0PUUE~NNHDb-l[4(>~gƉ2+ٰa(XTT$-5b}(vRܷo#q".[LD+msfS]ӧOocTPXXHZZZ=(=FlָiD}vׯ{D<@~&ۛ8Ol[ TvA"a֬ݮb4ĬW]!(HA@@@ {Vjjj D"ƬzŌ%%%,-8N`[6 (U0e;v8rv 54ܪ %9$BBBjhZ㉈rFWi5@9+S7]~<޽{m-]QݎuQSS/ۈޅujZ !d ݽL ܞOAx^?S' aJ`z))/Sh^F,Y¤ICc{Q($v2A,ZũT*F$$l'OzjGuvbSI^թʚAff&VB~ʕ+ٸq#nnݛaqoJ._A:7 Oy·\;ȑAhhh5W^eܵNGԌDRU|?qu@L("9jeLQ aDX,fz=j Tehnyw{ >Q]ҾL;Tz[OPO  @phBp"=5?Dl|+)qyC_峊 q?:Kei8 8G4hG>3IENDB`dreampie-1.1.1/share/pixmaps/dreampie.svg0000644000175000017500000001476211300026131017035 0ustar noamnoam image/svg+xml dreampie-1.1.1/share/applications/0000755000175000017500000000000011443646025015543 5ustar noamnoamdreampie-1.1.1/share/applications/dreampie.desktop0000644000175000017500000000025611274303173020723 0ustar noamnoam[Desktop Entry] Name=DreamPie Comment=An interactive Python shell Exec=dreampie Icon=dreampie Terminal=false Type=Application Categories=GTK;Development; StartupNotify=true dreampie-1.1.1/share/dreampie/0000755000175000017500000000000011443646025014643 5ustar noamnoamdreampie-1.1.1/share/dreampie/subp_main.py0000644000175000017500000000363711376277545017217 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . # This file is a script (not a module) run by the DreamPie GUI. # It expects one argument: the port to connect to. # It creates a package called dreampielib from subp-py2.zip or subp-py3.zip # (which are expected to be in the directory of __file__), # and runs dreampielib.subprocess.main(port). # This is a hack to solve bug #527630. Python2.5 ignores the PYTHONIOENCODING # environment variable, but we want to set the output encoding to utf-8 so that # unicode chars will be printed. So we disable automatic loading of site.py with # the -S flag, and call sys.setdefaultencoding before site.py has a chance of # doing anything else. import sys sys.setdefaultencoding('utf-8') import site from os.path import abspath, join, dirname def main(): port = int(sys.argv[1]) py_ver = sys.version_info[0] lib_name = abspath(join(dirname(__file__), 'subp-py%d' % py_ver)) sys.path.insert(0, lib_name) from dreampielib.subprocess import main as subprocess_main del sys.path[0] if sys.version_info[:2] == (3, 0): sys.stderr.write("Warning: DreamPie doesn't support Python 3.0. \n" "Please upgrade to Python 3.1.\n") subprocess_main(port) if __name__ == '__main__': main() dreampie-1.1.1/share/dreampie/dreampie.glade0000644000175000017500000026223511443154130017430 0ustar noamnoam DreamPie True True True _History True True _Load History... True Load history previously saved to a file and add it before the history the is currently displayed. True False True gtk-open _Save History True Save the history the is currently displayed to a file. True False True gtk-save Save History _As... True Save the history that is currently displayed to a file with a new name. True False True gtk-save-as _Discard History... True Discard the history that is currently displayed. True False True gtk-delete True True Search the history upwards History Up True True Search the history downwards History Down True True True Fold Last Unfolded Output True True Unfold Last Folded Output True True _0 recent0 True _1 recent1 True _2 recent2 True _3 recent3 True gtk-quit True True True True _Edit True True gtk-cut True False True True gtk-copy True False True True True False Copy only the code, without output or leading dots. Copy Code Only True gtk-paste True True True True True Show Completions True True Show Call Tip True True gtk-preferences True True True True _Shell True True True Execute the current command in the shell Execute Code True True Interrupt Subprocess True True Restart the subprocess, and start with a fresh one. Be warned that all your variables will be lost! Restart Subprocess True True Remove all previously computed results which are stored in the result history. Clear Result History True True Hel_p True True True Getting Started... True True Report a Problem... True True DreamPie Homepage... True gtk-about True True True False 0 True True True True automatic automatic True True False char True True True True automatic automatic False True 1 True 2 False 2 5 Getting Started with DreamPie center normal True close <span weight='bold' size='large' foreground='DodgerBlue'>Welcome to DreamPie!</span> True Some tips to help you get started quickly:<span size='xx-small'> </span>• Type your code in the lower pane of the window. To execute, press Ctrl+Enter. One-liners can be executed by simply pressing Enter; If you don't want them executed, press Space and then Enter.<span size='xx-small'> </span>• Use Ctrl+Up and Ctrl+Down to navigate between code segments you've already executed. You can write a few letters before pressing Ctrl+Up, and DreamPie will only search through code segments starting with those letters.<span size='xx-small'> </span>• Press Tab or Ctrl+Space to show a list of completions to the current expression. It will also complete file names!<span size='xx-small'> </span>• Your results are stored in variables named _0, _1, and so on.<span size='xx-small'> </span>• Type a function name and press the space key and DreamPie will automatically add parentheses for you!<span size='xx-small'> </span>• These commands, and some neat others, are also available from the menu.<span size='xx-small'> </span>• You can display these tips again by choosing Help→Getting Started from the menu. True False end 0 5 DreamPie Preferences normal False True 2 True True True 9 9 9 9 True 12 True Font and Size False tab True 9 9 9 9 True 3 True 3 True 0 Themes False 0 True True automatic automatic True True False True 1 True 3 True True True Copy Theme True gtk-copy False 0 True True True Delete Theme True gtk-delete False 1 False False 2 False 0 True False 1 True 3 True 0 Elements False 0 True 3 True True never True True False 0 True True never automatic True True False False 1 1 2 True False 3 True 3 True 0 none True 12 True Default True True False True True 0 Special: True True False True True 1 True 20 True True True #000000000000 2 True Foreground True label_item False 0 True 0 none True 12 True Default True True False True True 0 Special: True True False True True 1 True 20 True True True #000000000000 2 True Background True label_item False 1 False 4 1 True Colors 1 False tab True 9 9 9 9 True 3 Pretty-print results True True False True False 0 Keep result history True True False True False 1 True 22 True True History size: False 0 True True 1 1 9999 1 10 0 True True False 1 False 2 True 0 none True 22 True True automatic automatic True Execute this code automatically when starting the subprocess: True label_item 3 True 0 When matplotlib in non-interactive mode is imported: False 4 True 22 True 9 Switch to interactive mode True True False True True False 0 Warn True True False True True matplotlib_ia_switch_rad False 1 Do nothing True True False True True matplotlib_ia_switch_rad False 2 False 5 2 True Shell 2 False tab True 9 9 9 9 True 3 Automatically fold long output True True False True False 0 True 22 True True Fold output with more than False 0 True True 1 1 9999 1 10 0 True True False 1 True lines False 2 False 1 True True A program for externally viewing sections; The name of a temporary file will be appended. Tip: If you want a non-gui viewer, use something like "xterm -e less" True External viewer: False 0 True True 1 Browse... True True True False 2 False 2 Automatically add parentheses when pressing space True True False True False 3 True False 22 True True 0 Also add quotes for these functions: False False 0 True True A list of function names, separated by spaces, that usually get a string literal as an argument. If you type the name of a function of the list and then press the space key, parens and quotes will automatically be added - for example, 'execfile<space>' -> 'execfile("")'. You can also set the __expects_str__ attribute of a function to True to achieve the same effect. False 1 False 4 Leave code in the code box after execution True True False True False 5 Hide content of function and class definitions True True False True False 6 Ask before exiting DreamPie True True False True False 7 3 True User Interface 3 False tab 1 True end gtk-cancel -6 True True True True False False 0 gtk-ok -5 True True True True False False 1 False end 0 5 normal False DreamPie Copyright © 2010 Noam Yorav-Raphael The Python shell you've always dreamed about! DreamPie is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. DreamPie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with DreamPie; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Noam Yorav-Raphael <noamraph@gmail.com> True True 2 True end False end 0 5 normal False True 2 True 3 True 0 gtk-dialog-question 6 False 0 True 3 True 0 What do you want to discard? False 0 Discard history from previous sessions True True False True True False 1 Discard all history, including history from the current session True True False True False 2 1 1 True end gtk-cancel -6 True True True True True False False 0 gtk-ok -5 True True True True True False False 1 False end 0 True Copy True False True gtk-copy True Copy Code Only True True True Fold/Unfold Output/Code Section True True Copy Output/Code Section True True View Output/Code Section True True Save Output/Code Section True 12 True normal window_main False True 12 True 12 True gtk-dialog-question 6 False 0 True Do you want to quit DreamPie? Your history is not saved. False 1 False 1 _Don't ask me again next time True True False True True False 2 True end gtk-cancel True True True True False False 0 gtk-quit 1 True True True True True True True True False False 1 False end 0 dreampie-1.1.1/share/man/0000755000175000017500000000000011443646025013630 5ustar noamnoamdreampie-1.1.1/share/man/man1/0000755000175000017500000000000011443646025014464 5ustar noamnoamdreampie-1.1.1/share/man/man1/dreampie.10000644000175000017500000000200411274302547016330 0ustar noamnoam.TH "DREAMPIE" "1" "Nov 4, 2009" "" "" .SH "NAME" DreamPie \- An interactive Python shell .SH "SYNOPSIS" .B dreampie .RI [ options ]\ [ PYINTERP ] .br .SH "DESCRIPTION" This manual page documents briefly the .B DreamPie command. .PP \fBDreamPie\fP is a graphical interactive Python shell. It allows you to instantly run Python code and see the results. .PP PYINTERP can be a Python interpreter executable. Currently, Python versions 2.5, 2.6, 3.0, 3.1 are supported, as well as Jython 2.5. If PYINTERP isn't given, the default Python interpreter will be used. .SH "OPTIONS" .TP .B \-h, \-\-help Show summary of options. .SH "EXAMPLES" .TP \w'dreampie\ 'u .BI dreampie \ Run DreamPie with the default Python interpreter .TP .BI dreampie \ python3 Use the default Python 3 version installed .TP .BI dreampie \ jython Use Jython .TP .BI dreampie \ ~/Python-2.6/python Use a locally-build version of Python .SH "SEE ALSO" .BR python(1), .BR idle(1) .SH "AUTHOR" DreamPie was written by Noam Yorav-Raphael dreampie-1.1.1/LICENSE-PSF0000644000175000017500000000456211347001073013404 0ustar noamnoamPYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 -------------------------------------------- 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated documentation. 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Python Software Foundation; All Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee. 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in any such work a brief summary of the changes made to Python. 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions. 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade name in a trademark sense to endorse or promote products or services of Licensee, or any third party. 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License Agreement. dreampie-1.1.1/COPYING0000644000175000017500000010451311227673407013016 0ustar noamnoam GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . dreampie-1.1.1/PKG-INFO0000644000175000017500000000044211443646025013050 0ustar noamnoamMetadata-Version: 1.0 Name: dreampie Version: 1.1.1 Summary: DreamPie - The interactive Python shell you've always dreamed about! Home-page: http://dreampie.sourceforge.net/ Author: Noam Yorav-Raphael Author-email: noamraph@gmail.com License: GPL v3+ Description: UNKNOWN Platform: UNKNOWN dreampie-1.1.1/README0000644000175000017500000000267311334756641012650 0ustar noamnoamThis is DreamPie - The Python shell you've always dreamed about! Homepage: http://dreampie.sourceforge.net/ Requirements: Python 2.5/2.6, PyGTK DreamPie can also use Python 3.1 and Jython 1.5 as interpreters, but it requires Python 2.5/2.6 in order to run the main process (There's no PyGTK on those interpreters.) To run Python 3.1 you'll need Python 2.6, since lib2to3 (available only in 2.6) is needed. If you have those, you simply run ./dreampie or ./dreampie.py from the source directory. To use another interpreter, run something like ./dreampie jython A general-purpose python install: * python setup.py install To build the windows installer: * Remove the 'dist' directory * Make sure setup.nsi has the correct version number * \python26\python.exe setup.py py2exe * Copy pygtk-2.6, gtk-runtime directories from a previous installation into the dist directory. * Compile setup.nsi To build the debian package: (The version numbers are just to give you an idea) * python2.6 setup.py sdist * mkdir packaging * cd packaging * cp ../dist/dreampie-0.9.tar.gz dreampie_0.9.orig.tar.gz * tar -xvzf dreampie_0.9.orig.tar.gz * Copy the 'debian' directory from the previous package into dreampie-0.9 * cd dreampie-0.9/debian * dch -i (update version number, write something like "update to upstream") * cd .. * To create a source package: debuild -S To create a binary package: dpkg-buildpackage (Add -us -uc if you don't have a private key set up) dreampie-1.1.1/setup.py0000755000175000017500000001322711417110135013462 0ustar noamnoam#!/usr/bin/env python import os from distutils.core import setup from distutils.command.build import build from distutils.command.install import install from distutils.core import Command from distutils import log from dreampielib import __version__, subp_lib try: import py2exe except ImportError: py2exe = None # This file is non-standard because we want to build the subprocess library # (subp-py2, subp-py3) and put them in share/dreampie. # When run from the source directory, these files are automatically # created in the local share/dreampie. # We don't want to always do that in the setup script because the debian # packaging doesn't expect to find new files outside the build directory. # So we build them in the build directory and have a special install command # which copies them to the right place. # However, py2exe doesn't run custom install commands. So it py2exe is # available, the build_subp_lib commands puts them in the local share/dreampie # dir, and they are added to the data_files list. class build_subp_lib(Command): description = 'Build the subprocess lib, which include the needed modules.' user_options = [ ('build-dir=', 'd', "directory to build to"), ('force', 'f', "forcibly build everything (ignore file timestamps"), ] boolean_options = ['force'] def initialize_options(self): self.build_dir = None self.force = None def finalize_options(self): self.set_undefined_options('build', ('build_base', 'build_dir'), ('force', 'force'), ) def run(self): my_dir = os.path.dirname(__file__) src_dir = my_dir if py2exe is None: dst_dir = self.build_dir else: dst_dir = os.path.join(my_dir, 'share', 'dreampie') subp_lib.build(src_dir, dst_dir, log, self.force) build.sub_commands.append(('build_subp_lib', None)) class install_subp_lib (Command): description = "install the subprocess lib" user_options = [ ('install-dir=', 'd', "directory to install lib to"), ('build-dir=','b', "build directory (where to install from)"), ('force', 'f', "force installation (overwrite existing files)"), ('skip-build', None, "skip the build steps"), ] boolean_options = ['force', 'skip-build'] def initialize_options (self): self.install_dir = None self.force = 0 self.build_dir = None self.skip_build = None self.outfiles = [] def finalize_options (self): self.set_undefined_options('build', ('build_base', 'build_dir')) self.set_undefined_options('install', ('install_data', 'install_dir'), ('force', 'force'), ('skip_build', 'skip_build'), ) def run (self): join = os.path.join if not self.skip_build: self.run_command('build_subp_lib') for ver in subp_lib.lib_vers: src = join(self.build_dir, subp_lib.lib_fns[ver]) dst = join(self.install_dir, 'share', 'dreampie', subp_lib.lib_fns[ver]) self.outfiles.append(self.copy_tree(src, dst)) def get_outputs(self): return self.outfiles if py2exe is None: install.sub_commands.append(('install_subp_lib', None)) if py2exe is not None: d = {} for v in subp_lib.lib_vers: for fn in subp_lib.files: dst_dir = os.path.join('share/dreampie', subp_lib.lib_fns[v], os.path.dirname(fn)) src_fn = os.path.join('share/dreampie', subp_lib.lib_fns[v], fn) d.setdefault(dst_dir, []).append(src_fn) additional_py2exe_data_files = d.items() else: additional_py2exe_data_files = [] setup_args = dict( name='dreampie', version=__version__, description="DreamPie - The interactive Python shell you've always dreamed about!", author='Noam Yorav-Raphael', author_email='noamraph@gmail.com', url='http://dreampie.sourceforge.net/', license='GPL v3+', scripts=['dreampie'], console=[{'script': 'dreampie.py', 'icon_resources': [(1, 'dreampie.ico')]}], windows=[{'script': 'create-shortcuts.py', 'icon_resources': [(1, 'blank.ico')]}], packages=['dreampielib', 'dreampielib.common', 'dreampielib.gui', 'dreampielib.subprocess', ], data_files=[ ('share/applications', ['share/applications/dreampie.desktop']), ('share/man/man1', ['share/man/man1/dreampie.1']), ('share/pixmaps', ['share/pixmaps/dreampie.svg', 'share/pixmaps/dreampie.png']), ('share/dreampie', ['share/dreampie/subp_main.py', 'share/dreampie/dreampie.glade']), ] + additional_py2exe_data_files, cmdclass={'build_subp_lib': build_subp_lib, 'install_subp_lib': install_subp_lib}, options={'py2exe': {'ignores':['_scproxy', 'glib', 'gobject', 'gtk', 'gtk.gdk', 'gtk.glade', 'gtksourceview2', 'pango', 'pygtk'], 'excludes':['_ssl', 'doctest', 'pdb', 'unittest', 'difflib', 'unicodedata', 'bz2', 'zipfile', 'lib2to3'], 'includes':['fnmatch', 'glob'], }}, ) if py2exe is None: # Avoid the warning if py2exe is not available del setup_args['console'] del setup_args['windows'] setup(**setup_args) dreampie-1.1.1/dreampie0000755000175000017500000000010011274264246013461 0ustar noamnoam#!/usr/bin/env python from dreampielib.gui import main main() dreampie-1.1.1/dreampielib/0000755000175000017500000000000011443646025014230 5ustar noamnoamdreampie-1.1.1/dreampielib/__init__.py0000644000175000017500000000002611443644425016341 0ustar noamnoam__version__ = "1.1.1" dreampie-1.1.1/dreampielib/common/0000755000175000017500000000000011443646025015520 5ustar noamnoamdreampie-1.1.1/dreampielib/common/__init__.py0000644000175000017500000000000011021010326017574 0ustar noamnoamdreampie-1.1.1/dreampielib/common/objectstream.py0000644000175000017500000000350211415026714020550 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . """ Send objects over a socket by brining them. """ __all__ = ['send_object', 'recv_object'] import sys py3k = (sys.version_info[0] == 3) import struct # This was "from . import brine", but a bug in 2to3 in Python 2.6.5 # converted it to "from .. import brine", so I changed that. from ..common import brine if not py3k: empty_bytes = '' else: empty_bytes = bytes() def send_object(sock, obj): """Send an object over a socket""" s = brine.dump(obj) msg = struct.pack('. """ brine - a simple, fast and secure object serializer, optimized for small integers [-48..160), suitable for Python 2/3k communication. the following types are supported: int (in the unsigned long range), bool, unicode (In Py2) / str (In Py3), float, slice, complex, tuple(of simple types), list(of simple types), frozenset(of simple types) as well as the following singletons: None, NotImplemented, Ellipsis """ import sys py3k = (sys.version_info[0] == 3) if not py3k: from cStringIO import StringIO else: from io import BytesIO from struct import Struct if not py3k: def b(n): return chr(n) empty_bytes = '' else: def b(n): return bytes([n]) empty_bytes = bytes() # singletons TAG_NONE = b(0x00) TAG_EMPTY_STR = b(0x01) TAG_EMPTY_TUPLE = b(0x02) TAG_TRUE = b(0x03) TAG_FALSE = b(0x04) TAG_NOT_IMPLEMENTED = b(0x05) TAG_ELLIPSIS = b(0x06) # types #TAG_UNICODE = b(0x08) # Removed - STR is unicode. #TAG_LONG = b(0x09) # Removed TAG_STR1 = b(0x0a) TAG_STR2 = b(0x0b) TAG_STR3 = b(0x0c) TAG_STR4 = b(0x0d) TAG_STR_L1 = b(0x0e) TAG_STR_L4 = b(0x0f) TAG_TUP1 = b(0x10) TAG_TUP2 = b(0x11) TAG_TUP3 = b(0x12) TAG_TUP4 = b(0x13) TAG_TUP_L1 = b(0x14) TAG_TUP_L4 = b(0x15) TAG_INT_L1 = b(0x16) TAG_INT_L4 = b(0x17) TAG_FLOAT = b(0x18) TAG_SLICE = b(0x19) TAG_FSET = b(0x1a) TAG_COMPLEX = b(0x1b) # List TAG_EMPTY_LIST = b(0x1c) TAG_LIST1 = b(0x1d) TAG_LIST_L1 = b(0x1e) TAG_LIST_L4 = b(0x1f) IMM_INTS = dict((i, b(i + 0x50)) for i in range(-0x30, 0xa0)) I1 = Struct("!B") I4 = Struct("!L") F8 = Struct("!d") C16 = Struct("!dd") _dump_registry = {} _load_registry = {} IMM_INTS_LOADER = dict((v, k) for k, v in IMM_INTS.iteritems()) def register(coll, key): def deco(func): coll[key] = func return func return deco #=============================================================================== # dumping #=============================================================================== @register(_dump_registry, type(None)) def _dump_none(_obj, stream): stream.append(TAG_NONE) @register(_dump_registry, type(NotImplemented)) def _dump_notimplemeted(_obj, stream): stream.append(TAG_NOT_IMPLEMENTED) @register(_dump_registry, type(Ellipsis)) def _dump_ellipsis(_obj, stream): stream.append(TAG_ELLIPSIS) @register(_dump_registry, bool) def _dump_bool(obj, stream): if obj: stream.append(TAG_TRUE) else: stream.append(TAG_FALSE) @register(_dump_registry, slice) def _dump_slice(obj, stream): stream.append(TAG_SLICE) _dump((obj.start, obj.stop, obj.step), stream) @register(_dump_registry, frozenset) def _dump_frozenset(obj, stream): stream.append(TAG_FSET) _dump(tuple(obj), stream) @register(_dump_registry, int) def _dump_int(obj, stream): if obj in IMM_INTS: stream.append(IMM_INTS[obj]) else: obj = str(obj) l = len(obj) if l < 256: stream.append(TAG_INT_L1 + I1.pack(l) + obj) else: stream.append(TAG_INT_L4 + I4.pack(l) + obj) #@register(_dump_registry, long) #def _dump_long(obj, stream): # stream.append(TAG_LONG) # _dump_int(obj, stream) @register(_dump_registry, unicode) def _dump_str(obj, stream): obj = obj.encode('utf8') l = len(obj) if l == 0: stream.append(TAG_EMPTY_STR) elif l == 1: stream.append(TAG_STR1 + obj) elif l == 2: stream.append(TAG_STR2 + obj) elif l == 3: stream.append(TAG_STR3 + obj) elif l == 4: stream.append(TAG_STR4 + obj) elif l < 256: stream.append(TAG_STR_L1 + I1.pack(l) + obj) else: stream.append(TAG_STR_L4 + I4.pack(l) + obj) @register(_dump_registry, float) def _dump_float(obj, stream): stream.append(TAG_FLOAT + F8.pack(obj)) @register(_dump_registry, complex) def _dump_complex(obj, stream): stream.append(TAG_COMPLEX + C16.pack(obj.real, obj.imag)) #@register(_dump_registry, unicode) #def _dump_unicode(obj, stream): # stream.append(TAG_UNICODE) # _dump_str(obj.encode("utf8"), stream) @register(_dump_registry, tuple) def _dump_tuple(obj, stream): l = len(obj) if l == 0: stream.append(TAG_EMPTY_TUPLE) elif l == 1: stream.append(TAG_TUP1) elif l == 2: stream.append(TAG_TUP2) elif l == 3: stream.append(TAG_TUP3) elif l == 4: stream.append(TAG_TUP4) elif l < 256: stream.append(TAG_TUP_L1 + I1.pack(l)) else: stream.append(TAG_TUP_L4 + I4.pack(l)) for item in obj: _dump(item, stream) @register(_dump_registry, list) def _dump_list(obj, stream): l = len(obj) if l == 0: stream.append(TAG_EMPTY_LIST) elif l == 1: stream.append(TAG_LIST1) elif l < 256: stream.append(TAG_LIST_L1 + I1.pack(l)) else: stream.append(TAG_LIST_L4 + I4.pack(l)) for item in obj: _dump(item, stream) def _undumpable(obj, stream): raise TypeError("cannot dump %r" % (obj,)) def _dump(obj, stream): _dump_registry.get(type(obj), _undumpable)(obj, stream) #=============================================================================== # loading #=============================================================================== @register(_load_registry, TAG_NONE) def _load_none(_stream): return None @register(_load_registry, TAG_NOT_IMPLEMENTED) def _load_nonimp(_stream): return NotImplemented @register(_load_registry, TAG_ELLIPSIS) def _load_elipsis(_stream): return Ellipsis @register(_load_registry, TAG_TRUE) def _load_true(_stream): return True @register(_load_registry, TAG_FALSE) def _load_false(_stream): return False @register(_load_registry, TAG_EMPTY_TUPLE) def _load_empty_tuple(_stream): return () @register(_load_registry, TAG_EMPTY_LIST) def _load_empty_list(_stream): return [] @register(_load_registry, TAG_EMPTY_STR) def _load_empty_str(_stream): return u"" #@register(_load_registry, TAG_UNICODE) #def _load_unicode(stream): # obj = _load(stream) # return obj.decode("utf-8") #@register(_load_registry, TAG_LONG) #def _load_long(stream): # obj = _load(stream) # return long(obj) @register(_load_registry, TAG_FLOAT) def _load_float(stream): return F8.unpack(stream.read(8))[0] @register(_load_registry, TAG_COMPLEX) def _load_complex(stream): real, imag = C16.unpack(stream.read(16)) return complex(real, imag) @register(_load_registry, TAG_STR1) def _load_str1(stream): return stream.read(1).decode('utf8') @register(_load_registry, TAG_STR2) def _load_str2(stream): return stream.read(2).decode('utf8') @register(_load_registry, TAG_STR3) def _load_str3(stream): return stream.read(3).decode('utf8') @register(_load_registry, TAG_STR4) def _load_str4(stream): return stream.read(4).decode('utf8') @register(_load_registry, TAG_STR_L1) def _load_str_l1(stream): l, = I1.unpack(stream.read(1)) return stream.read(l).decode('utf8') @register(_load_registry, TAG_STR_L4) def _load_str_l4(stream): l, = I4.unpack(stream.read(4)) return stream.read(l).decode('utf8') @register(_load_registry, TAG_TUP1) def _load_tup1(stream): return (_load(stream),) @register(_load_registry, TAG_TUP2) def _load_tup2(stream): return (_load(stream), _load(stream)) @register(_load_registry, TAG_TUP3) def _load_tup3(stream): return (_load(stream), _load(stream), _load(stream)) @register(_load_registry, TAG_TUP4) def _load_tup4(stream): return (_load(stream), _load(stream), _load(stream), _load(stream)) @register(_load_registry, TAG_TUP_L1) def _load_tup_l1(stream): l, = I1.unpack(stream.read(1)) return tuple(_load(stream) for i in range(l)) @register(_load_registry, TAG_TUP_L4) def _load_tup_l4(stream): l, = I4.unpack(stream.read(4)) return tuple(_load(stream) for i in xrange(l)) @register(_load_registry, TAG_LIST1) def _load_list1(stream): return [_load(stream)] @register(_load_registry, TAG_LIST_L1) def _load_list_l1(stream): l, = I1.unpack(stream.read(1)) return list(_load(stream) for i in range(l)) @register(_load_registry, TAG_LIST_L4) def _load_list_l4(stream): l, = I4.unpack(stream.read(4)) return list(_load(stream) for i in xrange(l)) @register(_load_registry, TAG_SLICE) def _load_slice(stream): start, stop, step = _load(stream) return slice(start, stop, step) @register(_load_registry, TAG_FSET) def _load_frozenset(stream): return frozenset(_load(stream)) @register(_load_registry, TAG_INT_L1) def _load_int_l1(stream): l, = I1.unpack(stream.read(1)) return int(stream.read(l)) @register(_load_registry, TAG_INT_L4) def _load_int_l4(stream): l, = I4.unpack(stream.read(4)) return int(stream.read(l)) def _load(stream): tag = stream.read(1) if tag in IMM_INTS_LOADER: return IMM_INTS_LOADER[tag] return _load_registry.get(tag)(stream) #=============================================================================== # API #=============================================================================== def dump(obj): """dumps the given object to a byte-string representation""" stream = [] _dump(obj, stream) return empty_bytes.join(stream) def load(data): """loads the given byte-string representation to an object""" if not py3k: stream = StringIO(data) else: stream = BytesIO(data) return _load(stream) simple_types = frozenset([type(None), int, long, bool, str, float, unicode, slice, complex, type(NotImplemented), type(Ellipsis)]) def dumpable(obj): """indicates whether the object is dumpable by brine""" if type(obj) in simple_types: return True if type(obj) in (tuple, list, frozenset): return all(dumpable(item) for item in obj) return False if __name__ == "__main__": x = (u"he", 7, u"llo", 8, (), 900, None, True, Ellipsis, 18.2, 18.2j + 13, slice(1,2,3), frozenset([5,6,7]), [8,9,10], NotImplemented) assert dumpable(x) y = dump(x) z = load(y) assert x == z dreampie-1.1.1/dreampielib/subp_lib.py0000644000175000017500000000675211415370313016404 0ustar noamnoam# Copyright 2010 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . """ Build library used by the subprocess. This is not in setup.py so that it may be called at runtime when running from the source directory. """ __all__ = ['build', 'dirs', 'files', 'lib_fns', 'lib_vers'] import sys import os try: from lib2to3 import refactor except ImportError: py3_available = False else: py3_available = True dirs = [ 'dreampielib', 'dreampielib/subprocess', 'dreampielib/common'] files = [ 'dreampielib/__init__.py', 'dreampielib/subprocess/__init__.py', 'dreampielib/subprocess/find_modules.py', 'dreampielib/subprocess/split_to_singles.py', 'dreampielib/subprocess/trunc_traceback.py', 'dreampielib/common/__init__.py', 'dreampielib/common/objectstream.py', 'dreampielib/common/brine.py', ] lib_fns = {2: 'subp-py2', 3: 'subp-py3'} if py3_available: lib_vers = [2, 3] else: lib_vers = [2] def newer(source, target): """ Return True if the source is newer than the target or if the target doesn't exist. """ if not os.path.exists(target): return True return os.path.getmtime(source) > os.path.getmtime(target) class SimpleLogger(object): """Used when real logging isn't needed""" def debug(self, s): pass def info(self, s): print >> sys.stderr, s simple_logger = SimpleLogger() def build(src_dir, build_dir, log=simple_logger, force=False): join = os.path.join if py3_available: avail_fixes = refactor.get_fixers_from_package('lib2to3.fixes') rt = refactor.RefactoringTool(avail_fixes) for ver in lib_vers: lib_fn = join(build_dir, lib_fns[ver]) # Make dirs if they don't exist yet if not os.path.exists(lib_fn): os.mkdir(lib_fn) for dir in dirs: dir_fn = join(lib_fn, dir) if not os.path.exists(dir_fn): os.mkdir(dir_fn) # Write files if not up to date for fn in files: src_fn = join(src_dir, fn) dst_fn = join(lib_fn, fn) if not force and not newer(src_fn, dst_fn): continue if ver == 3: log.info("Converting %s to Python 3..." % fn) else: log.info("Copying %s..." % fn) f = open(join(src_dir, fn), 'rb') src = f.read() f.close() if ver == 3: dst = str(rt.refactor_string(src+'\n', fn))[:-1] else: dst = src dst = """\ # This file was automatically generated from a file in the source DreamPie dir. # DO NOT EDIT IT, as your changes will be gone when the file is created again. """ + dst f = open(dst_fn, 'wb') f.write(dst) f.close() dreampie-1.1.1/dreampielib/subprocess/0000755000175000017500000000000011443646025016420 5ustar noamnoamdreampie-1.1.1/dreampielib/subprocess/__init__.py0000644000175000017500000007130311443160213020523 0ustar noamnoam# Copyright 2010 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . import sys py3k = (sys.version_info[0] == 3) import os import time import socket from select import select from StringIO import StringIO import linecache import traceback import types import keyword import __builtin__ import inspect import pydoc import pprint import codeop try: # Executing multiple statements in 'single' mode (print results) is done # with the ast module. Python 2.5 doesn't have it, so we use the compiler # module, which also seems to work. Jython 2.5 does have the ast module, # which is fortunate, because the 'compiler module trick' doesn't work there. import ast except ImportError: ast = None from .split_to_singles import split_to_singles import __future__ if sys.platform == 'win32': from msvcrt import get_osfhandle from ctypes import byref, c_ulong, windll PeekNamedPipe = windll.kernel32.PeekNamedPipe from .trunc_traceback import trunc_traceback from .find_modules import find_modules # We don't use relative import because of a Jython 2.5.1 bug. from dreampielib.common.objectstream import send_object, recv_object #import rpdb2; rpdb2.start_embedded_debugger('a') import logging from logging import debug #logging.basicConfig(filename='/tmp/dreampie_subp_log', level=logging.DEBUG) # time interval to process GUI events, in seconds GUI_SLEEP = 0.1 rpc_funcs = set() # A decorator which adds the function name to rpc_funcs def rpc_func(func): rpc_funcs.add(func.func_name) return func # Taken from codeop.py PyCF_DONT_IMPLY_DEDENT = 0x200 _features = [getattr(__future__, fname) for fname in __future__.all_feature_names] case_insen_filenames = (os.path.normcase('A') == 'a') def unicodify(s): """Fault-tolerant conversion to unicode""" return s if isinstance(s, unicode) else s.decode('utf8', 'replace') class PlainTextDoc(pydoc.TextDoc): """pydoc.TextDoc returns strange bold text, so we disable it.""" def bold(self, text): return text textdoc = PlainTextDoc() # A mapping from id of types to boolean: is the type callable only. # We use ids instead of weakrefs because old classes aren't weakrefable, # and because we don't want to rely on weakref. This will fail if a type # was deleted and another was created at the same memory address, but this # seems unlikely. is_callable_cache = {} # magic methods for objects with defined operators operator_methods = ['__%s__' % s for s in 'add sub mul div floordiv truediv mod divmod pow lshift ' 'rshift and xor or'.split()] class Subprocess(object): def __init__(self, port): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect(('localhost', port)) # Make sys.displayhook change self.last_res self.last_res = None sys.displayhook = self.displayhook # Trick things like pdb into thinking that the namespace we create is # the main module mainmodule = types.ModuleType('__main__') sys.modules['__main__'] = mainmodule self.locs = mainmodule.__dict__ # Add '' to sys.path, to be like the regular Python interpreter sys.path.insert(0, '') # Set sys.argv to [''], to be like the regular Python interpreter # (Otherwise multiprocessing on win32 starts running subp_main.py) sys.argv = [''] # Remove __builtin__.exit, which only confuses users del __builtin__.exit self.gui_handlers = [GtkHandler(), Qt4Handler(), TkHandler()] self.gid = 0 self.flags = 0 # Config self.is_pprint = False self.is_matplotlib_ia_switch = False self.is_matplotlib_ia_warn = False self.reshist_size = 0 # Did we already handle matplotlib in non-interactive mode? self.matplotlib_ia_handled = False # The result history index of the next value to enter the history self.reshist_counter = 0 # Run endless loop self.loop() def loop(self): while True: self.handle_gui_events(self.sock) funcname, args = recv_object(self.sock) if funcname in rpc_funcs: func = getattr(self, funcname) try: r = func(*args) if isinstance(r, types.GeneratorType): for obj in r: send_object(self.sock, obj) else: send_object(self.sock, r) except Exception: # This may help in debugging exceptions. traceback.print_exc() send_object(self.sock, None) else: # aid in debug print >> sys.stderr, "Unknown command: %s" % funcname send_object(self.sock, None) def displayhook(self, res): if res is not None: self.last_res = res def handle_gui_events(self, sock): """ Handle GUI events until there's something to read from sock. If there's no graphic toolkit, just return. """ sock.setblocking(False) try: while not select([sock], [], [], 0)[0]: executed = False for handler in self.gui_handlers: cur_executed = handler.handle_events(GUI_SLEEP) executed = executed or cur_executed if not executed: break finally: sock.setblocking(True) @rpc_func def is_incomplete(self, source): """ Get a string of code. Return True if it's incomplete and False otherwise (if it's complete or if there's a syntax error.) This is used to run single-line statements without need for ctrl+enter. """ try: r = codeop.compile_command(source) except (SyntaxError, OverflowError, ValueError): return False return (r is None) @staticmethod def update_features(cur_flags, co_flags): """ Get an int with current __future__ flags, return it updated with co_flags from a code object. """ for feature in _features: if co_flags & feature.compiler_flag: cur_flags |= feature.compiler_flag return cur_flags def compile_ast(self, source): """ Compile source into a list of code objects, updating linecache, self.gid and self.flags. Return True, codeob on success. Return False, reason on syntax error. This version uses the ast module available in Python 2.6 and Jython 2.5. This version always returns a list with one item. """ filename = '' % self.gid try: a = compile(source, filename, 'exec', ast.PyCF_ONLY_AST | self.flags) b = ast.Interactive(a.body) codeob = compile(b, filename, 'single', self.flags) except SyntaxError, e: # Sometimes lineno or offset are not defined. Zero them in that case. lineno = e.lineno if e.lineno is not None else 1 offset = e.offset if e.offset is not None else 1 return False, (unicode(e.msg), lineno-1, offset-1) except ValueError, e: # Compiling "\x%" raises a ValueError return False, (unicode(e), 0, 0) # Update gid, linecache, flags self.gid += 1 lines = [x+'\n' for x in source.split("\n")] linecache.cache[filename] = len(source)+1, None, lines, filename self.flags = self.update_features(self.flags, codeob.co_flags) return True, [codeob] def compile_no_ast(self, source): """ This function does the same thing as compile_ast, but it works without the ast module. """ split_source = split_to_singles(source) # This added newline is because sometimes the CommandCompiler wants # more if there isn't a newline at the end split_source[-1] += '\n' line_count = 0 # Compile to check for syntax errors cur_flags = self.flags for src in split_source: try: c = compile(src, '', 'single', cur_flags) except SyntaxError, e: # Sometimes lineno or offset are not defined. Zero them in that # case. lineno = e.lineno if e.lineno is not None else 1 offset = e.offset if e.offset is not None else 1 msg = unicodify(e.msg) return False, (msg, lineno-1+line_count, offset-1) except ValueError, e: # Compiling "\x%" raises a ValueError return False, (unicode(e), 0, 0) else: if c is None: return False, None return else: line_count += src.count('\n') cur_flags = self.update_features(cur_flags, c.co_flags) # If compilation was successful... codeobs = [] for src in split_source: # We compile again, so as not to put into linecache code # which had no effect filename = '' % self.gid self.gid += 1 lines = [x+'\n' for x in src.split("\n")] linecache.cache[filename] = len(src)+1, None, lines, filename codeob = compile(src, filename, 'single', self.flags) self.flags = self.update_features(self.flags, codeob.co_flags) codeobs.append(codeob) return True, codeobs @rpc_func def execute(self, source): """ Get the source code to execute (a unicode string). Compile it. If there was a syntax error, return (False, (msg, line, col)). If compilation was successful, return (True, None), then run the code and then send (is_success, res_no, res_str, exception_string, rem_stdin). is_success - True if there was no exception. res_no - number of the result in the history count, or None if there was no result or there's no history. res_str - a string representation of the result. exception_string - description of the exception, or None if is_success. rem_stdin - data that was sent into stdin and wasn't consumed. """ if ast: success, r = self.compile_ast(source) else: success, r = self.compile_no_ast(source) if not success: yield False, r return else: yield True, None codeobs = r self.last_res = None try: # Execute for codeob in codeobs: exec codeob in self.locs # Work around http://bugs.python.org/issue8213 - stdout buffered # in Python 3. sys.stdout.flush() sys.stderr.flush() # Convert the result to a string. This is here because exceptions # may be raised here. if self.last_res is not None: if self.is_pprint: res_str = unicode(pprint.pformat(self.last_res)) else: res_str = unicode(repr(self.last_res)) else: res_str = None except (Exception, KeyboardInterrupt): sys.stdout.flush() excinfo = sys.exc_info() sys.last_type, sys.last_value, sys.last_traceback = excinfo exception_string = trunc_traceback(excinfo, __file__) is_success = False res_no = None res_str = None else: is_success = True exception_string = None if self.last_res is not None: res_no = self.store_in_reshist(self.last_res) else: res_no = None # Discard the reference to the result self.last_res = None # Send back any data left on stdin. rem_stdin = [] if sys.platform == 'linux2': while select([sys.stdin], [], [], 0)[0]: r = os.read(sys.stdin.fileno(), 8192) if not r: # File may be in error state break rem_stdin.append(r) elif sys.platform == 'win32': fd = sys.stdin.fileno() handle = get_osfhandle(fd) avail = c_ulong(0) PeekNamedPipe(handle, None, 0, None, byref(avail), None) nAvail = avail.value if nAvail > 0: rem_stdin.append(os.read(fd, nAvail)) else: # I don't know how to do this in Jython. pass rem_stdin = u''.join(rem_stdin) # Check if matplotlib in non-interactive mode was imported self.check_matplotlib_ia() yield is_success, res_no, res_str, exception_string, rem_stdin @rpc_func def set_pprint(self, is_pprint): self.is_pprint = is_pprint @rpc_func def set_matplotlib_ia(self, is_switch, is_warn): self.is_matplotlib_ia_switch = is_switch self.is_matplotlib_ia_warn = is_warn @rpc_func def set_reshist_size(self, new_reshist_size): if new_reshist_size < self.reshist_size: for i in range(self.reshist_counter-self.reshist_size, self.reshist_counter-new_reshist_size): self.locs.pop('_%d' % i, None) self.reshist_size = new_reshist_size @rpc_func def clear_reshist(self): for i in range(self.reshist_counter-self.reshist_size, self.reshist_counter): self.locs.pop('_%d' % i, None) def store_in_reshist(self, res): """ Get a result value to store in the result history. Store it, and return the result's index. If the result isn't stored, return None. """ if res is None: return None if '__' in self.locs: self.locs['___'] = self.locs['__'] if '_' in self.locs: self.locs['__'] = self.locs['_'] self.locs['_'] = res if self.reshist_size == 0: return None res_index = self.reshist_counter self.locs['_%d' % res_index] = res del_index = self.reshist_counter - self.reshist_size if del_index >= 0: self.locs.pop('_%d' % del_index, None) self.reshist_counter += 1 return res_index @staticmethod def split_list(L, public_set): """ split L into two lists: public and private, according to public_set, which should be a set of names or None. If it's None, split according to whether the first char is '_'. """ public = [] private = [] if public_set is not None: for x in L: if x in public_set: public.append(x) else: private.append(x) else: for x in L: if not x.startswith('_'): public.append(x) else: private.append(x) return public, private @rpc_func def complete_attributes(self, expr): """ Evaluate expr in the namespace, and return its attributes as two sorted lists - public and private. public - completions that are thought to be relevant. private - completions that are not so. """ try: entity = eval(expr, self.locs) ids = dir(entity) ids = map(unicodify, ids) ids.sort() if hasattr(entity, '__all__'): all_set = set(entity.__all__) else: all_set = None public, private = self.split_list(ids, all_set) except Exception: public = private = [] return public, private @rpc_func def complete_firstlevels(self): """ Get (public, private) names (globals, builtins, keywords). """ namespace = self.locs.copy() namespace.update(__builtin__.__dict__) ids = eval("dir()", namespace) + keyword.kwlist ids = map(unicodify, ids) ids.sort() if '__all__' in namespace: all_set = set(namespace['__all__']) else: all_set = None public, private = self.split_list(ids, all_set) return public, private @rpc_func def get_func_args(self, expr): """Return the argument names of the function (a list of strings)""" try: obj = eval(expr, self.locs) except Exception: return None try: args = inspect.getargspec(obj)[0] except TypeError: return None # There may be nested args, so we filter them return [unicodify(s) for s in args if isinstance(s, basestring)] @rpc_func def find_modules(self, package): if package: package = package.split('.') else: package = [] return [unicodify(s) for s in find_modules(package)] @rpc_func def get_module_members(self, mod_name): try: mod = sys.modules[mod_name] except KeyError: return None if hasattr(mod, '__all__'): all_set = set(mod.__all__) else: all_set = None ids = [unicodify(x) for x in mod.__dict__.iterkeys()] return self.split_list(ids, all_set) @rpc_func def complete_filenames(self, str_prefix, text, str_char, add_quote): is_raw = 'r' in str_prefix.lower() is_unicode = 'u' in str_prefix.lower() try: # We add a space because a backslash can't be the last # char of a raw string literal comp_what = eval(str_prefix + text + ' ' + str_char)[:-1] except SyntaxError: return if comp_what == '': comp_what = '.' if comp_what.startswith('//'): # This may be an XPath expression. Calling listdir on win32 will # interpret this as UNC and search the network, which may take # a long time or even stall. You can still use r'\\...' if you # want completion from UNC paths. return try: dirlist = os.listdir(comp_what) except OSError: return if case_insen_filenames: dirlist.sort(key=lambda s: s.lower()) else: dirlist.sort() public = [] private = [] for name in dirlist: orig_name = name if not py3k: if is_unicode and isinstance(name, str): # A filename which can't be unicode continue if not is_unicode: # We need a unicode string as the code. From what I see, # Python evaluates unicode characters in byte strings as utf-8. try: name = name.decode('utf8') except UnicodeDecodeError: continue # skip troublesome names try: rename = eval(str_prefix + name + str_char) except (SyntaxError, UnicodeDecodeError): continue if rename != orig_name: continue is_dir = os.path.isdir(os.path.join(comp_what, orig_name)) if not is_dir: if add_quote: name += str_char else: if '/' in text or os.path.sep == '/': # Prefer forward slash name += '/' else: if not is_raw: name += '\\\\' else: name += '\\' if name.startswith('.'): private.append(name) else: public.append(name) return public, private, case_insen_filenames @rpc_func def get_welcome(self): if 'IronPython' in sys.version: first_line = sys.version[sys.version.find('(')+1:sys.version.rfind(')')] else: if sys.platform.startswith('java'): name = 'Jython' else: name = 'Python' first_line = u'%s %s on %s' % (name, sys.version, sys.platform) return (first_line+'\n' +u'Type "copyright", "credits" or "license()" for more information.\n') @classmethod def _find_constructor(cls, class_ob): # Given a class object, return a function object used for the # constructor (ie, __init__() ) or None if we can't find one. try: return class_ob.__init__.im_func except AttributeError: for base in class_ob.__bases__: rc = cls._find_constructor(base) if rc is not None: return rc return None @rpc_func def get_func_doc(self, expr): """Get a string describing the arguments for the given object""" try: obj = eval(expr, self.locs) except Exception: return None if isinstance(obj, (types.BuiltinFunctionType, types.BuiltinMethodType)): # These don't have source code, and using pydoc will only # add something like "execfile(...)" before the doc. doc = inspect.getdoc(obj) if doc is None: return None return unicodify(doc) # Check if obj.__doc__ is not in the code (was added after definition). # If so, return pydoc's documentation. # This test is CPython-specific. Another approach would be to look for # the string in the source code. co_consts = getattr(getattr(obj, 'func_code', None), 'co_consts', None) __doc__ = getattr(obj, '__doc__', None) if co_consts is not None and __doc__ is not None: if __doc__ not in co_consts: # Return pydoc's documentation return unicodify(textdoc.document(obj).strip()) try: source = inspect.getsource(obj) except (TypeError, IOError): # If can't get the source, return pydoc's documentation return unicodify(textdoc.document(obj).strip()) else: # If we can get the source, return it. # cleandoc removes extra indentation. # We add a newline because it ignores indentation of first line... # The next line is for Python 2.5 compatibility. cleandoc = getattr(inspect, 'cleandoc', lambda s: s) return unicodify(cleandoc('\n'+source)) @rpc_func def is_callable_only(self, what): """ Checks whether an object is callable, and doesn't expect operators (so there's no point in typing space after its name, unless to add parens). Also checks whether obj.__expects_str__ is True, which means that the expected argument is a string so quotes will be added. Returns (is_callable_only, expects_str) """ try: obj = eval(what, self.locs) except Exception: return False, False typ = type(obj) expects_str = bool(getattr(obj, '__expects_str__', False)) # Check cache try: return is_callable_cache[id(typ)], expects_str except KeyError: pass r = (callable(obj) and not any(hasattr(obj, att) for att in operator_methods)) is_callable_cache[id(typ)] = r return r, expects_str def check_matplotlib_ia(self): """Check if matplotlib is in non-interactive mode, and handle it.""" if not self.is_matplotlib_ia_warn and not self.is_matplotlib_ia_switch: return if self.matplotlib_ia_handled: return if 'matplotlib' not in sys.modules: return self.matplotlib_ia_handled = True # From here we do this only once. matplotlib = sys.modules['matplotlib'] if not hasattr(matplotlib, 'is_interactive'): return if matplotlib.is_interactive(): return if self.is_matplotlib_ia_switch: if not hasattr(matplotlib, 'interactive'): return matplotlib.interactive(True) else: sys.stderr.write( "Warning: matplotlib in non-interactive mode detected.\n" "This means that plots will appear only after you run show().\n" "Use Edit->Preferences->Shell to automatically switch to interactive mode \n" "or to suppress this warning.\n") # Handle GUI events class GuiHandler(object): def handle_events(self, delay): """ This method gets the time in which to process GUI events, in seconds. If the GUI toolkit is loaded, run it for the specified delay and return True. If it isn't loaded, return False immediately. """ raise NotImplementedError("Abstract method") class GtkHandler(GuiHandler): def __init__(self): self.gtk = None self.timeout_add = None def handle_events(self, delay): if self.gtk is None: if 'gtk' in sys.modules: self.gtk = sys.modules['gtk'] try: from glib import timeout_add except ImportError: from gobject import timeout_add self.timeout_add = timeout_add else: return False self.timeout_add(int(delay * 1000), self.gtk_main_quit) self.gtk.main() return True def gtk_main_quit(self): self.gtk.main_quit() # Don't call me again return False class Qt4Handler(GuiHandler): def __init__(self): self.QtCore = None self.app = None def handle_events(self, delay): if self.QtCore is None: if 'PyQt4' in sys.modules: self.QtCore = sys.modules['PyQt4'].QtCore else: return False QtCore = self.QtCore if self.app is None: app = QtCore.QCoreApplication.instance() if app: self.app = app else: return False timer = QtCore.QTimer() QtCore.QObject.connect(timer, QtCore.SIGNAL('timeout()'), self.qt4_quit_if_no_modal) timer.start(delay*1000) self.app.exec_() timer.stop() QtCore.QObject.disconnect(timer, QtCore.SIGNAL('timeout()'), self.qt4_quit_if_no_modal) return True def qt4_quit_if_no_modal(self): app = self.app if app.__class__.__name__ != 'QApplication' or \ app.activeModalWidget() is None: app.quit() class TkHandler(GuiHandler): def __init__(self): self.Tkinter = None def handle_events(self, delay): # TODO: It's pretty silly to handle all events and then just wait. # But I haven't found a better way - if you find one, tell me! if self.Tkinter is None: if 'Tkinter' in sys.modules: self.Tkinter = sys.modules['Tkinter'] else: return False Tkinter = self.Tkinter # Handling Tk events is done only if there is an active tkapp object. # It is created by Tkinter.Tk.__init__, which sets # Tkinter._default_root to itself, when Tkinter._support_default_root # is True (the default). Here we check whether Tkinter._default_root # is something before we handle Tk events. if Tkinter._default_root: _tkinter = Tkinter._tkinter while _tkinter.dooneevent(_tkinter.DONT_WAIT): pass time.sleep(delay) return True def main(port): _subp = Subprocess(port) dreampie-1.1.1/dreampielib/subprocess/find_modules.py0000644000175000017500000000676211415401171021443 0ustar noamnoam# Copyright 2010 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . import sys import os from os.path import join, isdir, exists import stat import imp import re import time TIMEOUT = 1 # Stop after 1 second # Match any of the suffixes suffix_re = re.compile( r'(?:%s)$' % '|'.join(re.escape(suffix[0]) for suffix in imp.get_suffixes())) # A mapping from absolute names to (mtime, module_names) tuple. cache = {} def find_in_dir(dirname): """ Yield all names of modules in the given dir. """ if dirname == '': dirname = '.' try: basenames = os.listdir(dirname) except OSError: return for basename in basenames: m = suffix_re.search(basename) if m: yield basename[:m.start()] else: if '.' not in basename and isdir(join(dirname, basename)): init = join(dirname, basename, '__init__.py') if exists(init) or exists(init+'c'): yield basename def find_in_dir_cached(dirname): if dirname not in cache: # If it is in cache, it's already absolute. dirname = os.path.abspath(dirname) try: st = os.stat(dirname) except OSError: return () if not stat.S_ISDIR(st.st_mode): return () try: mtime, modules = cache[dirname] except KeyError: mtime = 0 if mtime != st.st_mtime: modules = list(find_in_dir(dirname)) cache[dirname] = (st.st_mtime, modules) return modules def find_package_path(package): """ Get a package as a list, try to find its path (list of dirs) or return None. """ for i in xrange(len(package), 0, -1): package_name = '.'.join(package[:i]) if package_name in sys.modules: try: path = sys.modules[package_name].__path__ except AttributeError: return None break else: i = 0 path = sys.path for j in xrange(i, len(package)): name = package[j] for dir in path: newdir = join(dir, name) if isdir(newdir): path = [newdir] break else: return None return path def find_modules(package): """ Get a sequence of names (what you get from package_name.split('.')), or [] for a toplevel module. Return a list of module names. """ start_time = time.time() r = set() path = find_package_path(package) if path: for dirname in path: r.update(find_in_dir_cached(dirname)) if time.time() - start_time > TIMEOUT: break prefix = ''.join(s+'.' for s in package) for name in sys.modules: if name.startswith(prefix): mod = name[len(prefix):] if '.' not in mod: r.add(mod) r.discard('__init__') return sorted(r) dreampie-1.1.1/dreampielib/subprocess/trunc_traceback.py0000644000175000017500000001052511443644310022122 0ustar noamnoam# Copyright 2010 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['trunc_traceback'] import sys py3k = (sys.version_info[0] == 3) import traceback import linecache from StringIO import StringIO def unicodify(s): """Fault-tolerant conversion to unicode""" return s if isinstance(s, unicode) else s.decode('utf8', 'replace') ####################################################################### # This is copied from traceback.py from Python 3.1.1. # It is copied because I don't want to rely on private functions. _cause_message = ( "\nThe above exception was the direct cause " "of the following exception:\n") _context_message = ( "\nDuring handling of the above exception, " "another exception occurred:\n") def _iter_chain(exc, custom_tb=None, seen=None): if seen is None: seen = set() seen.add(exc) its = [] cause = exc.__cause__ context = exc.__context__ if cause is not None and cause not in seen: its.append(_iter_chain(cause, None, seen)) its.append([(_cause_message, None)]) if context is not None and context is not cause and context not in seen: its.append(_iter_chain(context, None, seen)) its.append([(_context_message, None)]) its.append([(exc, custom_tb or exc.__traceback__)]) # itertools.chain is in an extension module and may be unavailable for it in its: for x in it: yield x # Copied up to here. ####################################################################### def canonical_fn(fn): """ Return something that will be equal for both source file and the cached compile file. """ # If the file contains a '$', remove from it (Jython uses it). Otherwise, # remove from a '.'. if '$' in fn: return fn.rsplit('$', 1)[0] else: return fn.rsplit('.', 1)[0] def trunc_traceback((_typ, value, tb), source_file): """ Format a traceback where entries before a frame from source_file are omitted (unless the last frame is from source_file). Return the result as a unicode string. """ # This is complicated because we want to support nested tracebacks # in Python 3. linecache.checkcache() efile = StringIO() if py3k: values = _iter_chain(value, tb) else: values = [(value, tb)] # The source_file and filename may differ in extension (pyc/py), so we # ignore the extension source_file = canonical_fn(source_file) for value, tb in values: if isinstance(value, basestring): efile.write(value+'\n') continue tbe = traceback.extract_tb(tb) # This is a work around a really weird IronPython bug. while len(tbe)>1 and 'split_to_singles' in tbe[-1][0]: tbe.pop() if canonical_fn(tbe[-1][0]) != source_file: # If the last entry is from this file, don't remove # anything. Otherwise, remove lines before the current # frame. for i in xrange(len(tbe)-2, -1, -1): if canonical_fn(tbe[i][0]) == source_file: tbe = tbe[i+1:] break efile.write('Traceback (most recent call last):'+'\n') traceback.print_list(tbe, file=efile) lines = traceback.format_exception_only(type(value), value) for line in lines: efile.write(line) if not hasattr(efile, 'buflist'): # Py3k return efile.getvalue() else: # The following line replaces efile.getvalue(), because if it # includes both unicode strings and byte string with non-ascii # chars, it fails. return u''.join(unicodify(s) for s in efile.buflist) dreampie-1.1.1/dreampielib/subprocess/split_to_singles.py0000644000175000017500000001267611374232152022362 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['split_to_singles'] import tokenize import itertools class ReadLiner(object): """ Perform readline over a string. After finishing, line_offsets contains the offset in the string for each line. Each line, except for the last one, ends with a '\n'. The last line doesn't end with a '\n'. So the number of lines is the number of '\n' chars in the string plus 1. """ def __init__(self, s): self.s = s self.line_offsets = [0] self.finished = False def __call__(self): if self.finished: return '' s = self.s line_offsets = self.line_offsets next_offset = s.find('\n', line_offsets[-1]) if next_offset == -1: self.finished = True return s[line_offsets[-1]:] else: line_offsets.append(next_offset+1) return s[line_offsets[-2]:line_offsets[-1]] class TeeIter(object): """Wrap an iterable to add a tee() method which tees.""" def __init__(self, iterable): self._it = iterable def __iter__(self): return self def next(self): return self._it.next() def tee(self): self._it, r = itertools.tee(self._it) return r def split_to_singles(source): """Get a source string, and split it into several strings, each one a "single block" which can be compiled in the "single" mode. Every string which is not the last one ends with a '\n', so to convert a line number of a sub-string to a line number of the big string, add the number of '\n' chars in the preceding strings. """ readline = ReadLiner(source) first_lines = [0] # Indices, 0-based, of the rows which start a new single. cur_indent_level = 0 had_decorator = False # What this does is pretty simple: We split on every NEWLINE token which # is on indentation level 0 and is not followed by "else", "except" or # "finally" (in that case it should be kept with the previous "single"). # Since we get the tokens one by one, and INDENT and DEDENT tokens come # *after* the NEWLINE token, we need a bit of care, so we peek at tokens # after the NEWLINE token to decide what to do. tokens_iter = TeeIter( itertools.ifilter(lambda x: x[0] not in (tokenize.COMMENT, tokenize.NL), tokenize.generate_tokens(readline))) try: for typ, s, (srow, _scol), (_erow, _rcol), line in tokens_iter: if typ == tokenize.NEWLINE: for typ2, s2, (_srow2, _scol2), (_erow2, _rcol2), _line2 \ in tokens_iter.tee(): if typ2 == tokenize.INDENT: cur_indent_level += 1 elif typ2 == tokenize.DEDENT: cur_indent_level -= 1 else: break else: raise AssertionError("Should have received an ENDMARKER") # Now we have the first token after INDENT/DEDENT ones. if (cur_indent_level == 0 and (typ2 != tokenize.ENDMARKER and not (typ2 == tokenize.NAME and s2 in ('else', 'except', 'finally')))): if not had_decorator: first_lines.append(srow) else: had_decorator = False elif s == '@' and cur_indent_level == 0: # Skip next first-line had_decorator = True except tokenize.TokenError: # EOF in the middle, it's a syntax error anyway. pass line_offsets = readline.line_offsets r = [] for i, line in enumerate(first_lines): if i != len(first_lines)-1: r.append(source[line_offsets[line]:line_offsets[first_lines[i+1]]]) else: r.append(source[line_offsets[line]:]) return r tests = [ """ a = 3 """,""" a = 3 b = 5 """,""" if 1: 1 """,""" if 1: 2 else: 3 """,""" if 1: 1 if 1: 2 else: 3 # comment """,""" try: 1/0 except: print 'oops' """,""" def f(): a = 3 def g(): a = 4 f() """,""" def f(): a = 3 def g(): a = 4 f() # comment """,""" try: 1 finally: 2 """,""" a=3 if 1: # comment 2 # comment # comment else: 3 """,""" @dec def f(): pass """,""" if 1: pass @dec def f(): pass """,""" class Class: @dec def method(): pass def f(): pass """ ] def test(): # This should raise a SyntaxError if splitting wasn't right. for t in tests: singles = split_to_singles(t) for s in singles: compile(s, "fn", "single") print "Test was successful"dreampie-1.1.1/dreampielib/gui/0000755000175000017500000000000011443646025015014 5ustar noamnoamdreampie-1.1.1/dreampielib/gui/subprocess_interact.py0000644000175000017500000001136511341162732021450 0ustar noamnoam# This module is based on a code by Josiah Carlson, # http://code.activestate.com/recipes/440554/ # Licensed under the PSF license. import os import subprocess import errno import time import sys PIPE = subprocess.PIPE if subprocess.mswindows: from msvcrt import get_osfhandle from ctypes import byref, c_ulong, windll PeekNamedPipe = windll.kernel32.PeekNamedPipe else: import select import fcntl class Popen(subprocess.Popen): def recv(self, maxsize=None): return self._recv('stdout', maxsize) def recv_err(self, maxsize=None): return self._recv('stderr', maxsize) def send_recv(self, input='', maxsize=None): return self.send(input), self.recv(maxsize), self.recv_err(maxsize) def get_conn_maxsize(self, which, maxsize): if maxsize is None: maxsize = 1024 elif maxsize < 1: maxsize = 1 return getattr(self, which), maxsize def _close(self, which): getattr(self, which).close() setattr(self, which, None) if subprocess.mswindows: def send(self, input): if not self.stdin: return None try: written = os.write(self.stdin, input) except ValueError: return self._close('stdin') except (subprocess.pywintypes.error, Exception), why: if why[0] in (109, errno.ESHUTDOWN): return self._close('stdin') raise return written def _recv(self, which, maxsize): conn, maxsize = self.get_conn_maxsize(which, maxsize) read = "" if conn is None: return None try: fd = conn.fileno() handle = get_osfhandle(fd) avail = c_ulong(0) PeekNamedPipe(handle, None, 0, None, byref(avail), None) nAvail = avail.value if maxsize < nAvail: nAvail = maxsize if nAvail > 0: read = os.read(fd, nAvail) except ValueError: return self._close(which) except (subprocess.pywintypes.error, Exception), why: if why[0] in (109, errno.ESHUTDOWN): return self._close(which) raise if self.universal_newlines: read = self._translate_newlines(read) return read else: def send(self, input): if not self.stdin: return None if not select.select([], [self.stdin], [], 0)[1]: return 0 try: written = os.write(self.stdin.fileno(), input) except OSError, why: if why[0] == errno.EPIPE: #broken pipe return self._close('stdin') raise return written def _recv(self, which, maxsize): conn, maxsize = self.get_conn_maxsize(which, maxsize) if conn is None: return None flags = fcntl.fcntl(conn, fcntl.F_GETFL) if not conn.closed: fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK) try: if not select.select([conn], [], [], 0)[0]: return '' r = conn.read(maxsize) if not r: return self._close(which) if self.universal_newlines: r = self._translate_newlines(r) return r finally: if not conn.closed: fcntl.fcntl(conn, fcntl.F_SETFL, flags) message = "Other end disconnected!" def recv_some(p, t=.1, e=1, tr=5, stderr=0): if tr < 1: tr = 1 x = time.time()+t y = [] r = '' pr = p.recv if stderr: pr = p.recv_err while time.time() < x or r: r = pr() if r is None: if e: raise Exception(message) else: break elif r: y.append(r) else: time.sleep(max((x-time.time())/tr, 0)) return ''.join(y) def send_all(p, data): while len(data): sent = p.send(data) if sent is None: raise Exception(message) data = buffer(data, sent) if __name__ == '__main__': if sys.platform == 'win32': shell, commands, tail = ('cmd', ('dir /w', 'echo HELLO WORLD'), '\r\n') else: shell, commands, tail = ('sh', ('ls', 'echo HELLO WORLD'), '\n') a = Popen(shell, stdin=PIPE, stdout=PIPE) print recv_some(a), for cmd in commands: send_all(a, cmd + tail) print recv_some(a), send_all(a, 'exit' + tail) print recv_some(a, e=0) a.wait() dreampie-1.1.1/dreampielib/gui/newline_and_indent.py0000644000175000017500000001130711274224251021207 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['newline_and_indent'] from . import pyparse def newline_and_indent(sourceview, INDENT_WIDTH): """ Get a sourceview. Add a newline and indent - what happens when the user pressed Enter. """ # This is based on newline_and_indent_event(), # from idlelib/EditorWindow.py sb = sourceview.get_buffer() sb.begin_user_action() insert_mark = sb.get_insert() insert = lambda: sb.get_iter_at_mark(insert_mark) try: sb.delete_selection(True, True) line = sb.get_text(sb.get_iter_at_line(insert().get_line()), insert()) i, n = 0, len(line) while i < n and line[i] in " \t": i = i+1 if i == n: # the cursor is in or at leading indentation in a continuation # line; just copy the indentation sb.insert_at_cursor('\n'+line) sourceview.scroll_mark_onscreen(sb.get_insert()) return True indent = line[:i] # strip whitespace before insert point i = 0 while line and line[-1] in " \t": line = line[:-1] i = i+1 if i: sb.delete(sb.get_iter_at_line_offset(insert().get_line(), len(line)), insert()) # strip whitespace after insert point it = insert(); it.forward_to_line_end() after_insert = sb.get_text(insert(), it) i = 0 while i < len(after_insert) and after_insert[i] in " \t": i += 1 if i > 0: it = insert(); it.forward_chars(i) sb.delete(insert(), it) # start new line sb.insert_at_cursor('\n') # scroll to see the beginning of the line sourceview.scroll_mark_onscreen(sb.get_insert()) #self.scrolledwindow_sourceview.get_hadjustment().set_value(0) # adjust indentation for continuations and block # open/close first need to find the last stmt y = pyparse.Parser(INDENT_WIDTH, INDENT_WIDTH) y.set_str(sb.get_text(sb.get_start_iter(), insert())) c = y.get_continuation_type() if c != pyparse.C_NONE: # The current stmt hasn't ended yet. if c == pyparse.C_STRING_FIRST_LINE: # after the first line of a string; do not indent at all pass elif c == pyparse.C_STRING_NEXT_LINES: # inside a string which started before this line; # just mimic the current indent sb.insert_at_cursor(indent) elif c == pyparse.C_BRACKET: # line up with the first (if any) element of the # last open bracket structure; else indent one # level beyond the indent of the line with the # last open bracket sb.insert_at_cursor(' ' * y.compute_bracket_indent()) elif c == pyparse.C_BACKSLASH: # if more than one line in this stmt already, just # mimic the current indent; else if initial line # has a start on an assignment stmt, indent to # beyond leftmost =; else to beyond first chunk of # non-whitespace on initial line if y.get_num_lines_in_stmt() > 1: sb.insert_at_cursor(indent) else: sb.insert_at_cursor(' ' * y.compute_backslash_indent()) else: assert False, "bogus continuation type %r" % (c,) return True # This line starts a brand new stmt; indent relative to # indentation of initial line of closest preceding # interesting stmt. indent = len(y.get_base_indent_string()) if y.is_block_opener(): indent = (indent // INDENT_WIDTH + 1) * INDENT_WIDTH elif y.is_block_closer(): indent = max(((indent - 1) // INDENT_WIDTH) * INDENT_WIDTH, 0) sb.insert_at_cursor(' ' * indent) return True finally: sb.end_user_action() dreampie-1.1.1/dreampielib/gui/__init__.py0000644000175000017500000012440511443155512017127 0ustar noamnoam# Copyright 2010 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . import sys import os from os import path import time import tempfile from optparse import OptionParser import subprocess import webbrowser import re from keyword import iskeyword import logging from logging import debug #logging.basicConfig(format="dreampie: %(message)s", level=logging.DEBUG) def find_data_dir(): """ Find the 'share' directory in which to find files. If we are inside the source directory, build subp zips. """ # Scenarios: # * Running from the source directory. 'share' is near 'dreampielib' # * Running from a distutils installed executable. The scheme is: # prefix/bin/executable # prefix/share/ # * Running from py2exe. 'share' is near the executable. # * Running from /usr/bin/X11/dreampie on Debian. Just check '/usr/share'. # # So, if we find a 'share' near dreampielib, we build zips and return it. # Otherwise, we search for 'share' near the executable. If it doesn't # exist, we search for 'share' one level below the executable. from os.path import join, dirname, isdir, pardir, abspath local_data_dir = join(dirname(__file__), pardir, pardir, 'share') if isdir(join(local_data_dir, 'dreampie')): # We're in the source path. Build zips if needed, and return the right # dir. from ..subp_lib import build src_dir = join(dirname(__file__), pardir, pardir) build_dir = join(local_data_dir, 'dreampie') build(src_dir, build_dir) return abspath(local_data_dir) else: alternatives = [ join(dirname(sys.argv[0]), 'share'), # py2exe join(dirname(sys.argv[0]), pardir, 'share'), # distutils '/usr/share', # debian ] for dir in alternatives: absdir = abspath(dir) if isdir(join(absdir, 'dreampie')): return absdir else: raise OSError("Could not find the 'share' directory") data_dir = find_data_dir() gladefile = path.join(data_dir, 'dreampie', 'dreampie.glade') if sys.platform == 'win32': from .load_pygtk import load_pygtk load_pygtk(data_dir) import pygtk pygtk.require('2.0') import gobject import gtk from gtk import gdk, glade import pango import gtksourceview2 from . import gtkexcepthook try: from glib import timeout_add, idle_add except ImportError: # In PyGObject 2.14, it's in gobject. from gobject import timeout_add, idle_add from .. import __version__ from .SimpleGladeApp import SimpleGladeApp from .keyhandler import (make_keyhandler_decorator, handle_keypress, parse_keypress_event) from .config import Config from .config_dialog import ConfigDialog from .write_command import write_command from .newline_and_indent import newline_and_indent from .output import Output from .folding import Folding from .selection import Selection from .status_bar import StatusBar from .vadj_to_bottom import VAdjToBottom from .history import History from .hist_persist import HistPersist from .autocomplete import Autocomplete from .call_tips import CallTips from .autoparen import Autoparen from .subprocess_handler import SubprocessHandler, StartError from .beep import beep from .file_dialogs import save_dialog from .tags import (OUTPUT, STDIN, STDOUT, STDERR, EXCEPTION, PROMPT, COMMAND, COMMAND_DEFS, COMMAND_SEP, MESSAGE, RESULT_IND, RESULT) from . import tags INDENT_WIDTH = 4 # Default line length, by which we set the default window size LINE_LEN = 80 # Time to wait before autocompleting, to see if the user continues to type AUTOCOMPLETE_WAIT = 400 # Maybe someday we'll want translations... _ = lambda s: s # A decorator for managing sourceview key handlers sourceview_keyhandlers = {} sourceview_keyhandler = make_keyhandler_decorator(sourceview_keyhandlers) def get_widget(name): """Create a widget from the glade file.""" xml = glade.XML(gladefile, name) return xml.get_widget(name) class DreamPie(SimpleGladeApp): def __init__(self, pyexec): SimpleGladeApp.__init__(self, gladefile, 'window_main') self.load_popup_menus() self.set_mac_accelerators() self.config = Config() self.window_main.set_icon_from_file( path.join(data_dir, 'pixmaps', 'dreampie.png')) self.textbuffer = tb = self.textview.get_buffer() self.init_textbufferview() # Mark where the cursor was when the popup menu was popped self.popup_mark = tb.create_mark('popup-mark', tb.get_start_iter(), left_gravity=True) self.init_sourcebufferview() self.configure() self.output = Output(self.textview) self.folding = Folding(self.textbuffer, LINE_LEN) self.selection = Selection(self.textview, self.sourceview, self.on_is_something_selected_changed) self.status_bar = StatusBar(self.sourcebuffer, self.statusbar) self.vadj_to_bottom = VAdjToBottom(self.scrolledwindow_textview .get_vadjustment()) self.history = History(self.textview, self.sourceview, self.config) self.recent_manager = gtk.recent_manager_get_default() self.menuitem_recent = [self.menuitem_recent0, self.menuitem_recent1, self.menuitem_recent2, self.menuitem_recent3] self.recent_filenames = [None] * len(self.menuitem_recent) self.recent_manager.connect('changed', self.on_recent_manager_changed) self.histpersist = HistPersist(self.window_main, self.textview, self.status_bar, self.recent_manager) self.autocomplete = Autocomplete(self.sourceview, self.complete_attributes, self.complete_firstlevels, self.get_func_args, self.find_modules, self.get_module_members, self.complete_filenames, INDENT_WIDTH) # Hack: we connect this signal here, so that it will have lower # priority than the key-press event of autocomplete, when active. self.sourceview.connect('key-press-event', self.on_sourceview_keypress) self.call_tips = CallTips(self.sourceview, self.get_func_doc, INDENT_WIDTH) self.autoparen = Autoparen(self.sourcebuffer, self.is_callable_only, self.get_expects_str, self.autoparen_show_call_tip, INDENT_WIDTH) self.subp = SubprocessHandler( pyexec, data_dir, self.on_stdout_recv, self.on_stderr_recv, self.on_object_recv, self.on_subp_terminated) try: self.subp.start() except StartError, e: msg = gtk.MessageDialog( None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, _("Couldn't start subprocess: %s") % e) _response = msg.run() msg.destroy() print >> sys.stderr, e sys.exit(1) # Is the subprocess executing a command self.is_executing = False # Are we trying to shut down self.is_terminating = False self.set_window_default_size() self.window_main.show_all() self.set_is_executing(False) self.update_recent() self.show_welcome() self.configure_subp() self.run_init_code() if self.config.get_bool('show-getting-started'): self.show_getting_started_dialog() self.config.set_bool('show-getting-started', False) self.config.save() def load_popup_menus(self): # Load popup menus from the glade file. Would not have been needed if # popup menus could be children of windows. xml = glade.XML(gladefile, 'popup_sel_menu') xml.signal_autoconnect(self) self.popup_sel_menu = xml.get_widget('popup_sel_menu') xml = glade.XML(gladefile, 'popup_nosel_menu') xml.signal_autoconnect(self) self.popup_nosel_menu = xml.get_widget('popup_nosel_menu') self.fold_unfold_section_menu = xml.get_widget('fold_unfold_section_menu') self.copy_section_menu = xml.get_widget('copy_section_menu') self.view_section_menu = xml.get_widget('view_section_menu') self.save_section_menu = xml.get_widget('save_section_menu') def set_mac_accelerators(self): # Set up accelerators suitable for the Mac. # Ctrl-Up and Ctrl-Down are taken by the window manager, so we use # Ctrl-PgUp and Ctrl-PgDn. # We want it to be easy to switch, so both sets of keys are always # active, but only one, most suitable for each platform, is displayed # in the menu. accel_group = gtk.accel_groups_from_object(self.window_main)[0] menu_up = self.menuitem_history_up UP = gdk.keyval_from_name('Up') PGUP = gdk.keyval_from_name('Prior') menu_dn = self.menuitem_history_down DN = gdk.keyval_from_name('Down') PGDN = gdk.keyval_from_name('Next') if sys.platform != 'darwin': menu_up.add_accelerator('activate', accel_group, PGUP, gdk.CONTROL_MASK, 0) menu_dn.add_accelerator('activate', accel_group, PGDN, gdk.CONTROL_MASK, 0) else: menu_up.remove_accelerator(accel_group, UP, gdk.CONTROL_MASK) menu_up.add_accelerator('activate', accel_group, PGUP, gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) menu_up.add_accelerator('activate', accel_group, UP, gdk.CONTROL_MASK, 0) menu_dn.remove_accelerator(accel_group, DN, gdk.CONTROL_MASK) menu_dn.add_accelerator('activate', accel_group, PGDN, gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) menu_dn.add_accelerator('activate', accel_group, DN, gdk.CONTROL_MASK, 0) def on_cut(self, _widget): return self.selection.cut() def on_copy(self, _widget): return self.selection.copy() def on_copy_commands_only(self, _widget): return self.selection.copy_commands_only() def on_paste(self, _widget): return self.selection.paste() def on_is_something_selected_changed(self, is_something_selected): self.menuitem_cut.props.sensitive = is_something_selected self.menuitem_copy.props.sensitive = is_something_selected self.menuitem_copy_commands_only.props.sensitive = is_something_selected self.menuitem_interrupt.props.sensitive = not is_something_selected # Source buffer, Text buffer def init_textbufferview(self): tv = self.textview tb = self.textbuffer tv.set_wrap_mode(gtk.WRAP_CHAR) tags.add_tags(tb) tv.connect('key-press-event', self.on_textview_keypress) tv.connect('focus-in-event', self.on_textview_focus_in) def set_window_default_size(self): tv = self.textview context = tv.get_pango_context() metrics = context.get_metrics(tv.style.font_desc, context.get_language()) # I don't know why I have to add 2, but it works. width = pango.PIXELS(metrics.get_approximate_digit_width()*(LINE_LEN+2)) height = pango.PIXELS( (metrics.get_ascent() + metrics.get_descent())*30) self.window_main.set_default_size(width, height) def init_sourcebufferview(self): self.sourcebuffer = sb = gtksourceview2.Buffer() self.sourceview = sv = gtksourceview2.View(self.sourcebuffer) lm = gtksourceview2.LanguageManager() python = lm.get_language('python') sb.set_language(python) self.scrolledwindow_sourceview.add(self.sourceview) sv.connect('focus-in-event', self.on_sourceview_focus_in) sv.grab_focus() def sb_get_text(self, *args): # Unfortunately, PyGTK returns utf-8 encoded byte strings... return self.sourcebuffer.get_text(*args).decode('utf8') def sv_scroll_cursor_onscreen(self): self.sourceview.scroll_mark_onscreen(self.sourcebuffer.get_insert()) def on_textview_focus_in(self, _widget, _event): # Clear the selection of the sourcebuffer self.sourcebuffer.move_mark(self.sourcebuffer.get_selection_bound(), self.sourcebuffer.get_iter_at_mark( self.sourcebuffer.get_insert())) def on_sourceview_focus_in(self, _widget, _event): # Clear the selection of the textbuffer self.textbuffer.move_mark(self.textbuffer.get_selection_bound(), self.textbuffer.get_iter_at_mark( self.textbuffer.get_insert())) def write(self, data, *tag_names): self.textbuffer.insert_with_tags_by_name( self.textbuffer.get_end_iter(), data, *tag_names) def write_output(self, data, tag_names, onnewline=False, addbreaks=True): """ Call self.output.write with the given arguments, and autofold if needed. """ it = self.output.write(data, tag_names, onnewline, addbreaks) if self.config.get_bool('autofold'): self.folding.autofold(it, self.config.get_int('autofold-numlines')) def set_is_executing(self, is_executing): self.is_executing = is_executing label = _(u'Execute Code') if not is_executing else _(u'Write Input') self.menuitem_execute.child.props.label = label self.menuitem_discard_hist.props.sensitive = not is_executing @staticmethod def replace_gtk_quotes(source): # Work around GTK+ bug https://bugzilla.gnome.org/show_bug.cgi?id=610928 # in order to fix bug #525469 - replace fancy quotes with regular # quotes. return source.replace(u'\xa8', '"').replace(u'\xb4', "'") def execute_source(self): """Execute the source in the source buffer. """ sb = self.sourcebuffer source = self.sb_get_text(sb.get_start_iter(), sb.get_end_iter()) source = source.rstrip() source = self.replace_gtk_quotes(source) is_ok, syntax_error_info = self.call_subp(u'execute', source) if not is_ok: if syntax_error_info: msg, lineno, offset = syntax_error_info status_msg = _("Syntax error: %s (at line %d col %d)") % ( msg, lineno+1, offset+1) # Work around a bug: offset may be wrong, which will cause # gtk to crash if using sb.get_iter_at_line_offset. iter = sb.get_iter_at_line(lineno) iter.forward_chars(offset+1) sb.place_cursor(iter) else: # Incomplete status_msg = _("Command is incomplete") sb.place_cursor(sb.get_end_iter()) self.status_bar.set_status(status_msg) beep() else: write_command(self.write, source.strip()) self.output.start_new_section() if not self.config.get_bool('leave-code'): sb.delete(sb.get_start_iter(), sb.get_end_iter()) self.vadj_to_bottom.scroll_to_bottom() self.set_is_executing(True) def send_stdin(self): """Send the contents of the sourcebuffer as stdin.""" sb = self.sourcebuffer s = self.sb_get_text(sb.get_start_iter(), sb.get_end_iter()) if not s.endswith('\n'): s += '\n' self.write_output(s, [COMMAND, STDIN], addbreaks=False) self.write('\r', COMMAND_SEP) self.output.start_new_section() self.vadj_to_bottom.scroll_to_bottom() if not self.config.get_bool('leave-code'): sb.delete(sb.get_start_iter(), sb.get_end_iter()) self.subp.write(s) @sourceview_keyhandler('Return', 0) def on_sourceview_return(self): sb = self.sourcebuffer # If we are on the first line, and it doesn't end with a ' ': # * If we are not executing, try to execute (if failed, continue # with normal behavior) # * If we are executing, send the line as stdin. insert_iter = sb.get_iter_at_mark(sb.get_insert()) if (insert_iter.equal(sb.get_end_iter()) and insert_iter.get_line() == 0 and insert_iter.get_offset() != 0 and not self.sb_get_text(sb.get_start_iter(), insert_iter).endswith(' ')): if not self.is_executing: source = self.sb_get_text(sb.get_start_iter(), sb.get_end_iter()) source = source.rstrip() source = self.replace_gtk_quotes(source) is_incomplete = self.call_subp(u'is_incomplete', source) if not is_incomplete: self.execute_source() return True else: # is_executing self.send_stdin() return True # If we are after too many newlines, the user probably just wanted to # execute - notify him. # We check if this line is empty and the previous one is. show_execution_tip = False if insert_iter.equal(sb.get_end_iter()): it = sb.get_end_iter() # This goes to the beginning of the line, and another line # backwards, so we get two lines it.backward_lines(1) text = self.sb_get_text(it, sb.get_end_iter()) if not text.strip(): show_execution_tip = True # We didn't execute, so newline-and-indent. r = newline_and_indent(self.sourceview, INDENT_WIDTH) if show_execution_tip: self.status_bar.set_status(_( "Tip: To execute your code, use Ctrl+Enter.")) return r @sourceview_keyhandler('Tab', 0) def on_sourceview_tab(self): sb = self.sourcebuffer insert = sb.get_iter_at_mark(sb.get_insert()) insert_linestart = sb.get_iter_at_line(insert.get_line()) line = self.sb_get_text(insert_linestart, insert) if not line.strip(): # We are at the beginning of a line, so indent - forward to next # "tab stop" sb.insert_at_cursor(' ' * (INDENT_WIDTH - len(line) % INDENT_WIDTH)) else: # Completion should come here self.autocomplete.show_completions(is_auto=False, complete=True) self.sv_scroll_cursor_onscreen() return True @sourceview_keyhandler('ISO_Left_Tab', 0) def on_sourceview_shift_tab(self): self.textview.grab_focus() return True @sourceview_keyhandler('BackSpace', 0) def on_sourceview_backspace(self): sb = self.sourcebuffer insert = sb.get_iter_at_mark(sb.get_insert()) insert_linestart = sb.get_iter_at_line(insert.get_line()) line = self.sb_get_text(insert_linestart, insert) if line and not line.strip(): # There are only space before us, so remove spaces up to last # "tab stop" delete_from = ((len(line) - 1) // INDENT_WIDTH) * INDENT_WIDTH it = sb.get_iter_at_line_offset(insert.get_line(), delete_from) sb.delete(it, insert) self.sv_scroll_cursor_onscreen() return True return False # The following 3 handlers are for characters which may trigger automatic # opening of the completion list. (slash and backslash depend on path.sep) # We leave the final decision whether to open the list to the autocompleter. # We just notify it that the char was inserted and the user waited a while. @sourceview_keyhandler('period', 0) def on_sourceview_period(self): timeout_add(AUTOCOMPLETE_WAIT, self.check_autocomplete, '.') @sourceview_keyhandler('slash', 0) def on_sourceview_slash(self): timeout_add(AUTOCOMPLETE_WAIT, self.check_autocomplete, '/') @sourceview_keyhandler('backslash', 0) def on_sourceview_backslash(self): timeout_add(AUTOCOMPLETE_WAIT, self.check_autocomplete, '\\') def check_autocomplete(self, last_char): """ If the last char in the sourcebuffer is last_char, call show_completions. """ sb = self.sourcebuffer if self.sourceview.is_focus(): it = sb.get_iter_at_mark(sb.get_insert()) it2 = it.copy() it2.backward_chars(1) char = sb.get_text(it2, it) if char == last_char: self.autocomplete.show_completions(is_auto=True, complete=False) # return False so as not to be called repeatedly. return False @sourceview_keyhandler('parenleft', 0) def on_sourceview_parenleft(self): idle_add(self.call_tips.show, True) def on_sourceview_keypress(self, _widget, event): return handle_keypress(self, event, sourceview_keyhandlers) # Autoparen @sourceview_keyhandler('space', 0) def on_sourceview_space(self): """ If a space was hit after a callable-only object, add parentheses. """ if self.is_executing: return False if not self.config.get_bool('autoparen'): return False return self.autoparen.add_parens() def is_callable_only(self, expr): # This should be called only as a result of on_sourceview_space, which # already checks that is_executing==False. return self.call_subp(u'is_callable_only', expr) def get_expects_str(self): return set(self.config.get('expects-str-2').split()) def autoparen_show_call_tip(self): self.call_tips.show(is_auto=True) # History def on_textview_keypress(self, _widget, event): keyval_name, state = parse_keypress_event(event) if (keyval_name, state) == ('Return', 0): return self.history.copy_to_sourceview() def on_history_up(self, _widget): self.history.history_up() def on_history_down(self, _widget): self.history.history_down() # Subprocess def show_welcome(self): s = self.call_subp(u'get_welcome') s += 'DreamPie %s\n' % __version__ self.write(s, MESSAGE) self.output.start_new_section() def configure_subp(self): config = self.config if config.get_bool('use-reshist'): reshist_size = config.get_int('reshist-size') else: reshist_size = 0 self.call_subp(u'set_reshist_size', reshist_size) self.menuitem_clear_reshist.props.sensitive = (reshist_size > 0) self.call_subp(u'set_pprint', config.get_bool('pprint')) self.call_subp(u'set_matplotlib_ia', config.get_bool('matplotlib-ia-switch'), config.get_bool('matplotlib-ia-warn')) def run_init_code(self): """ Runs the init code. This will result in the code being run and a '>>>' printed afterwards. If there's no init code, will just print '>>>'. """ init_code = unicode(eval(self.config.get('init-code'))) if init_code: is_ok, syntax_error_info = self.call_subp(u'execute', init_code) if not is_ok: msg, lineno, offset = syntax_error_info warning = _( "Could not run initialization code because of a syntax " "error:\n" "%s at line %d col %d.") % (msg, lineno+1, offset+1) msg = gtk.MessageDialog(self.window_main, gtk.DIALOG_MODAL, gtk.MESSAGE_WARNING, gtk.BUTTONS_CLOSE, warning) _response = msg.run() msg.destroy() else: self.set_is_executing(True) if not self.is_executing: self.write('>>> ', COMMAND, PROMPT) def on_subp_terminated(self): if self.is_terminating: return # This may raise an exception if subprocess couldn't be started, # but hopefully if it was started once it will be started again. self.subp.start() self.set_is_executing(False) self.write('\n') self.write( '==================== New Session ====================\n', MESSAGE) self.output.start_new_section() self.configure_subp() self.run_init_code() def on_restart_subprocess(self, _widget): self.subp.kill() def on_stdout_recv(self, data): self.write_output(data, STDOUT) def on_stderr_recv(self, data): self.write_output(data, STDERR) def call_subp(self, funcname, *args): self.subp.send_object((funcname, args)) return self.subp.recv_object() def on_object_recv(self, obj): assert self.is_executing is_success, val_no, val_str, exception_string, rem_stdin = obj if not is_success: self.write_output(exception_string, EXCEPTION, onnewline=True) else: if val_str is not None: if val_no is not None: self.write_output('%d: ' % val_no, RESULT_IND, onnewline=True) self.write_output(val_str+'\n', RESULT) self.write('>>> ', COMMAND, PROMPT) self.set_is_executing(False) self.handle_rem_stdin(rem_stdin) def handle_rem_stdin(self, rem_stdin): """ Add the stdin text that was not processed to the source buffer. Remove it from the text buffer (we check that the STDIN text is consistent with rem_stdin - otherwise we give up) """ if not rem_stdin: return self.sourcebuffer.insert(self.sourcebuffer.get_start_iter(), rem_stdin) self.sv_scroll_cursor_onscreen() tb = self.textbuffer stdin = tb.get_tag_table().lookup(STDIN) it = tb.get_end_iter() if not it.ends_tag(stdin): it.backward_to_tag_toggle(stdin) while True: it2 = it.copy() it2.backward_to_tag_toggle(stdin) cur_stdin = tb.get_slice(it2, it, True) min_len = min(len(cur_stdin), len(rem_stdin)) assert min_len > 0 if cur_stdin[-min_len:] != rem_stdin[-min_len:]: debug("rem_stdin doesn't match what's in textview") break it2.forward_chars(len(cur_stdin)-min_len) tb.delete(it2, it) rem_stdin = rem_stdin[:-min_len] if not rem_stdin: break else: it = it2 # if rem_stdin is left, it2 must be at the beginning of the # stdin region. it2.backward_to_tag_toggle(stdin) assert it2.ends_tag(stdin) def on_execute_command(self, _widget): if self.is_executing: self.send_stdin() elif self.sourcebuffer.get_char_count() == 0: beep() else: self.execute_source() return True def on_interrupt(self, _widget): if self.is_executing: self.subp.interrupt() else: self.status_bar.set_status( _("A command isn't being executed currently")) beep() # History persistence def on_save_history(self, _widget): self.histpersist.save() def on_save_history_as(self, _widget): self.histpersist.save_as() def on_load_history(self, _widget): self.histpersist.load() # Recent history files def on_recent_manager_changed(self, _recent_manager): self.update_recent() def update_recent(self): """Update the menu and self.recent_filenames""" rman = self.recent_manager recent_items = [it for it in rman.get_items() if it.has_application('dreampie') and it.get_uri().startswith('file://')] recent_items.sort(key=lambda it: it.get_application_info('dreampie')[2], reverse=True) self.menuitem_recentsep.props.visible = (len(recent_items) > 0) for i, menuitem in enumerate(self.menuitem_recent): if i < len(recent_items): it = recent_items[i] fn = it.get_uri()[len('file://'):] menuitem.props.visible = True menuitem.props.label = "_%d %s" % (i, fn) self.recent_filenames[i] = fn else: menuitem.props.visible = False self.recent_filenames[i] = None def on_menuitem_recent(self, widget): num = self.menuitem_recent.index(widget) fn = self.recent_filenames[num] self.histpersist.load_filename(fn) # Discard history def discard_hist_before_tag(self, tag): """ Discard history before the given tag. If tag == COMMAND, this discards all history, and if tag == MESSAGE, this discards previous sessions. """ tb = self.textbuffer tag = tb.get_tag_table().lookup(tag) it = tb.get_end_iter() it.backward_to_tag_toggle(tag) if not it.begins_tag(tag): it.backward_to_tag_toggle(tag) tb.delete(tb.get_start_iter(), it) def on_discard_history(self, _widget): xml = glade.XML(gladefile, 'discard_hist_dialog') d = xml.get_widget('discard_hist_dialog') d.set_transient_for(self.window_main) d.set_default_response(gtk.RESPONSE_OK) previous_rad = xml.get_widget('previous_rad') all_rad = xml.get_widget('all_rad') previous_rad.set_group(all_rad) previous_rad.props.active = True r = d.run() d.destroy() if r == gtk.RESPONSE_OK: tb = self.textbuffer if previous_rad.props.active: self.discard_hist_before_tag(MESSAGE) else: self.discard_hist_before_tag(COMMAND) tb.insert_with_tags_by_name( tb.get_start_iter(), '================= History Discarded =================\n', MESSAGE) self.status_bar.set_status(_('History discarded.')) # Folding def on_section_menu_activate(self, widget): """ Called when the used clicked a section-related item in a popup menu. """ tb = self.textbuffer it = tb.get_iter_at_mark(self.popup_mark) r = self.folding.get_section_status(it) if r is None: # May happen if something was changed in the textbuffer between # popup and activation return typ, is_folded, start_it = r if widget is self.fold_unfold_section_menu: # Fold/Unfold if is_folded is None: # No point in folding. beep() elif not is_folded: self.folding.fold(typ, start_it) else: self.folding.unfold(typ, start_it) else: if typ == COMMAND: text = self.history.iter_get_command(start_it) else: end_it = start_it.copy() end_it.forward_to_tag_toggle(self.folding.get_tag(typ)) text = tb.get_text(start_it, end_it).decode('utf8') if sys.platform == 'win32': text = text.replace('\n', '\r\n') if widget is self.copy_section_menu: # Copy self.selection.clipboard.set_text(text) elif widget is self.view_section_menu: # View fd, fn = tempfile.mkstemp() os.write(fd, text) os.close(fd) viewer = eval(self.config.get('viewer')) self.spawn_and_forget('%s %s' % (viewer, fn)) elif widget is self.save_section_menu: # Save def func(filename): f = open(filename, 'wb') f.write(text) f.close() save_dialog(func, _("Choose where to save the section"), self.main_widget, _("All Files"), "*", None) else: assert False, "Unexpected widget" def spawn_and_forget(self, argv): """ Start a process and forget about it. """ if sys.platform == 'linux2': # We use a trick so as not to create zombie processes: we fork, # and let the fork spawn the process (actually another fork). The # (first) fork immediately exists, so the process we spawned is # made the child of process number 1. pid = os.fork() if pid == 0: _p = subprocess.Popen(argv, shell=True) os._exit(0) else: os.waitpid(pid, 0) else: _p = subprocess.Popen(argv, shell=True) def on_double_click(self, event): """If we are on a folded section, unfold it and return True, to avoid event propagation.""" tv = self.textview if tv.get_window(gtk.TEXT_WINDOW_TEXT) is not event.window: # Probably a click on the border or something return x, y = tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, int(event.x), int(event.y)) it = tv.get_iter_at_location(x, y) r = self.folding.get_section_status(it) if r is not None: typ, is_folded, start_it = r if is_folded: self.folding.unfold(typ, start_it) return True def on_fold_last(self, _widget): self.folding.fold_last() def on_unfold_last(self, _widget): self.folding.unfold_last() # Other events def on_show_completions(self, _widget): self.autocomplete.show_completions(is_auto=False, complete=False) def complete_attributes(self, expr): if self.is_executing: return None return self.call_subp(u'complete_attributes', expr) def complete_firstlevels(self): if self.is_executing: return None return self.call_subp(u'complete_firstlevels') def get_func_args(self, expr): if self.is_executing: return None return self.call_subp(u'get_func_args', expr) def find_modules(self, expr): if self.is_executing: return None return self.call_subp(u'find_modules', expr) def get_module_members(self, expr): if self.is_executing: return None return self.call_subp(u'get_module_members', expr) def complete_filenames(self, str_prefix, text, str_char, add_quote): if self.is_executing: return None return self.call_subp(u'complete_filenames', str_prefix, text, str_char, add_quote) def on_show_calltip(self, _widget): self.call_tips.show(is_auto=False) def get_func_doc(self, expr): if self.is_executing: return None return self.call_subp(u'get_func_doc', expr) def configure(self): """ Apply configuration. Called on initialization and after configuration was changed by the configuration dialog. """ config = self.config tv = self.textview; tb = self.textbuffer sv = self.sourceview; sb = self.sourcebuffer font_name = config.get('font') font = pango.FontDescription(font_name) tv.modify_font(font) sv.modify_font(font) cur_theme = self.config.get('current-theme') tags.apply_theme_text(tv, tb, tags.get_theme(self.config, cur_theme)) tags.apply_theme_source(sb, tags.get_theme(self.config, cur_theme)) self.set_window_default_size() command_defs = self.textbuffer.get_tag_table().lookup(COMMAND_DEFS) command_defs.props.invisible = config.get_bool('hide-defs') def on_preferences(self, _widget): cd = ConfigDialog(self.config, gladefile, self.window_main) r = cd.run() if r == gtk.RESPONSE_OK: self.configure() self.configure_subp() cd.destroy() def on_clear_reshist(self, _widget): self.call_subp(u'clear_reshist') self.status_bar.set_status(_("Result history cleared.")) def on_close(self, _widget, _event): self.quit() return True def on_quit(self, _widget): self.quit() def quit(self): if (self.textbuffer.get_modified() and self.config.get_bool('ask-on-quit')): xml = glade.XML(gladefile, 'quit_dialog') d = xml.get_widget('quit_dialog') dontask_check = xml.get_widget('dontask_check') d.set_transient_for(self.window_main) d.set_default_response(1) quit = (d.run() == 1) if quit and dontask_check.props.active: self.config.set_bool('ask-on-quit', False) self.config.save() d.destroy() else: quit = True if quit: self.is_terminating = True self.window_main.destroy() self.subp.kill() gtk.main_quit() def on_about(self, _widget): d = get_widget('about_dialog') d.set_transient_for(self.window_main) d.set_version(__version__) d.set_logo(gdk.pixbuf_new_from_file( path.join(data_dir, 'pixmaps', 'dreampie.png'))) d.run() d.destroy() def on_report_bug(self, _widget): webbrowser.open('https://bugs.launchpad.net/dreampie/+filebug') def on_homepage(self, _widget): webbrowser.open('http://dreampie.sourceforge.net/') def on_getting_started(self, _widget): self.show_getting_started_dialog() def show_getting_started_dialog(self): d = get_widget('getting_started_dialog') d.set_transient_for(self.window_main) d.run() d.destroy() def on_textview_button_press_event(self, _widget, event): if event.button == 3: self.show_popup_menu(event) return True if event.type == gdk._2BUTTON_PRESS: return self.on_double_click(event) def show_popup_menu(self, event): tv = self.textview tb = self.textbuffer if tb.get_has_selection(): self.popup_sel_menu.popup(None, None, None, event.button, event.get_time()) else: if tv.get_window(gtk.TEXT_WINDOW_TEXT) is not event.window: # Probably a click on the border or something return x, y = tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, int(event.x), int(event.y)) it = tv.get_iter_at_location(x, y) r = self.folding.get_section_status(it) if r is not None: typ, is_folded, _start_it = r if typ == OUTPUT: typ_s = _('Output Section') else: typ_s = _('Code Section') self.fold_unfold_section_menu.props.visible = ( is_folded is not None) self.fold_unfold_section_menu.child.props.label = ( _('Unfold %s') if is_folded else _('Fold %s')) % typ_s self.copy_section_menu.child.props.label = _('Copy %s') % typ_s self.view_section_menu.child.props.label = _('View %s') % typ_s self.save_section_menu.child.props.label = _('Save %s') % typ_s self.view_section_menu.props.visible = \ bool(eval(self.config.get('viewer'))) tb.move_mark(self.popup_mark, it) self.popup_nosel_menu.popup(None, None, None, event.button, event.get_time()) else: beep() def main(): usage = "%prog [options] [python-executable]" version = 'DreamPie %s' % __version__ parser = OptionParser(usage=usage, version=version) if sys.platform == 'win32': parser.add_option("--hide-console-window", action="store_true", dest="hide_console", help="Hide the console window") opts, args = parser.parse_args() if len(args) > 1: parser.error("Can accept at most one argument") if len(args) == 1: pyexec = args[0] elif 'dreampie' in sys.executable.lower(): # We are under py2exe. msg = gtk.MessageDialog( None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, _("DreamPie must be given the file name of a Python interpreter. " "Please create a shortcut to something like '%s " "--hide-console-window c:\\python26\\python.exe'.") % os.path.abspath(sys.argv[0])) _response = msg.run() msg.destroy() sys.exit(1) else: pyexec = sys.executable if sys.platform == 'win32' and opts.hide_console: from .hide_console_window import hide_console_window hide_console_window() gtk.widget_set_default_direction(gtk.TEXT_DIR_LTR) _dp = DreamPie(pyexec) gtk.main() dreampie-1.1.1/dreampielib/gui/gtkexcepthook.py0000644000175000017500000001502311341517113020236 0ustar noamnoam# vim: sw=4 ts=4: # # (c) 2003 Gustavo J A M Carneiro gjc at inescporto.pt # 2004-2005 Filip Van Raemdonck # # http://www.daa.com.au/pipermail/pygtk/2003-August/005775.html # Message-ID: <1062087716.1196.5.camel@emperor.homelinux.net> # "The license is whatever you want." import inspect, linecache, sys, traceback from repr import repr as safe_repr from cStringIO import StringIO from gettext import gettext as _ #from smtplib import SMTP import pygtk pygtk.require ('2.0') import gtk, pango feedback = None #def analyse (exctyp, value, tb): # trace = StringIO() # traceback.print_exception (exctyp, value, tb, None, trace) # return trace.getvalue() def lookup (name, frame, lcls): '''Find the value for a given name in the given frame''' if name in lcls: return 'local', lcls[name] elif name in frame.f_globals: return 'global', frame.f_globals[name] elif '__builtins__' in frame.f_globals: builtins = frame.f_globals['__builtins__'] if type (builtins) is dict: if name in builtins: return 'builtin', builtins[name] else: if hasattr (builtins, name): return 'builtin', getattr (builtins, name) return None, [] def analyse (exctyp, value, tb): import tokenize, keyword trace = StringIO() nlines = 1 frecs = inspect.getinnerframes (tb, nlines) trace.write ('Variables:\n') for frame, fname, lineno, funcname, _context, _cindex in frecs: trace.write (' File "%s", line %d, ' % (fname, lineno)) args, varargs, varkw, lcls = inspect.getargvalues (frame) def readline (lno=[lineno], *args): if args: print args try: return linecache.getline (fname, lno[0]) finally: lno[0] += 1 all, prev, name, scope = {}, None, '', None for ttype, tstr, _stup, _etup, _line in tokenize.generate_tokens (readline): if ttype == tokenize.NAME and tstr not in keyword.kwlist: if name: if name[-1] == '.': try: val = getattr (prev, tstr) except AttributeError: # XXX skip the rest of this identifier only break name += tstr else: assert not name and not scope scope, val = lookup (tstr, frame, lcls) name = tstr if val is not None: prev = val #print ' found', scope, 'name', name, 'val', val, 'in', prev, 'for token', tstr elif tstr == '.': if prev: name += '.' else: if name: all[name] = prev prev, name, scope = None, '', None if ttype == tokenize.NEWLINE: break trace.write (funcname + inspect.formatargvalues (args, varargs, varkw, lcls, formatvalue=lambda v: '=' + safe_repr (v)) + '\n') if len (all): trace.write (' %s\n' % str (all)) trace.write('\n') traceback.print_exception (exctyp, value, tb, None, trace) return trace.getvalue() def _info (exctyp, value, tb): try: import pdb except ImportError: # py2exe pdb = None if exctyp is KeyboardInterrupt: sys.exit(1) trace = None dialog = gtk.MessageDialog (parent=None, flags=0, type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_NONE) dialog.set_title (_("Bug Detected")) if gtk.check_version (2, 4, 0) is not None: dialog.set_has_separator (False) primary = _("A programming error has been detected.") secondary = _("Please report it by copying the information that appears " "when you click the \"Details\" button and submitting a bug " "report by choosing Help->Report a Problem. Thanks!") dialog.set_markup (primary) dialog.format_secondary_text (secondary) if feedback is not None: dialog.add_button (_("Report..."), 3) dialog.add_button (_("Details..."), 2) if pdb: dialog.add_button (_("Debug..."), 4) dialog.add_button (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE) dialog.add_button (gtk.STOCK_QUIT, 1) while True: resp = dialog.run() if resp == 4: pdb.post_mortem(tb) if resp == 3: # if trace == None: # trace = analyse (exctyp, value, tb) # # # TODO: prettyprint, deal with problems in sending feedback, &tc # try: # server = smtphost # except NameError: # server = 'localhost' # # message = 'From: buggy_application"\nTo: bad_programmer\nSubject: Exception feedback\n\n%s' % trace.getvalue() # # s = SMTP() # s.connect (server) # s.sendmail (feedback, (feedback,), message) # s.quit() break elif resp == 2: if trace == None: trace = analyse (exctyp, value, tb) # Show details... details = gtk.Dialog (_("Bug Details"), dialog, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE, )) details.set_property ("has-separator", False) textview = gtk.TextView(); textview.show() textview.set_editable (False) textview.modify_font (pango.FontDescription ("Monospace")) sw = gtk.ScrolledWindow(); sw.show() sw.set_policy (gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.add (textview) details.vbox.add (sw) textbuffer = textview.get_buffer() textbuffer.set_text (trace) monitor = gtk.gdk.screen_get_default ().get_monitor_at_window (dialog.window) area = gtk.gdk.screen_get_default ().get_monitor_geometry (monitor) w = area.width // 1.6 h = area.height // 1.6 details.set_default_size (int (w), int (h)) details.run() details.destroy() elif resp == 1 and gtk.main_level() > 0: sys.exit(1) else: break dialog.destroy() sys.excepthook = _info if __name__ == '__main__': class X (object): pass x = X() x.y = 'Test' x.z = x w = ' e' #feedback = 'developer@bigcorp.comp' #smtphost = 'mx.bigcorp.comp' 1, x.z.y, w raise Exception (x.z.y + w) dreampie-1.1.1/dreampielib/gui/autocomplete.py0000644000175000017500000002672211440107022020062 0ustar noamnoam# Copyright 2010 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['Autocomplete'] import string import re from .hyper_parser import HyperParser from .autocomplete_window import AutocompleteWindow, find_prefix_range from .beep import beep # This string includes all chars that may be in an identifier ID_CHARS = string.ascii_letters + string.digits + "_" ID_CHARS_DOT = ID_CHARS + '.' class Autocomplete(object): def __init__(self, sourceview, complete_attributes, complete_firstlevels, get_func_args, find_modules, get_module_members, complete_filenames, INDENT_WIDTH): self.sourceview = sourceview self.sourcebuffer = sourceview.get_buffer() self.complete_attributes = complete_attributes self.complete_firstlevels = complete_firstlevels self.get_func_args = get_func_args self.find_modules = find_modules self.get_module_members = get_module_members self.complete_filenames = complete_filenames self.INDENT_WIDTH = INDENT_WIDTH self.window = AutocompleteWindow(sourceview, self._on_complete) def show_completions(self, is_auto, complete): """ If complete is False, just show the comopletion list. If complete is True, complete as far as possible. If there's only one completion, don't show the window. If is_auto is True, don't beep if can't find completions. """ sb = self.sourcebuffer text = sb.get_slice(sb.get_start_iter(), sb.get_end_iter()).decode('utf8') index = sb.get_iter_at_mark(sb.get_insert()).get_offset() hp = HyperParser(text, index, self.INDENT_WIDTH) if hp.is_in_code(): line = text[text.rfind('\n', 0, index)+1:index].lstrip() if line.startswith('import '): res = self._complete_modules(line, is_auto) elif line.startswith('from '): if len((line+'x').split()) == 3: # The third word should be "import". res = self._complete_import(line) elif ' import ' not in line: res = self._complete_modules(line, is_auto) else: res = self._complete_module_members(line, is_auto) else: res = self._complete_attributes(text, index, hp, is_auto) elif hp.is_in_string(): res = self._complete_filenames(text, index, hp, is_auto) else: # Not in string and not in code res = None if res is not None: comp_prefix, public, private, is_case_insen = res else: if not is_auto: beep() return combined = public + private if is_case_insen: combined.sort(key = lambda s: s.lower()) combined_keys = [s.lower() for s in combined] else: combined.sort() combined_keys = combined comp_prefix_key = comp_prefix.lower() if is_case_insen else comp_prefix start, end = find_prefix_range(combined_keys, comp_prefix_key) if start == end: # No completions if not is_auto: beep() return if complete: # Find maximum prefix first = combined_keys[start] last = combined_keys[end-1] i = 0 while i < len(first) and i < len(last) and first[i] == last[i]: i += 1 if i > len(comp_prefix): sb.insert_at_cursor(combined[start][len(comp_prefix):i]) comp_prefix = first[:i] if end == start + 1: # Only one matchine completion - don't show the window return self.window.show(public, private, is_case_insen, len(comp_prefix)) def _complete_attributes(self, text, index, hp, is_auto): """ Return (comp_prefix, public, private, is_case_insen) (string, list, list, bool). If shouldn't complete - return None. """ # Check whether autocompletion is really appropriate if is_auto and text[index-1] != '.': return i = index while i and text[i-1] in ID_CHARS: i -= 1 comp_prefix = text[i:index] if i and text[i-1] == '.': hp.set_index(i-1) comp_what = hp.get_expression() if not comp_what: return if is_auto and '(' in comp_what: # Don't evaluate expressions which may contain a function call. return public_and_private = self.complete_attributes(comp_what) if public_and_private is None: # The subprocess is busy return public, private = public_and_private else: public_and_private = self.complete_firstlevels() if public_and_private is None: # The subprocess is busy return public, private = public_and_private # If we are inside a function call after a ',' or '(', # get argument names. if text[:i].rstrip()[-1:] in (',', '('): opener, _closer = hp.get_surrounding_brackets('(') if opener: hp.set_index(opener) expr = hp.get_expression() if expr and '(' not in expr: # Don't need to execute a function just to get arguments args = self.get_func_args(expr) if args is not None: public.extend(args) public.sort() is_case_insen = False return comp_prefix, public, private, is_case_insen def _complete_import(self, line): """ Complete the word "import"... """ i = len(line) while i and line[i-1] in ID_CHARS: i -= 1 comp_prefix = line[i:] public = ['import'] private = [] is_case_insen = False return comp_prefix, public, private, is_case_insen def _complete_modules(self, line, is_auto): """ line - the stripped line from its beginning to the cursor. Return (comp_prefix, public, private, is_case_insen) (string, list, list, bool). If shouldn't complete - return None. """ # Check whether autocompletion is really appropriate if is_auto and line[-1] != '.': return i = len(line) while i and line[i-1] in ID_CHARS: i -= 1 comp_prefix = line[i:] if i and line[i-1] == '.': i -= 1 j = i while j and line[j-1] in ID_CHARS_DOT: j -= 1 comp_what = line[j:i] else: comp_what = u'' modules = self.find_modules(comp_what) if modules is None: return None public = [s for s in modules if s[0] != '_'] private = [s for s in modules if s[0] == '_'] is_case_insen = False return comp_prefix, public, private, is_case_insen def _complete_module_members(self, line, is_auto): """ line - the stripped line from its beginning to the cursor. Return (comp_prefix, public, private, is_case_insen) (string, list, list, bool). If shouldn't complete - return None. """ # Check whether autocompletion is really appropriate if is_auto: return i = len(line) while i and line[i-1] in ID_CHARS: i -= 1 comp_prefix = line[i:] m = re.match(r'from\s+([\w.]+)\s+import', line) if m is None: return comp_what = m.group(1) public_and_private = self.get_module_members(comp_what) if public_and_private is None: return public, private = public_and_private is_case_insen = False return comp_prefix, public, private, is_case_insen def _complete_filenames(self, text, index, hp, is_auto): """ Return (comp_prefix, public, private, is_case_insen) (string, list, list, bool). If shouldn't complete - return None. """ # Check whether autocompletion is really appropriate if is_auto and text[index-1] not in '\\/': return str_start = hp.bracketing[hp.indexbracket][0] + 1 # Analyze string a bit pos = str_start - 1 str_char = text[pos] assert str_char in ('"', "'") if text[pos+1:pos+3] == str_char + str_char: # triple-quoted string - not for us return is_raw = pos > 0 and text[pos-1].lower() == 'r' if is_raw: pos -= 1 is_unicode = pos > 0 and text[pos-1].lower() == 'u' if is_unicode: pos -= 1 str_prefix = text[pos:str_start] # Do not open a completion list if after a single backslash in a # non-raw string if is_auto and text[index-1] == '\\' \ and not is_raw and not self._is_backslash_char(text, index-1): return # Find completion start - last '/' or real '\\' sep_ind = max(text.rfind('/', 0, index), text.rfind('\\', 0, index)) if sep_ind == -1 or sep_ind < str_start: # not found - prefix is all the string. comp_prefix_index = str_start elif text[sep_ind] == '\\' and not is_raw and not self._is_backslash_char(text, sep_ind): # Do not complete if the completion prefix contains a backslash. return else: comp_prefix_index = sep_ind+1 comp_prefix = text[comp_prefix_index:index] add_quote = not (len(text) > index and text[index] == str_char) res = self.complete_filenames( str_prefix, text[str_start:comp_prefix_index], str_char, add_quote) if res is None: return public, private, is_case_insen = res return comp_prefix, public, private, is_case_insen def _on_complete(self): # Called when the user completed. This is relevant if he completed # a dir name, so that another completion window will be opened. self.show_completions(is_auto=True, complete=False) @staticmethod def _is_backslash_char(string, index): """ Assuming that string[index] is a backslash, check whether it's a real backslash char or just an escape - if it has an odd number of preceding backslashes it's a real backslash """ assert string[index] == '\\' count = 0 while index-count > 0 and string[index-count-1] == '\\': count += 1 return (count % 2) == 1 dreampie-1.1.1/dreampielib/gui/call_tip_window.py0000644000175000017500000002471211441627034020547 0ustar noamnoam# Copyright 2010 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['CallTipWindow'] import gtk from gtk import gdk import pango from gobject import TYPE_NONE from .keyhandler import make_keyhandler_decorator, handle_keypress N_ROWS = 4 N_COLS = 80 # A decorator for managing sourceview key handlers keyhandlers = {} keyhandler = make_keyhandler_decorator(keyhandlers) class CallTipWindow(object): """ This class manages the calltip window, which displays function documentation. The window is shown and hidden upon request. """ # The window looks like this: Most of it is occupied by the text box. # Below we have a horizontal scroll bar, which is displayed only when # needed, and to the right there's a vertical scroll bar, which is always # displayed. Below it is a "resize grip", which lets you resize the window. # The window can be moved by dragging the main text area. # This looks pretty much like a ScrolledWindow, but a SW doesn't have the # resize grip, So we layout the widgets by ourselves, and handle scrolling. # We implement our own resize grip - for some reason, the resize grip of # a status bar doesn't work on popup windows. def __init__(self, sourceview): self.sourceview = sourceview # Widgets self.textview = tv = gtk.TextView() self.hscrollbar = hs = gtk.HScrollbar() self.vscrollbar = vs = gtk.VScrollbar() self.resizegrip = rg = gtk.EventBox() self.vbox1 = vb1 = gtk.VBox() self.vbox2 = vb2 = gtk.VBox() self.hbox = hb = gtk.HBox() self.window = win = gtk.Window(gtk.WINDOW_POPUP) self.char_width, self.char_height = self.get_char_size(tv) # Dragging vars self.is_dragging = None self.drag_x = None self.drag_y = None self.drag_left = None self.drag_top = None self.was_dragged = None # Resizing vars self.is_resizing = None self.resize_x = None self.resize_y = None self.resize_width = None self.resize_height = None self.was_displayed = False # Initialization style = gtk.rc_get_style_by_paths( tv.get_settings(), 'gtk-tooltip', 'gtk-tooltip', TYPE_NONE) tv.modify_text(gtk.STATE_NORMAL, style.fg[gtk.STATE_NORMAL]) tv.modify_base(gtk.STATE_NORMAL, style.bg[gtk.STATE_NORMAL]) tv.set_size_request(0,0) tv.props.editable = False tv.connect('event', self.on_textview_event) tv.set_scroll_adjustments(hs.props.adjustment, vs.props.adjustment) tv.connect('scroll-event', self.on_textview_scroll) hs.props.adjustment.connect('changed', self.on_hadj_changed) rg.add_events(gdk.BUTTON_PRESS_MASK | gdk.BUTTON_MOTION_MASK | gdk.BUTTON_RELEASE_MASK | gdk.EXPOSURE_MASK) rg.connect('event', self.on_resizegrip_event) rg.set_size_request(vs.size_request()[0], vs.size_request()[0]) vb1.pack_start(tv, True, True) vb1.pack_start(hs, False, False) vb2.pack_start(vs, True, True) vb2.pack_end(rg, False, False) hb.pack_start(vb1, True, True) hb.pack_start(vb2, False, False) win.add(hb) # Make all widgets except the window visible, so that a simple "show" # will suffice to show the window hb.show_all() # We define this handler here so that it will be defined before # the default key-press handler, and so will have higher priority. self.keypress_handler = self.sourceview.connect( 'key-press-event', self.on_keypress) self.sourceview.handler_block(self.keypress_handler) self.keypress_handler_blocked = True @staticmethod def get_char_size(textview): """ Get width, height of a character in pixels. """ tv = textview context = tv.get_pango_context() metrics = context.get_metrics(tv.style.font_desc, context.get_language()) width = pango.PIXELS(metrics.get_approximate_digit_width()) height = pango.PIXELS(metrics.get_ascent() + metrics.get_descent()) return width, height def on_textview_scroll(self, _widget, event): adj = self.vscrollbar.props.adjustment # Scrolling: 3 lines step = self.char_height * 3 if event.direction == gtk.gdk.SCROLL_UP: adj.props.value -= step elif event.direction == gtk.gdk.SCROLL_DOWN: adj.props.value = min(adj.props.value+step, adj.props.upper-adj.props.page_size) def on_hadj_changed(self, adj): self.hscrollbar.props.visible = (adj.props.page_size < adj.props.upper) def on_textview_event(self, _widget, event): if event.type == gdk.BUTTON_PRESS: self.is_dragging = True self.was_dragged = True self.drag_x = event.x_root self.drag_y = event.y_root self.drag_left, self.drag_top = self.window.get_position() return True elif event.type == gdk.MOTION_NOTIFY and self.is_dragging: left = self.drag_left + event.x_root - self.drag_x top = self.drag_top + event.y_root - self.drag_y self.window.move(int(left), int(top)) return True elif event.type == gdk.BUTTON_RELEASE: self.is_dragging = False def on_resizegrip_event(self, _widget, event): if event.type == gdk.BUTTON_PRESS: self.resize_x = event.x_root self.resize_y = event.y_root self.resize_width, self.resize_height = self.window.get_size() return True elif event.type == gdk.MOTION_NOTIFY: width = max(0, self.resize_width + event.x_root - self.resize_x) height = max(0, self.resize_height + event.y_root - self.resize_y) self.window.resize(int(width), int(height)) return True elif event.type == gdk.EXPOSE: rg = self.resizegrip win = rg.get_window() _x, _y, width, height, _depth = win.get_geometry() rg.get_style().paint_resize_grip( win, gtk.STATE_NORMAL, None, rg, None, gdk.WINDOW_EDGE_SOUTH_EAST, 0, 0, width, height) return True @keyhandler('Up', 0) def on_up(self): adj = self.vscrollbar.props.adjustment adj.props.value -= self.char_height return True @keyhandler('Down', 0) def on_down(self): adj = self.vscrollbar.props.adjustment adj.props.value = min(adj.props.value + self.char_height, adj.props.upper - adj.props.page_size) return True @keyhandler('Page_Up', 0) def on_page_up(self): self.textview.emit('move-viewport', gtk.SCROLL_PAGES, -1) return True @keyhandler('Page_Down', 0) def on_page_down(self): self.textview.emit('move-viewport', gtk.SCROLL_PAGES, 1) return True @keyhandler('Escape', 0) def on_esc(self): self.hide() # Don't return True - other things may be escaped too. def on_keypress(self, _widget, event): return handle_keypress(self, event, keyhandlers) def show(self, text, x, y): """ Show the window with the given text, its top-left corner at x-y. Decide on initial size. """ # The initial size is the minimum of: # * N_COLS*N_ROWS # * Whatever fits into the screen # * The actual content tv = self.textview vs = self.vscrollbar win = self.window text = text.replace('\0', '') # Fixes bug #611513 win.hide() tv.get_buffer().set_text(text) f_width = self.char_width * N_COLS f_height = self.char_height * N_ROWS s_width = gdk.screen_width() - x s_height = gdk.screen_height() - y # Get the size of the contents layout = tv.create_pango_layout(text) p_width, p_height = layout.get_size() c_width = pango.PIXELS(p_width) c_height = pango.PIXELS(p_height) del layout add_width = vs.size_request()[0] + 5 width = int(min(f_width, s_width, c_width) + add_width) height = int(min(f_height, s_height, c_height)) # Don't show the vertical scrollbar if the height is short enough. vs.props.visible = (height > vs.size_request()[1]) win.resize(width, height) win.move(x, y) self.hscrollbar.props.adjustment.props.value = 0 self.vscrollbar.props.adjustment.props.value = 0 self.sourceview.handler_unblock(self.keypress_handler) self.keypress_handler_blocked = False win.show() # This has to be done after the textview was displayed if not self.was_displayed: self.was_displayed = True hand = gdk.Cursor(gdk.HAND1) tv.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(hand) br_corner = gdk.Cursor(gdk.BOTTOM_RIGHT_CORNER) self.resizegrip.get_window().set_cursor(br_corner) def hide(self): self.window.hide() if not self.keypress_handler_blocked: self.sourceview.handler_block(self.keypress_handler) self.keypress_handler_blocked = True self.is_dragging = False self.is_resizing = False self.was_dragged = False def move_perhaps(self, x, y): """ Move the window to x-y, unless it was already manually dragged. """ if not self.was_dragged: self.window.move(x, y)dreampie-1.1.1/dreampielib/gui/config.py0000644000175000017500000001444611443131076016637 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['Config'] import sys import os from ConfigParser import SafeConfigParser from StringIO import StringIO from .odict import OrderedDict # We use expects-str-2, because expects-str had a different format (uses repr) # in DreamPie 1.1 default_config = """ [DreamPie] show-getting-started = True font=Courier New 10 current-theme = Dark pprint = True use-reshist = True reshist-size = 30 autofold = True autofold-numlines = 30 viewer = '' init-code = '' autoparen = True expects-str-2 = execfile chdir open run runeval ask-on-quit = True matplotlib-ia-switch = False matplotlib-ia-warn = True recall-1-char-commands = False hide-defs = False leave-code = False [Dark theme] is-active = True default-fg = white default-bg = black stdin-fg = white stdin-bg = black stdout-fg = #bcffff stdout-bg = black stderr-fg = #ff8080 stderr-bg = black result-ind-fg = blue result-ind-bg = black result-fg = #bcffff result-bg = black exception-fg = #ff8080 exception-bg = black prompt-fg = #e400b6 prompt-bg = black message-fg = yellow message-bg = black fold-message-fg = #a7a7a7 fold-message-bg = #003b6c keyword-fg = #ff7700 keyword-bg = black builtin-fg = #efcfcf builtin-bg = black string-fg = #00e400 string-bg = black number-fg = #aeacff number-bg = black comment-fg = #c9a3a0 comment-bg = black bracket-match-fg = white bracket-match-bg = darkblue stdin-fg-set = False stdin-bg-set = False stdout-fg-set = True stdout-bg-set = False stderr-fg-set = True stderr-bg-set = False result-ind-fg-set = True result-ind-bg-set = False result-fg-set = True result-bg-set = False exception-fg-set = True exception-bg-set = False prompt-fg-set = True prompt-bg-set = False message-fg-set = True message-bg-set = False fold-message-fg-set = True fold-message-bg-set = True keyword-fg-set = True keyword-bg-set = False builtin-fg-set = True builtin-bg-set = False string-fg-set = True string-bg-set = False number-fg-set = True number-bg-set = False comment-fg-set = True comment-bg-set = False bracket-match-fg-set = False bracket-match-bg-set = True [Light theme] is-active = True default-fg = black default-bg = white stdin-fg = #770000 stdin-bg = white stdout-fg = blue stdout-bg = white stderr-fg = red stderr-bg = white result-ind-fg = #808080 result-ind-bg = white result-fg = blue result-bg = white exception-fg = red exception-bg = white prompt-fg = #770000 prompt-bg = white message-fg = #008000 message-bg = white fold-message-fg = #404040 fold-message-bg = #b2ddff keyword-fg = #ff7700 keyword-bg = white builtin-fg = #0000ff builtin-bg = white string-fg = #00aa00 string-bg = white number-fg = blue number-bg = white comment-fg = #dd0000 comment-bg = white bracket-match-fg = black bracket-match-bg = lightblue stdin-fg-set = False stdin-bg-set = False stdout-fg-set = True stdout-bg-set = False stderr-fg-set = True stderr-bg-set = False result-ind-fg-set = True result-ind-bg-set = False result-fg-set = True result-bg-set = False exception-fg-set = True exception-bg-set = False prompt-fg-set = True prompt-bg-set = False message-fg-set = True message-bg-set = False fold-message-fg-set = True fold-message-bg-set = True keyword-fg-set = True keyword-bg-set = False builtin-fg-set = True builtin-bg-set = False string-fg-set = True string-bg-set = False number-fg-set = True number-bg-set = False comment-fg-set = True comment-bg-set = False bracket-match-fg-set = False bracket-match-bg-set = True """ def get_config_fn(): if sys.platform != 'win32': return os.path.expanduser('~/.dreampie') else: # On win32, expanduser doesn't work when the path includes unicode # chars. import ctypes MAX_PATH = 255 nFolder = 26 # CSIDL_APPDATA flags = 0 buf = ctypes.create_unicode_buffer(MAX_PATH) ctypes.windll.shell32.SHGetFolderPathW(None, nFolder, None, flags, buf) return os.path.join(buf.value, 'DreamPie') class Config(object): """ Manage configuration - a simple wrapper around SafeConfigParser. Upon initialization, the loaded file is updated with the default values. config.save() will save the current state. """ def __init__(self): self.filename = get_config_fn() try: self.parser = SafeConfigParser(dict_type=OrderedDict) except TypeError: # Python versions < 2.6 don't support dict_type self.parser = SafeConfigParser() f = StringIO(default_config) self.parser.readfp(f) self.parser.read(self.filename) self.save() def get(self, key, section='DreamPie'): return self.parser.get(section, key) def get_bool(self, key, section='DreamPie'): return self.parser.getboolean(section, key) def get_int(self, key, section='DreamPie'): return self.parser.getint(section, key) def set(self, key, value, section='DreamPie'): self.parser.set(section, key, value) def set_bool(self, key, value, section='DreamPie'): value_str = 'True' if value else 'False' self.set(key, value_str, section) def set_int(self, key, value, section='DreamPie'): if value != int(value): raise ValueError("Expected an int, got %r" % value) self.set(key, '%d' % value, section) def sections(self): return self.parser.sections() def has_section(self, section): return self.parser.has_section(section) def add_section(self, section): return self.parser.add_section(section) def remove_section(self, section): return self.parser.remove_section(section) def save(self): f = open(self.filename, 'w') self.parser.write(f) f.close() dreampie-1.1.1/dreampielib/gui/output.py0000644000175000017500000001472211345771343016737 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['Output'] import re from StringIO import StringIO from .tags import OUTPUT # This RE is used to remove chars that won't be displayed from the data string. remove_cr_re = re.compile(r'\n[^\n]*\r') # Match ANSI escapes. See http://en.wikipedia.org/wiki/ANSI_escape_code ansi_escape_re = re.compile(r'\x1b\[[^@-~]*?[@-~]') # Length after which to break a line with a '\r' - a character which we # ignore when copying. BREAK_LEN = 1600 class Output(object): """ Manage writing output to the text view. See a long documentation string in tags.py for more information about the model. """ def __init__(self, textview): self.textview = textview self.textbuffer = tb = textview.get_buffer() # A mark where new output should be written self.mark = tb.create_mark(None, tb.get_end_iter(), left_gravity=True) # If the real output doesn't end with a newline, we add "our own", # because we want the output section to always end with a newline. # This newline will be deleted if more output is written. # If we did, self.added_newline is True. self.added_newline = False # Was something written at all in this section? self.was_something_written = False # Does the output end with a cr? (If it does, the last line will be # deleted unless the next output starts with a lf) self.is_cr = False def start_new_section(self): tb = self.textbuffer it = tb.get_end_iter() tb.move_mark(self.mark, it) self.added_newline = False self.was_something_written = False self.is_cr = False def write(self, data, tag_names, onnewline=False, addbreaks=True): """ Write data (unicode string) to the text buffer, marked with tag_names. (tag_names can be either a string or a list of strings) If onnewline is True, will add a newline if the output until now doesn't end with one. If addbreaks is True, '\r' chars will be added so that lines will be broken and output will not burden the textview. Return a TextIter pointing to the end of the written text. """ tb = self.textbuffer if isinstance(tag_names, basestring): tag_names = [tag_names] if not data: return if self.added_newline: if onnewline: # If we added a newline, it means that the section didn't end # with a newline, so we need to add one. data = '\n' + data it = tb.get_iter_at_mark(self.mark) it2 = it.copy() it2.backward_char() assert tb.get_text(it2, it) == '\n' tb.delete(it2, it) self.added_newline = False # Keep lines if after the cr there was no data before the lf. # Since that's the normal Windows newline, it's very important. data = data.replace('\r\n', '\n') # Remove ANSI escapes data = ansi_escape_re.sub('', data) # Remove NULL chars data = data.replace('\0', '') has_trailing_cr = data.endswith('\r') if has_trailing_cr: data = data[:-1] if data.startswith('\n'): # Don't delete the last line if it ended with a cr but this data # starts with a lf. self.is_cr = False # Remove chars that will not be displayed from data. No crs will be left # after the first lf. data = remove_cr_re.sub('\n', data) cr_pos = data.rfind('\r') if (self.is_cr or cr_pos != -1) and self.was_something_written: # Delete last written line it = tb.get_iter_at_mark(self.mark) output_start = it.copy() output_tag = tb.get_tag_table().lookup(OUTPUT) output_start.backward_to_tag_toggle(output_tag) assert output_start.begins_tag(output_tag) r = it.backward_search('\n', 0, output_start) if r is not None: _before_newline, after_newline = r else: # Didn't find a newline - delete from beginning of output after_newline = output_start tb.delete(after_newline, it) # Remove data up to \r. if cr_pos != -1: data = data[cr_pos+1:] if addbreaks: # We DO use \r characters as linebreaks after BREAK_LEN chars, which # are not copied. f = StringIO() pos = 0 copied_pos = 0 col = tb.get_iter_at_mark(self.mark).get_line_offset() next_newline = data.find('\n', pos) if next_newline == -1: next_newline = len(data) while pos < len(data): if next_newline - pos + col > BREAK_LEN: pos = pos + BREAK_LEN - col f.write(data[copied_pos:pos]) f.write('\r') copied_pos = pos col = 0 else: pos = next_newline + 1 col = 0 next_newline = data.find('\n', pos) if next_newline == -1: next_newline = len(data) f.write(data[copied_pos:]) data = f.getvalue() it = tb.get_iter_at_mark(self.mark) tb.insert_with_tags_by_name(it, data, OUTPUT, *tag_names) if not data.endswith('\n'): tb.insert_with_tags_by_name(it, '\n', OUTPUT) self.added_newline = True # Move mark to after the written text tb.move_mark(self.mark, it) self.is_cr = has_trailing_cr self.was_something_written = True return it dreampie-1.1.1/dreampielib/gui/folding.py0000644000175000017500000002000611341450060016773 0ustar noamnoam# Copyright 2010 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . all = ['Folding'] from .tags import OUTPUT, COMMAND, FOLDED, FOLD_MESSAGE from .beep import beep # Maybe someday we'll want translations... _ = lambda s: s class Folding(object): """ Support folding and unfolding of output and code sections. """ def __init__(self, textbuffer, LINE_LEN): self.textbuffer = tb = textbuffer self.LINE_LEN = LINE_LEN # Mark the bottom-most section which was unfolded, so as not to # auto-fold it. self.last_unfolded_mark = tb.create_mark( 'last-folded', tb.get_start_iter(), left_gravity=True) tt = self.textbuffer.get_tag_table() self.fold_message_tag = tt.lookup(FOLD_MESSAGE) self.output_tag = tt.lookup(OUTPUT) self.command_tag = tt.lookup(COMMAND) self.tags = {OUTPUT: self.output_tag, COMMAND: self.command_tag} def get_section_status(self, it): """ Get an iterator of the sourcebuffer. Return a tuple: (typ, is_folded, start_it) typ: one of tags.OUTPUT, tags.COMMAND is_folded: boolean (is folded), or None if not folded but too short to fold (1 line or less). start_it: An iterator pointing to the beginning of the section. If it isn't in an OUTPUT or COMMAND section, return None. """ it = it.copy() # The iterator is in an OUTPUT section if it's either tagged with # OUTPUT or if it's inside a FOLD_MESSAGE which goes right after # the OUTPUT tagged text. The same goes for COMMAND - note that STDIN # is marked with both COMMAND and OUTPUT and is considered output, so # we check OUTPUT first. # A section is folded iff it's followed by a FOLD_MESSAGE. if it.has_tag(self.fold_message_tag): if not it.begins_tag(self.fold_message_tag): it.backward_to_tag_toggle(self.fold_message_tag) if it.ends_tag(self.output_tag): typ = OUTPUT elif it.ends_tag(self.command_tag): typ = COMMAND else: assert False, "FOLD_MESSAGE doesn't follow OUTPUT/COMMAND" it.backward_to_tag_toggle(self.tags[typ]) return (typ, True, it) else: if it.has_tag(self.output_tag) or it.ends_tag(self.output_tag): typ = OUTPUT tag = self.output_tag elif it.has_tag(self.command_tag) or it.ends_tag(self.command_tag): typ = COMMAND tag = self.command_tag else: return None if not it.ends_tag(tag): it.forward_to_tag_toggle(tag) end_it = it.copy() is_folded = end_it.has_tag(self.fold_message_tag) it.backward_to_tag_toggle(tag) if not is_folded: n_lines = self._count_lines(it, end_it) if n_lines <= 1: is_folded = None return (typ, is_folded, it) def _count_lines(self, start_it, end_it): return max(end_it.get_line()-start_it.get_line(), (end_it.get_offset()-start_it.get_offset())//self.LINE_LEN) def fold(self, typ, start_it): """ Get an iterator pointing to the beginning of an unfolded OUTPUT/COMMAND section. Fold it. """ tb = self.textbuffer # Move end_it to the end of the section end_it = start_it.copy() end_it.forward_to_tag_toggle(self.tags[typ]) n_lines = self._count_lines(start_it, end_it) # Move 'it' to the end of the first line (this is where we start hiding) it = start_it.copy() it.forward_chars(self.LINE_LEN) first_line = start_it.get_slice(it).decode('utf8') newline_pos = first_line.find('\n') if newline_pos != -1: it.backward_chars(len(first_line)-newline_pos) # Hide tb.apply_tag_by_name(FOLDED, it, end_it) # Add message tb.insert_with_tags_by_name( end_it, _("[About %d more lines. Double-click to unfold]\n") % (n_lines-1), FOLD_MESSAGE) def unfold(self, typ, start_it): """ Get an iterator pointing to the beginning of an unfolded OUTPUT/COMMAND section. Unfold it. """ tb = self.textbuffer last_unfolded_it = tb.get_iter_at_mark(self.last_unfolded_mark) if start_it.compare(last_unfolded_it) > 0: tb.move_mark(self.last_unfolded_mark, start_it) it = start_it.copy() it.forward_to_tag_toggle(self.tags[typ]) tb.remove_tag_by_name(FOLDED, start_it, it) it2 = it.copy() it2.forward_to_tag_toggle(self.fold_message_tag) assert it2.ends_tag(self.fold_message_tag) tb.delete(it, it2) def autofold(self, it, numlines): """ Get an iterator to a recently-written output section. If it is folded, update the fold message and hide what was written. It it isn't folded, then if the number of lines exceeds numlines and the section wasn't manually unfolded, fold it. """ tb = self.textbuffer typ, is_folded, start_it = self.get_section_status(it) if is_folded: # Just unfold and fold. We create a mark because start_iter is # invalidated start_it_mark = tb.create_mark(None, start_it, left_gravity=True) self.unfold(typ, start_it) start_it = tb.get_iter_at_mark(start_it_mark) tb.delete_mark(start_it_mark) self.fold(typ, start_it) else: last_unfolded_it = tb.get_iter_at_mark(self.last_unfolded_mark) if not start_it.equal(last_unfolded_it): end_it = start_it.copy() end_it.forward_to_tag_toggle(self.tags[typ]) n_lines = self._count_lines(start_it, end_it) if n_lines >= numlines: self.fold(typ, start_it) def get_tag(self, typ): """Return the gtk.TextTag for a specific typ string.""" return self.tags[typ] def fold_last(self): """ Fold last unfolded output section. """ tb = self.textbuffer it = tb.get_end_iter() while True: r = it.backward_to_tag_toggle(self.output_tag) if not r: # Didn't find something to fold beep() break if it.begins_tag(self.output_tag): typ, is_folded, start_it = self.get_section_status(it) if is_folded is not None and not is_folded: self.fold(typ, start_it) break def unfold_last(self): """ Unfold last folded output section. """ tb = self.textbuffer it = tb.get_end_iter() while True: r = it.backward_to_tag_toggle(self.output_tag) if not r: # Didn't find something to fold beep() return if not it.begins_tag(self.output_tag): continue typ, is_folded, start_it = self.get_section_status(it) if is_folded: self.unfold(typ, start_it) return dreampie-1.1.1/dreampielib/gui/SimpleGladeApp.py0000644000175000017500000002707111335725017020223 0ustar noamnoam""" SimpleGladeApp.py Module that provides an object oriented abstraction to pygtk and libglade. Copyright (C) 2004 Sandino Flores Moreno """ # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA import os import sys import re import tokenize import gtk _ = gtk; del _ # Make pydev quiet import gtk.glade import weakref import inspect __version__ = "1.0" __author__ = 'Sandino "tigrux" Flores-Moreno' def bindtextdomain(app_name, locale_dir=None): """ Bind the domain represented by app_name to the locale directory locale_dir. It has the effect of loading translations, enabling applications for different languages. app_name: a domain to look for translations, tipically the name of an application. locale_dir: a directory with locales like locale_dir/lang_isocode/LC_MESSAGES/app_name.mo If omitted or None, then the current binding for app_name is used. """ try: import locale import gettext locale.setlocale(locale.LC_ALL, "") gtk.glade.bindtextdomain(app_name, locale_dir) gettext.install(app_name, locale_dir, unicode=1) except (IOError,locale.Error), e: print "Warning", app_name, e __builtins__.__dict__["_"] = lambda x : x class SimpleGladeApp: def __init__(self, path, root=None, domain=None, **kwargs): """ Load a glade file specified by glade_filename, using root as root widget and domain as the domain for translations. If it receives extra named arguments (argname=value), then they are used as attributes of the instance. path: path to a glade filename. If glade_filename cannot be found, then it will be searched in the same directory of the program (sys.argv[0]) root: the name of the widget that is the root of the user interface, usually a window or dialog (a top level widget). If None or ommited, the full user interface is loaded. domain: A domain to use for loading translations. If None or ommited, no translation is loaded. **kwargs: a dictionary representing the named extra arguments. It is useful to set attributes of new instances, for example: glade_app = SimpleGladeApp("ui.glade", foo="some value", bar="another value") sets two attributes (foo and bar) to glade_app. """ if os.path.isfile(path): self.glade_path = path else: glade_dir = os.path.dirname( sys.argv[0] ) self.glade_path = os.path.join(glade_dir, path) for key, value in kwargs.items(): try: setattr(self, key, weakref.proxy(value) ) except TypeError: setattr(self, key, value) self.glade = None self.install_custom_handler(self.custom_handler) self.glade = self.create_glade(self.glade_path, root, domain) if root: self.main_widget = self.get_widget(root) else: self.main_widget = None self.normalize_names() self.add_callbacks(self) self.new() def __repr__(self): class_name = self.__class__.__name__ if self.main_widget: root = gtk.Widget.get_name(self.main_widget) repr = '%s(path="%s", root="%s")' % (class_name, self.glade_path, root) else: repr = '%s(path="%s")' % (class_name, self.glade_path) return repr def new(self): """ Method called when the user interface is loaded and ready to be used. At this moment, the widgets are loaded and can be refered as self.widget_name """ pass def add_callbacks(self, callbacks_proxy): """ It uses the methods of callbacks_proxy as callbacks. The callbacks are specified by using: Properties window -> Signals tab in glade-2 (or any other gui designer like gazpacho). Methods of classes inheriting from SimpleGladeApp are used as callbacks automatically. callbacks_proxy: an instance with methods as code of callbacks. It means it has methods like on_button1_clicked, on_entry1_activate, etc. """ self.glade.signal_autoconnect(callbacks_proxy) def normalize_names(self): """ It is internally used to normalize the name of the widgets. It means a widget named foo:vbox-dialog in glade is refered self.vbox_dialog in the code. It also sets a data "prefixes" with the list of prefixes a widget has for each widget. """ for widget in self.get_widgets(): widget_name = gtk.Widget.get_name(widget) prefixes_name_l = widget_name.split(":") prefixes = prefixes_name_l[ : -1] widget_api_name = prefixes_name_l[-1] widget_api_name = "_".join( re.findall(tokenize.Name, widget_api_name) ) gtk.Widget.set_name(widget, widget_api_name) if hasattr(self, widget_api_name): raise AttributeError("instance %s already has an attribute %s" % (self,widget_api_name)) else: setattr(self, widget_api_name, widget) if prefixes: gtk.Widget.set_data(widget, "prefixes", prefixes) def add_prefix_actions(self, prefix_actions_proxy): """ By using a gui designer (glade-2, gazpacho, etc) widgets can have a prefix in theirs names like foo:entry1 or foo:label3 It means entry1 and label3 has a prefix action named foo. Then, prefix_actions_proxy must have a method named prefix_foo which is called everytime a widget with prefix foo is found, using the found widget as argument. prefix_actions_proxy: An instance with methods as prefix actions. It means it has methods like prefix_foo, prefix_bar, etc. """ prefix_s = "prefix_" prefix_pos = len(prefix_s) is_method = lambda t : callable( t[1] ) is_prefix_action = lambda t : t[0].startswith(prefix_s) drop_prefix = lambda (k,w): (k[prefix_pos:],w) members_t = inspect.getmembers(prefix_actions_proxy) methods_t = filter(is_method, members_t) prefix_actions_t = filter(is_prefix_action, methods_t) prefix_actions_d = dict( map(drop_prefix, prefix_actions_t) ) for widget in self.get_widgets(): prefixes = gtk.Widget.get_data(widget, "prefixes") if prefixes: for prefix in prefixes: if prefix in prefix_actions_d: prefix_action = prefix_actions_d[prefix] prefix_action(widget) def custom_handler(self, _glade, function_name, _widget_name, str1, str2, int1, int2): """ Generic handler for creating custom widgets, internally used to enable custom widgets (custom widgets of glade). The custom widgets have a creation function specified in design time. Those creation functions are always called with str1,str2,int1,int2 as arguments, that are values specified in design time. Methods of classes inheriting from SimpleGladeApp are used as creation functions automatically. If a custom widget has create_foo as creation function, then the method named create_foo is called with str1,str2,int1,int2 as arguments. """ try: handler = getattr(self, function_name) return handler(str1, str2, int1, int2) except AttributeError: return None def gtk_widget_show(self, widget, *_args): """ Predefined callback. The widget is showed. Equivalent to widget.show() """ widget.show() def gtk_widget_hide(self, widget, *_args): """ Predefined callback. The widget is hidden. Equivalent to widget.hide() """ widget.hide() def gtk_widget_grab_focus(self, widget, *_args): """ Predefined callback. The widget grabs the focus. Equivalent to widget.grab_focus() """ widget.grab_focus() def gtk_widget_destroy(self, widget, *_args): """ Predefined callback. The widget is destroyed. Equivalent to widget.destroy() """ widget.destroy() def gtk_window_activate_default(self, widget, *_args): """ Predefined callback. The default widget of the window is activated. Equivalent to window.activate_default() """ widget.activate_default() def gtk_true(self, *_args): """ Predefined callback. Equivalent to return True in a callback. Useful for stopping propagation of signals. """ return True def gtk_false(self, *_args): """ Predefined callback. Equivalent to return False in a callback. """ return False def gtk_main_quit(self, *_args): """ Predefined callback. Equivalent to self.quit() """ self.quit() def main(self): """ Starts the main loop of processing events. The default implementation calls gtk.main() Useful for applications that needs a non gtk main loop. For example, applications based on gstreamer needs to override this method with gst.main() Do not directly call this method in your programs. Use the method run() instead. """ gtk.main() def quit(self): """ Quit processing events. The default implementation calls gtk.main_quit() Useful for applications that needs a non gtk main loop. For example, applications based on gstreamer needs to override this method with gst.main_quit() """ gtk.main_quit() def run(self): """ Starts the main loop of processing events checking for Control-C. The default implementation checks wheter a Control-C is pressed, then calls on_keyboard_interrupt(). Use this method for starting programs. """ try: self.main() except KeyboardInterrupt: self.on_keyboard_interrupt() def on_keyboard_interrupt(self): """ This method is called by the default implementation of run() after a program is finished by pressing Control-C. """ pass def install_custom_handler(self, custom_handler): gtk.glade.set_custom_handler(custom_handler) def create_glade(self, glade_path, root, domain): return gtk.glade.XML(glade_path, root, domain) def get_widget(self, widget_name): return self.glade.get_widget(widget_name) def get_widgets(self): return self.glade.get_widget_prefix("") dreampie-1.1.1/dreampielib/gui/status_bar.py0000644000175000017500000000545511336202766017547 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['StatusBar'] try: from glib import timeout_add_seconds, source_remove except ImportError: timeout_add_seconds = None # In PyGObject 2.14, it's in gobject. from gobject import timeout_add, source_remove class StatusBar(object): """ Add messages to the status bar which disappear when the contents is changed. """ def __init__(self, sourcebuffer, statusbar): self.sourcebuffer = sourcebuffer self.statusbar = statusbar # id of a message displayed in the status bar to be removed when # the contents of the source buffer is changed self.sourcebuffer_status_id = None self.sourcebuffer_changed_handler_id = None self.timeout_handle = None def set_status(self, message): """Set a message in the status bar to be removed when the contents of the source buffer is changed""" if self.sourcebuffer_status_id is not None: self.clear_status() self.sourcebuffer_status_id = self.statusbar.push(0, message) self.sourcebuffer_changed_handler_id = \ self.sourcebuffer.connect('changed', self.on_sourcebuffer_changed) if timeout_add_seconds is not None: timeout_add_seconds(10, self.on_timeout) else: timeout_add(10000, self.on_timeout) def clear_status(self): try: self.statusbar.remove_message(0, self.sourcebuffer_status_id) except AttributeError: # Support older PyGTK self.statusbar.remove(0, self.sourcebuffer_status_id) self.sourcebuffer_status_id = None self.sourcebuffer.disconnect(self.sourcebuffer_changed_handler_id) self.sourcebuffer_changed_handler_id = None if self.timeout_handle is not None: source_remove(self.timeout_handle) self.timeout_handle = None def on_sourcebuffer_changed(self, _widget): self.clear_status() return False def on_timeout(self): if self.sourcebuffer_status_id is not None: self.clear_status() return False dreampie-1.1.1/dreampielib/gui/write_command.py0000644000175000017500000001111411335725230020210 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['write_command'] import tokenize import keyword from .tags import PROMPT, COMMAND, COMMAND_DEFS, COMMAND_SEP from .tags import KEYWORD, BUILTIN, STRING, NUMBER, COMMENT keywords = set(keyword.kwlist) builtins = set(__builtins__) def write_command(write, command): """Write a command to the textview, with syntax highlighting and "...". """ lines = [x+'\n' for x in command.split('\n')] # Remove last newline - we don't tag it with COMMAND to separate commands lines[-1] = lines[-1][:-1] defs_lines = get_defs_lines(lines) tok_iter = tokenize.generate_tokens(iter(lines).next) highs = [] for typ, token, (sline, scol), (eline, ecol), line in tok_iter: tag = None if typ == tokenize.NAME: if token in keywords: tag = KEYWORD elif token in builtins: tag = BUILTIN elif typ == tokenize.STRING: tag = STRING elif typ == tokenize.NUMBER: tag = NUMBER elif typ == tokenize.COMMENT: tag = COMMENT if tag is not None: highs.append((tag, sline-1, scol, eline-1, ecol)) # Adding a terminal highlight will help us avoid end-cases highs.append((None, len(lines), 0, len(lines), 0)) def my_write(s, is_defs, *tags): if not is_defs: write(s, *tags) else: write(s, COMMAND_DEFS, *tags) high_pos = 0 cur_high = highs[0] in_high = False for lineno, line in enumerate(lines): is_defs = defs_lines[lineno] if lineno != 0: my_write('... ', is_defs, COMMAND, PROMPT) col = 0 while col < len(line): if not in_high: if cur_high[1] == lineno: if cur_high[2] > col: my_write(line[col:cur_high[2]], is_defs, COMMAND) col = cur_high[2] in_high = True else: my_write(line[col:], is_defs, COMMAND) col = len(line) else: if cur_high[3] == lineno: if cur_high[4] > col: my_write(line[col:cur_high[4]], is_defs, COMMAND, cur_high[0]) col = cur_high[4] in_high = False high_pos += 1 cur_high = highs[high_pos] else: my_write(line[col:], is_defs, COMMAND, cur_high[0]) col = len(line) write('\n', COMMAND) write('\r', COMMAND_SEP) def get_defs_lines(lines): """ Get a list of lines - strings with Python code. Return a list of booleans - whether a line should be hidden when hide-defs is True, because it's a part of a function or class definitions. """ # return value defs_lines = [False for _line in lines] # Last line with a 'def' or 'class' NAME last_def_line = -2 # Indentation depth - when reaches 0, we are back in a non-filtered area. cur_depth = 0 # First line of current filtered area first_filtered_line = None tok_iter = tokenize.generate_tokens(iter(lines).next) for typ, token, (sline, _scol), (_eline, _ecol), _line in tok_iter: if cur_depth > 0: if typ == tokenize.INDENT: cur_depth += 1 elif typ == tokenize.DEDENT: cur_depth -= 1 if cur_depth == 0: for i in range(first_filtered_line, sline-1): defs_lines[i] = True first_filtered_line = None else: if typ == tokenize.NAME and token in ('def', 'class'): last_def_line = sline elif typ == tokenize.INDENT and sline == last_def_line + 1: cur_depth = 1 first_filtered_line = sline-1 return defs_lines dreampie-1.1.1/dreampielib/gui/subprocess_handler.py0000644000175000017500000002224011417110135021240 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['SubprocessHandler'] import sys import os import time if sys.platform != 'win32': import signal else: import ctypes from .subprocess_interact import Popen, PIPE from select import select import socket import random from logging import debug import gobject from ..common.objectstream import send_object, recv_object _ = lambda s: s START_TIMEOUT = 30 # seconds class StartError(IOError): """Error when starting subprocess""" pass class StartTerminatedError(StartError): """Start subprocess failed because process terminated.""" def __init__(self, rc, output): self.rc = rc self.output = output def __str__(self): r = _("Subprocess terminated with return code %d.") % self.rc if self.output: r += _("\nSubprocess wrote:\n%s") % self.output return r class StartTimeoutError(StartError): """Start subprocess failed because timeout elapsed.""" def __init__(self, timeout, output): self.timeout = timeout self.output = output def __str__(self): r = _("Subprocess didn't call back in %s seconds.") % self.timeout if self.output: r += _("\nSubprocess wrote:\n%s") % self.output return r class SubprocessHandler(object): """ Manage interaction with the subprocess. The communication, besides stdout, stderr and stdin, goes like this: You can call a function, and get a return value. (This sends over the tuple with the function name and parameters, and waits for the next object, which is the return value.) You can also get objects asyncronically. (This happens when not waiting for a function's return value.) """ def __init__(self, pyexec, data_dir, on_stdout_recv, on_stderr_recv, on_object_recv, on_subp_terminated): self._pyexec = pyexec self._data_dir = data_dir self._on_stdout_recv = on_stdout_recv self._on_stderr_recv = on_stderr_recv self._on_object_recv = on_object_recv self._on_subp_terminated = on_subp_terminated self._sock = None # self._popen is None when there's no subprocess self._popen = None self._last_kill_time = 0 # I know that polling isn't the best way, but on Windows you have # no choice, and it allows us to do it all by ourselves, not use # gobject's functionality. gobject.timeout_add(10, self._manage_subp) def start(self): if self._popen is not None: raise ValueError("Subprocess is already living") # Find a socket to listen to ports = range(10000, 10100) random.shuffle(ports) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) for port in ports: #debug("Trying to listen on port %d..." % port) try: s.bind(('localhost', port)) except socket.error: #debug("Failed.") pass else: #debug("Ok.") break else: raise IOError("Couldn't find a port to bind to") # Now the socket is bound to port. #debug("Spawning subprocess") env = os.environ.copy() env['PYTHONUNBUFFERED'] = '1' env['PYTHONIOENCODING'] = 'UTF-8' script = os.path.join(self._data_dir, 'dreampie', 'subp_main.py') # The -S switch causes the subprocess to not automatically import # site.py. This is done so that the subprocess will be able to call # sys.setdefaultencoding('UTF-8') before importing site, and this is # needed because Python 2.5 ignores the PYTHONIOENCODING variable. # Hopefully it won't cause problems. popen = Popen([self._pyexec, '-S', script, str(port)], stdin=PIPE, stdout=PIPE, stderr=PIPE, env=env) #debug("Waiting for the subprocess to connect") s.listen(1) # We wait for the client to connect, but we also poll stdout and stderr, # and if it writes something then we report an error. s.settimeout(0.1) start_time = time.time() while True: try: self._sock, _addr = s.accept() except socket.timeout: pass else: break rc = popen.poll() if rc is not None: out = (popen.recv() or '') + (popen.recv_err() or '') raise StartTerminatedError(rc, out) if time.time() - start_time > START_TIMEOUT: out = (popen.recv() or '') + (popen.recv_err() or '') raise StartTimeoutError(START_TIMEOUT, out) self._sock.setblocking(True) #debug("Connected to addr %r." % (addr,)) s.close() self._popen = popen def _manage_subp(self): popen = self._popen if popen is None: # Just continue looping - there's no subprocess. return True # Check if exited rc = popen.poll() if rc is not None: if time.time() - self._last_kill_time > 10: debug("Process terminated unexpectedly with rc %r" % rc) self._sock.close() self._sock = None self._popen = None self._on_subp_terminated() return True # Read from stdout r = popen.recv() if r: self._on_stdout_recv(r.decode('utf8', 'replace')) # Read from stderr r = popen.recv_err() if r: self._on_stderr_recv(r.decode('utf8', 'replace')) # Read from socket if select([self._sock], [], [], 0)[0]: try: obj = recv_object(self._sock) except IOError: # Could happen when subprocess exits. See bug #525358. # We give the subprocess a second. If it shuts down, we ignore # the exception, since on the next round we will handle that. # Otherwise, the exception remains unexplained so we re-raise. time.sleep(1) if popen.poll() is None: raise else: self._on_object_recv(obj) return True def send_object(self, obj): """Send an object to the subprocess""" if self._popen is None: raise ValueError("Subprocess not living") send_object(self._sock, obj) def recv_object(self): """Wait for an object from the subprocess and return it""" if self._popen is None: raise ValueError("Subprocess not living") return recv_object(self._sock) def write(self, data): """Write data to stdin""" if self._popen is None: raise ValueError("Subprocess not living") self._popen.stdin.write(data.encode('utf8')) def kill(self): """Kill the subprocess. If the event loop continues, will start another one.""" if self._popen is None: raise ValueError("Subprocess not living") if sys.platform != 'win32': # Send SIGTERM, and if the process didn't terminate within 1 second, # send SIGKILL. os.kill(self._popen.pid, signal.SIGTERM) killtime = time.time() while True: rc = self._popen.poll() if rc is not None: break if time.time() - killtime > 1: os.kill(self._popen.pid, signal.SIGKILL) break time.sleep(0.1) else: kernel32 = ctypes.windll.kernel32 PROCESS_TERMINATE = 1 handle = kernel32.OpenProcess(PROCESS_TERMINATE, False, self._popen.pid) kernel32.TerminateProcess(handle, -1) kernel32.CloseHandle(handle) self._last_kill_time = time.time() def interrupt(self): if self._popen is None: raise ValueError("Subprocess not living") if sys.platform != 'win32': os.kill(self._popen.pid, signal.SIGINT) else: kernel32 = ctypes.windll.kernel32 CTRL_C_EVENT = 0 try: kernel32.GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0) time.sleep(10) except KeyboardInterrupt: # This also sends us a KeyboardInterrupt. It should # happen in time.sleep. pass dreampie-1.1.1/dreampielib/gui/pyparse.py0000644000175000017500000004755511335724347017075 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . # This file is based on the file from Python 2.5, Lib/idlelib/PyParse.py # Copyright Python Software Foundation. import re import sys # Reason last stmt is continued (or C_NONE if it's not). (C_NONE, C_BACKSLASH, C_STRING_FIRST_LINE, C_STRING_NEXT_LINES, C_BRACKET) = range(5) if 0: # for throwaway debugging output def dump(*stuff): sys.__stdout__.write(" ".join(map(str, stuff)) + "\n") # Find what looks like the start of a popular stmt. _synchre = re.compile(r""" ^ [ \t]* (?: while | else | def | return | assert | break | class | continue | elif | try | except | raise | import | yield ) \b """, re.VERBOSE | re.MULTILINE).search # Match blank line or non-indenting comment line. _junkre = re.compile(r""" [ \t]* (?: \# \S .* )? \n """, re.VERBOSE).match # Match any flavor of string; the terminating quote is optional # so that we're robust in the face of incomplete program text. _match_stringre = re.compile(r""" \""" [^"\\]* (?: (?: \\. | "(?!"") ) [^"\\]* )* (?: \""" )? | " [^"\\\n]* (?: \\. [^"\\\n]* )* "? | ''' [^'\\]* (?: (?: \\. | '(?!'') ) [^'\\]* )* (?: ''' )? | ' [^'\\\n]* (?: \\. [^'\\\n]* )* '? """, re.VERBOSE | re.DOTALL).match # Match a line that starts with something interesting; # used to find the first item of a bracket structure. _itemre = re.compile(r""" [ \t]* [^\s#\\] # if we match, m.end()-1 is the interesting char """, re.VERBOSE).match # Match start of stmts that should be followed by a dedent. _closere = re.compile(r""" \s* (?: return | break | continue | raise | pass ) \b """, re.VERBOSE).match # Chew up non-special chars as quickly as possible. If match is # successful, m.end() less 1 is the index of the last boring char # matched. If match is unsuccessful, the string starts with an # interesting char. _chew_ordinaryre = re.compile(r""" [^[\](){}#'"\\]+ """, re.VERBOSE).match # Build translation table to map uninteresting chars to "x", open # brackets to "(", and close brackets to ")". _tran = ['x'] * 256 for ch in "({[": _tran[ord(ch)] = '(' for ch in ")}]": _tran[ord(ch)] = ')' for ch in "\"'\\\n#": _tran[ord(ch)] = ch _tran = ''.join(_tran) del ch try: UnicodeType = type(unicode("")) except NameError: UnicodeType = None class Parser(object): def __init__(self, indentwidth, tabwidth): self.indentwidth = indentwidth self.tabwidth = tabwidth def set_str(self, str): assert len(str) == 0 or str[-1] == '\n' if type(str) is UnicodeType: # The parse functions have no idea what to do with Unicode, so # replace all Unicode characters with "x". This is "safe" # so long as the only characters germane to parsing the structure # of Python are 7-bit ASCII. It's *necessary* because Unicode # strings don't have a .translate() method that supports # deletechars. uniphooey = str str = [] push = str.append for raw in map(ord, uniphooey): push(raw < 127 and chr(raw) or "x") str = "".join(str) self.str = str self.study_level = 0 # Return index of a good place to begin parsing, as close to the # end of the string as possible. This will be the start of some # popular stmt like "if" or "def". Return None if none found: # the caller should pass more prior context then, if possible, or # if not (the entire program text up until the point of interest # has already been tried) pass 0 to set_lo. # # This will be reliable iff given a reliable is_char_in_string # function, meaning that when it says "no", it's absolutely # guaranteed that the char is not in a string. def find_good_parse_start(self, is_char_in_string=None, _synchre=_synchre): str, pos = self.str, None if not is_char_in_string: # no clue -- make the caller pass everything return None # Peek back from the end for a good place to start, # but don't try too often; pos will be left None, or # bumped to a legitimate synch point. limit = len(str) for _tries in range(5): i = str.rfind(":\n", 0, limit) if i < 0: break i = str.rfind('\n', 0, i) + 1 # start of colon line m = _synchre(str, i, limit) if m and not is_char_in_string(m.start()): pos = m.start() break limit = i if pos is None: # Nothing looks like a block-opener, or stuff does # but is_char_in_string keeps returning true; most likely # we're in or near a giant string, the colorizer hasn't # caught up enough to be helpful, or there simply *aren't* # any interesting stmts. In any of these cases we're # going to have to parse the whole thing to be sure, so # give it one last try from the start, but stop wasting # time here regardless of the outcome. m = _synchre(str) if m and not is_char_in_string(m.start()): pos = m.start() return pos # Peeking back worked; look forward until _synchre no longer # matches. i = pos + 1 while 1: m = _synchre(str, i) if m: s, i = m.span() if not is_char_in_string(s): pos = s else: break return pos # Throw away the start of the string. Intended to be called with # find_good_parse_start's result. def set_lo(self, lo): assert lo == 0 or self.str[lo-1] == '\n' if lo > 0: self.str = self.str[lo:] # As quickly as humanly possible , find the line numbers (0- # based) of the non-continuation lines. # Creates self.{goodlines, continuation}. def _study1(self): if self.study_level >= 1: return self.study_level = 1 # Map all uninteresting characters to "x", all open brackets # to "(", all close brackets to ")", then collapse runs of # uninteresting characters. This can cut the number of chars # by a factor of 10-40, and so greatly speed the following loop. str = self.str str = str.translate(_tran) str = str.replace('xxxxxxxx', 'x') str = str.replace('xxxx', 'x') str = str.replace('xx', 'x') str = str.replace('xx', 'x') str = str.replace('\nx', '\n') # note that replacing x\n with \n would be incorrect, because # x may be preceded by a backslash # March over the squashed version of the program, accumulating # the line numbers of non-continued stmts, and determining # whether & why the last stmt is a continuation. continuation = C_NONE level = lno = 0 # level is nesting level; lno is line number self.goodlines = goodlines = [0] push_good = goodlines.append i, n = 0, len(str) while i < n: ch = str[i] i = i+1 # cases are checked in decreasing order of frequency if ch == 'x': continue if ch == '\n': lno = lno + 1 if level == 0: push_good(lno) # else we're in an unclosed bracket structure continue if ch == '(': level = level + 1 continue if ch == ')': if level: level = level - 1 # else the program is invalid, but we can't complain continue if ch == '"' or ch == "'": # consume the string quote = ch if str[i-1:i+2] == quote * 3: quote = quote * 3 firstlno = lno w = len(quote) - 1 i = i+w while i < n: ch = str[i] i = i+1 if ch == 'x': continue if str[i-1:i+w] == quote: i = i+w break if ch == '\n': lno = lno + 1 if w == 0: # unterminated single-quoted string if level == 0: push_good(lno) break continue if ch == '\\': assert i < n if str[i] == '\n': lno = lno + 1 i = i+1 continue # else comment char or paren inside string else: # didn't break out of the loop, so we're still # inside a string if (lno - 1) == firstlno: # before the previous \n in str, we were in the first # line of the string continuation = C_STRING_FIRST_LINE else: continuation = C_STRING_NEXT_LINES continue # with outer loop if ch == '#': # consume the comment i = str.find('\n', i) assert i >= 0 continue assert ch == '\\' assert i < n if str[i] == '\n': lno = lno + 1 if i+1 == n: continuation = C_BACKSLASH i = i+1 # The last stmt may be continued for all 3 reasons. # String continuation takes precedence over bracket # continuation, which beats backslash continuation. if (continuation != C_STRING_FIRST_LINE and continuation != C_STRING_NEXT_LINES and level > 0): continuation = C_BRACKET self.continuation = continuation # Push the final line number as a sentinel value, regardless of # whether it's continued. assert (continuation == C_NONE) == (goodlines[-1] == lno) if goodlines[-1] != lno: push_good(lno) def get_continuation_type(self): self._study1() return self.continuation # study1 was sufficient to determine the continuation status, # but doing more requires looking at every character. study2 # does this for the last interesting statement in the block. # Creates: # self.stmt_start, stmt_end # slice indices of last interesting stmt # self.stmt_bracketing # the bracketing structure of the last interesting stmt; # for example, for the statement "say(boo) or die", stmt_bracketing # will be [(0, 0), (3, 1), (8, 0)]. Strings and comments are # treated as brackets, for the matter. # self.lastch # last non-whitespace character before optional trailing # comment # self.lastopenbracketpos # if continuation is C_BRACKET, index of last open bracket def _study2(self): if self.study_level >= 2: return self._study1() self.study_level = 2 # Set p and q to slice indices of last interesting stmt. str, goodlines = self.str, self.goodlines i = len(goodlines) - 1 p = len(str) # index of newest line while i: assert p # p is the index of the stmt at line number goodlines[i]. # Move p back to the stmt at line number goodlines[i-1]. q = p for _nothing in range(goodlines[i-1], goodlines[i]): # tricky: sets p to 0 if no preceding newline p = str.rfind('\n', 0, p-1) + 1 # The stmt str[p:q] isn't a continuation, but may be blank # or a non-indenting comment line. if _junkre(str, p): i = i-1 else: break if i == 0: # nothing but junk! assert p == 0 q = p self.stmt_start, self.stmt_end = p, q # Analyze this stmt, to find the last open bracket (if any) # and last interesting character (if any). lastch = "" stack = [] # stack of open bracket indices push_stack = stack.append bracketing = [(p, 0)] while p < q: # suck up all except ()[]{}'"#\\ m = _chew_ordinaryre(str, p, q) if m: # we skipped at least one boring char newp = m.end() # back up over totally boring whitespace i = newp - 1 # index of last boring char while i >= p and str[i] in " \t\n": i = i-1 if i >= p: lastch = str[i] p = newp if p >= q: break ch = str[p] if ch in "([{": push_stack(p) bracketing.append((p, len(stack))) lastch = ch p = p+1 continue if ch in ")]}": if stack: del stack[-1] lastch = ch p = p+1 bracketing.append((p, len(stack))) continue if ch == '"' or ch == "'": # consume string # Note that study1 did this with a Python loop, but # we use a regexp here; the reason is speed in both # cases; the string may be huge, but study1 pre-squashed # strings to a couple of characters per line. study1 # also needed to keep track of newlines, and we don't # have to. bracketing.append((p, len(stack)+1)) lastch = ch p = _match_stringre(str, p, q).end() bracketing.append((p, len(stack))) continue if ch == '#': # consume comment and trailing newline bracketing.append((p, len(stack)+1)) p = str.find('\n', p, q) + 1 assert p > 0 bracketing.append((p, len(stack))) continue assert ch == '\\' p = p+1 # beyond backslash assert p < q if str[p] != '\n': # the program is invalid, but can't complain lastch = ch + str[p] p = p+1 # beyond escaped char # end while p < q: self.lastch = lastch if stack: self.lastopenbracketpos = stack[-1] self.stmt_bracketing = tuple(bracketing) # Assuming continuation is C_BRACKET, return the number # of spaces the next line should be indented. def compute_bracket_indent(self): self._study2() assert self.continuation == C_BRACKET j = self.lastopenbracketpos str = self.str n = len(str) origi = i = str.rfind('\n', 0, j) + 1 j = j+1 # one beyond open bracket # find first list item; set i to start of its line while j < n: m = _itemre(str, j) if m: j = m.end() - 1 # index of first interesting char extra = 0 break else: # this line is junk; advance to next line i = j = str.find('\n', j) + 1 else: # nothing interesting follows the bracket; # reproduce the bracket line's indentation + a level j = i = origi while str[j] in " \t": j = j+1 extra = self.indentwidth return len(str[i:j].expandtabs(self.tabwidth)) + extra # Return number of physical lines in last stmt (whether or not # it's an interesting stmt! this is intended to be called when # continuation is C_BACKSLASH). def get_num_lines_in_stmt(self): self._study1() goodlines = self.goodlines return goodlines[-1] - goodlines[-2] # Assuming continuation is C_BACKSLASH, return the number of spaces # the next line should be indented. Also assuming the new line is # the first one following the initial line of the stmt. def compute_backslash_indent(self): self._study2() assert self.continuation == C_BACKSLASH str = self.str i = self.stmt_start while str[i] in " \t": i = i+1 startpos = i # See whether the initial line starts an assignment stmt; i.e., # look for an = operator endpos = str.find('\n', startpos) + 1 found = level = 0 while i < endpos: ch = str[i] if ch in "([{": level = level + 1 i = i+1 elif ch in ")]}": if level: level = level - 1 i = i+1 elif ch == '"' or ch == "'": i = _match_stringre(str, i, endpos).end() elif ch == '#': break elif level == 0 and ch == '=' and \ (i == 0 or str[i-1] not in "=<>!") and \ str[i+1] != '=': found = 1 break else: i = i+1 if found: # found a legit =, but it may be the last interesting # thing on the line i = i+1 # move beyond the = found = re.match(r"\s*\\", str[i:endpos]) is None if not found: # oh well ... settle for moving beyond the first chunk # of non-whitespace chars i = startpos while str[i] not in " \t\n": i = i+1 return len(str[self.stmt_start:i].expandtabs(\ self.tabwidth)) + 1 # Return the leading whitespace on the initial line of the last # interesting stmt. def get_base_indent_string(self): self._study2() i, n = self.stmt_start, self.stmt_end j = i str = self.str while j < n and str[j] in " \t": j = j + 1 return str[i:j] # Did the last interesting stmt open a block? def is_block_opener(self): self._study2() return self.lastch == ':' # Did the last interesting stmt close a block? def is_block_closer(self): self._study2() return _closere(self.str, self.stmt_start) is not None # index of last open bracket ({[, or None if none lastopenbracketpos = None def get_last_open_bracket_pos(self): self._study2() return self.lastopenbracketpos # the structure of the bracketing of the last interesting statement, # in the format defined in _study2, or None if the text didn't contain # anything stmt_bracketing = None def get_last_stmt_bracketing(self): self._study2() return self.stmt_bracketing dreampie-1.1.1/dreampielib/gui/load_pygtk.py0000600000175000017500000000367611341162744017525 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['load_pygtk'] import os from os.path import join, pardir import sys def load_pygtk(data_dir): """On win32, load PyGTK from subdirectory, if available.""" pygtk_dir = join(data_dir, pardir, 'pygtk-%d.%d' % (sys.version_info[0], sys.version_info[1])) if os.path.isdir(pygtk_dir): orig_pypath = sys.path sys.path = [pygtk_dir] + sys.path else: orig_pypath = None gtk_runtime_dir = join(data_dir, pardir, 'gtk-runtime') if os.path.isdir(gtk_runtime_dir): orig_path = os.environ['PATH'] os.environ['PATH'] = join(gtk_runtime_dir, 'bin') else: orig_path = None try: import pygtk pygtk.require('2.0') import gobject import gtk _ = gtk import gtk.glade import pango import gtksourceview2 # Make pydev quiet _ = gobject, gtk, gtk.glade, pango, gtksourceview2 try: import glib _ = glib except ImportError: # glib is only from 2.14, I think. pass finally: if orig_pypath is not None: sys.path = orig_pypath if orig_path is not None: os.environ['PATH'] = orig_path dreampie-1.1.1/dreampielib/gui/selection.py0000644000175000017500000001024611335724406017356 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['Selection'] import gtk from .tags import COMMAND, PROMPT from .beep import beep class Selection(object): """ Handle clipboard events. When something is selected, "Copy" should be enabled. When nothing is selected, "Interrupt" should be enabled. Also, "copy only commands" command. """ def __init__(self, textview, sourceview, on_is_something_selected_changed): self.textview = textview self.textbuffer = textview.get_buffer() self.sourceview = sourceview self.sourcebuffer = sourceview.get_buffer() self.on_is_something_selected_changed = on_is_something_selected_changed self.is_something_selected = None self.textbuffer.connect('mark-set', self.on_mark_set) self.sourcebuffer.connect('mark-set', self.on_mark_set) self.clipboard = gtk.Clipboard() def on_selection_changed(self, _clipboard, _event): is_something_selected = (self.textbuffer.get_has_selection() or self.sourcebuffer.get_has_selection()) self.on_is_something_selected_changed(is_something_selected) def on_mark_set(self, _widget, _it, _mark): is_something_selected = (self.textbuffer.get_has_selection() or self.sourcebuffer.get_has_selection()) if self.is_something_selected is None \ or is_something_selected != self.is_something_selected: self.is_something_selected = is_something_selected self.on_is_something_selected_changed(is_something_selected) def cut(self): if self.sourcebuffer.get_has_selection(): self.sourcebuffer.cut_clipboard(self.clipboard, True) else: beep() def copy(self): if self.textbuffer.get_has_selection(): # Don't copy '\r' chars, which are newlines only used for # display tb = self.textbuffer sel_start, sel_end = tb.get_selection_bounds() text = tb.get_text(sel_start, sel_end).decode('utf8') text = text.replace('\r', '') self.clipboard.set_text(text) elif self.sourcebuffer.get_has_selection(): self.sourcebuffer.copy_clipboard(self.clipboard) else: beep() def copy_commands_only(self): if self.sourcebuffer.get_has_selection(): self.sourcebuffer.copy_clipboard(self.clipboard) return if not self.textbuffer.get_has_selection(): beep() return # We need to copy the text which has the COMMAND tag, doesn't have # the PROMPT tag, and is selected. tb = self.textbuffer command = tb.get_tag_table().lookup(COMMAND) prompt = tb.get_tag_table().lookup(PROMPT) r = [] it, sel_end = tb.get_selection_bounds() reached_end = False while not reached_end: it2 = it.copy() it2.forward_to_tag_toggle(None) if it2.compare(sel_end) >= 0: it2 = sel_end.copy() reached_end = True if it.has_tag(command) and not it.has_tag(prompt): r.append(tb.get_text(it, it2).decode('utf8')) it = it2 r = ''.join(r) if not r: beep() else: self.clipboard.set_text(r) def paste(self): if self.sourceview.is_focus(): self.sourcebuffer.paste_clipboard(self.clipboard, None, True) else: beep() dreampie-1.1.1/dreampielib/gui/vadj_to_bottom.py0000644000175000017500000000617411335725162020410 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['VAdjToBottom'] try: from glib import idle_add except ImportError: # In PyGObject 2.14, it's in gobject. from gobject import idle_add class VAdjToBottom(object): """ Scroll automatically to the bottom of the VAdj if the height changes and it was at bottom """ # The way this works is a little bit tricky, so here is the reasoning: # self.user_wants_bottom records whether the user wants to see the bottom, # so we should automatically scroll if more text was added. It is changed # by self.on_value_changed; It assumes that if the scrollbar is at the # bottom then that's what the user wants, but if it isn't at the bottom, # it means that the user doesn't want to see the bottom only if the # scrollbar was scrolled upwards - otherwise it's just us trying to catch # up. # self.on_changed monitors changes in the textview. If the scrollbar isn't # at the bottom (as the result of changes) but self.user_wants_bottom is # True, it schedules a call to scroll_to_bottom when idle. We don't call # scroll_to_bottom immediately because many times the are a few changes # before display, and scrolling before they are finished will cause # redisplay after every stage, which will be slow. def __init__(self, vadj): self.vadj = vadj self.last_value = self.vadj.value self.user_wants_bottom = True self.is_scroll_scheduled = False vadj.connect('changed', self.on_changed) vadj.connect('value-changed', self.on_value_changed) def is_at_bottom(self): return self.vadj.value + self.vadj.page_size - self.vadj.upper == 0 def scroll_to_bottom(self): # Callback function try: self.is_scroll_scheduled = False self.vadj.set_value(self.vadj.upper - self.vadj.page_size) finally: # Avoid future calls return False def on_changed(self, _widget): if (not self.is_scroll_scheduled and self.user_wants_bottom and not self.is_at_bottom()): idle_add(self.scroll_to_bottom) self.is_scroll_scheduled = True def on_value_changed(self, _widget): is_at_bottom = self.is_at_bottom() if is_at_bottom: self.user_wants_bottom = True else: if self.vadj.value < self.last_value: self.user_wants_bottom = False self.last_value = self.vadj.value dreampie-1.1.1/dreampielib/gui/hide_console_window.py0000600000175000017500000000344511341162755021406 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . """ An ugly hack to hide the console window associated with the current process. See http://support.microsoft.com/kb/124103 """ __all__ = ['hide_console_window'] import time import ctypes kernel32 = ctypes.windll.kernel32 user32 = ctypes.windll.user32 def hide_console_window(): BUFSIZE = 1024 buf = ctypes.create_string_buffer('', BUFSIZE) # Get current title length = kernel32.GetConsoleTitleA(buf, BUFSIZE) title = buf.raw[:length] # Change title to a unique string temp_title = '%s/%s' % (kernel32.GetCurrentProcessId(), kernel32.GetTickCount()) kernel32.SetConsoleTitleA(temp_title) time.sleep(.04) # Get window handle handle = user32.FindWindowA(None, temp_title) # Get current title, to make sure that we got the right handle length = user32.GetWindowTextA(handle, buf, BUFSIZE) cur_title = buf.raw[:length] # Restore title kernel32.SetConsoleTitleA(title) if cur_title == temp_title: # We got the correct handle, so hide the window. SW_HIDE = 0 user32.ShowWindow(handle, SW_HIDE) dreampie-1.1.1/dreampielib/gui/beep.py0000640000175000017500000000150611341162710016266 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['beep'] import sys if sys.platform == 'win32': from winsound import MessageBeep as beep else: from gtk.gdk import beep dreampie-1.1.1/dreampielib/gui/history.py0000644000175000017500000001764111340760603017073 0ustar noamnoam# Copyright 2010 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['History'] from .tags import COMMAND, PROMPT from .beep import beep class History(object): """ Manage moving between commands on the text view, and recalling commands in the source view. """ def __init__(self, textview, sourceview, config): self.textview = textview self.textbuffer = textview.get_buffer() self.sourceview = sourceview self.sourcebuffer = sourceview.get_buffer() self.recall_1_char_commands = config.get_bool('recall-1-char-commands') tb = self.textbuffer self.hist_prefix = None self.sb_changed = True # A handler_id when sb_changed is False. self.changed_handler_id = None self.hist_mark = tb.create_mark('history', tb.get_end_iter(), False) def _track_change(self): """Set self.sb_changed to False, and add a handler which will set it to True on the next change.""" if not self.sb_changed: return self.sb_changed = False self.changed_handler_id = self.sourcebuffer.connect( 'changed', self._on_sourcebuffer_changed) def _on_sourcebuffer_changed(self, _widget): self.sb_changed = True self.sourcebuffer.disconnect(self.changed_handler_id) self.changed_handler_id = None def iter_get_command(self, it, only_first_line=False): """Get a textiter placed inside (or at the end of) a COMMAND tag. Return the text of the tag which doesn't have the PROMPT tag. """ tb = self.textbuffer prompt = tb.get_tag_table().lookup(PROMPT) command = tb.get_tag_table().lookup(COMMAND) it = it.copy() if not it.begins_tag(command): it.backward_to_tag_toggle(command) assert it.begins_tag(command) it_end = it.copy(); it_end.forward_to_tag_toggle(command) if it.has_tag(prompt): it.forward_to_tag_toggle(prompt) if it.compare(it_end) >= 0: # nothing but prompt return '' r = [] while True: it2 = it.copy() it2.forward_to_tag_toggle(prompt) if it2.compare(it_end) >= 0: it2 = it.copy() it2.forward_to_tag_toggle(command) r.append(tb.get_text(it, it2).decode('utf8')) break r.append(tb.get_text(it, it2)) if only_first_line: break it = it2 it.forward_to_tag_toggle(prompt) if it.compare(it_end) >= 0: break return ''.join(r) def copy_to_sourceview(self): # Copy the current command to the sourceview tb = self.textbuffer command = tb.get_tag_table().lookup(COMMAND) it = tb.get_iter_at_mark(tb.get_insert()) if not it.has_tag(command) and not it.ends_tag(command): beep() return True s = self.iter_get_command(it).strip() if not s: beep() return True self.sourcebuffer.set_text(s) self.sourceview.grab_focus() return True def history_up(self): """Called when the history up command is required""" if self.textview.is_focus(): tb = self.textbuffer command = tb.get_tag_table().lookup(COMMAND) insert = tb.get_insert() it = tb.get_iter_at_mark(insert) it.backward_to_tag_toggle(command) if it.ends_tag(command): it.backward_to_tag_toggle(command) self.textbuffer.place_cursor(it) self.textview.scroll_mark_onscreen(insert) elif self.sourceview.is_focus(): tb = self.textbuffer sb = self.sourcebuffer command = tb.get_tag_table().lookup(COMMAND) if self.sb_changed: if sb.get_end_iter().get_line() != 0: # Don't allow prefixes of more than one line beep() return self.hist_prefix = sb.get_text(sb.get_start_iter(), sb.get_end_iter()) self._track_change() tb.move_mark(self.hist_mark, tb.get_end_iter()) it = tb.get_iter_at_mark(self.hist_mark) if it.is_start(): beep() return while True: it.backward_to_tag_toggle(command) if it.ends_tag(command): it.backward_to_tag_toggle(command) if not it.begins_tag(command): beep() break first_line = self.iter_get_command(it, only_first_line=True).strip() if (first_line and first_line.startswith(self.hist_prefix) and (len(first_line) > 2 or self.recall_1_char_commands)): command = self.iter_get_command(it).strip() sb.set_text(command) sb.place_cursor(sb.get_end_iter()) self._track_change() tb.move_mark(self.hist_mark, it) break if it.is_start(): beep() return else: beep() def history_down(self): """Called when the history down command is required""" if self.textview.is_focus(): tb = self.textbuffer command = tb.get_tag_table().lookup(COMMAND) insert = tb.get_insert() it = tb.get_iter_at_mark(insert) it.forward_to_tag_toggle(command) if it.ends_tag(command): it.forward_to_tag_toggle(command) self.textbuffer.place_cursor(it) self.textview.scroll_mark_onscreen(insert) elif self.sourceview.is_focus(): tb = self.textbuffer sb = self.sourcebuffer command = tb.get_tag_table().lookup(COMMAND) if self.sb_changed: beep() return it = tb.get_iter_at_mark(self.hist_mark) while True: it.forward_to_tag_toggle(command) it.forward_to_tag_toggle(command) if not it.begins_tag(command): # Return the source buffer to the prefix and everything # to initial state sb.set_text(self.hist_prefix) sb.place_cursor(sb.get_end_iter()) # Since we change the text and not update the change count, # it's like the user did it and hist_prefix is not longer # meaningful. break first_line = self.iter_get_command(it, only_first_line=True).strip() if (first_line and first_line.startswith(self.hist_prefix) and (len(first_line) > 2 or self.recall_1_char_commands)): command = self.iter_get_command(it).strip() sb.set_text(command) sb.place_cursor(sb.get_end_iter()) self._track_change() tb.move_mark(self.hist_mark, it) break else: beep() dreampie-1.1.1/dreampielib/gui/keyhandler.py0000644000175000017500000000462111335723352017516 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['make_keyhandler_decorator', 'handle_keypress', 'parse_keypress_event'] """ Help handling keypress events. The functions here should be used like this: keyhandlers = {} keyhandler = make_keyhandler_decorator(keyhandlers) class Whatever: @keyhandler('Return', 0) def on_return(self): # Do something def on_keypress(self, widget, event): handle_keypress(self, event, keyhandlers) """ from gtk import gdk # We ignore all other mods. There isn't a standard modifier for Alt, # and we don't use it anyway in our shortcuts. handled_mods = gdk.SHIFT_MASK | gdk.CONTROL_MASK def make_keyhandler_decorator(keyhandlers_dict): def keyhandler(keyval, state): def decorator(func): keyhandlers_dict[keyval, state] = func return func return decorator return keyhandler def parse_keypress_event(event): """ Get a keypress event, return a tuple of (keyval_name, state). Will return (None, None) when no appropriate tuple is available. """ r = gdk.keymap_get_default().translate_keyboard_state( event.hardware_keycode, event.state, event.group) if r is None: # This seems to be the case when pressing CapsLock on win32 return (None, None) keyval, _group, _level, consumed_mods = r state = event.state & ~consumed_mods & handled_mods keyval_name = gdk.keyval_name(keyval) if keyval_name == 'KP_Enter': keyval_name = 'Return' return keyval_name, state def handle_keypress(self, event, keyhandlers_dict): keyval_name, state = parse_keypress_event(event) try: func = keyhandlers_dict[keyval_name, state] except KeyError: pass else: return func(self) dreampie-1.1.1/dreampielib/gui/hyper_parser.py0000644000175000017500000002116211274224251020066 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . # This file is based on idlelib/HyperParser.py from Python 2.5. # Copyright Python Software Foundation. __all__ = ['HyperParser'] """ This module defines the HyperParser class, which provides advanced parsing abilities. The HyperParser uses pyparse. pyparse is intended mostly to give information on the proper indentation of code. HyperParser gives some information on the structure of code. """ import string import keyword from . import pyparse class HyperParser(object): def __init__(self, text, index, INDENT_WIDTH): """Initialize the HyperParser to analyze the surroundings of the given index. Index must be in the last statement. """ self.text = text parser = pyparse.Parser(INDENT_WIDTH, INDENT_WIDTH) # We add the newline because pyparse requires a newline at end. # We add a space so that index won't be at end of line, so that # its status will be the same as the char before it, if should. parser.set_str(text+' \n') parser.set_lo(0) self.bracketing = parser.get_last_stmt_bracketing() # find which pairs of bracketing are openers. These always correspond # to a character of text. self.isopener = [i>0 and self.bracketing[i][1] > self.bracketing[i-1][1] for i in range(len(self.bracketing))] self.index = None self.set_index(index) def set_index(self, index): """Set the index to which the functions relate. Note that it must be in the last statement. """ self.index = index # find the rightmost bracket to which index belongs self.indexbracket = 0 while self.indexbracket < len(self.bracketing)-1 and \ self.bracketing[self.indexbracket+1][0] < index: self.indexbracket += 1 if self.indexbracket < len(self.bracketing)-1 and \ self.bracketing[self.indexbracket+1][0] == index and \ not self.isopener[self.indexbracket+1]: self.indexbracket += 1 def is_in_string(self): """Is the index given to the HyperParser is in a string?""" # The bracket to which we belong should be an opener. # If it's an opener, it has to have a character. return self.isopener[self.indexbracket] and \ self.text[self.bracketing[self.indexbracket][0]] in ('"', "'") def is_in_code(self): """Is the index given to the HyperParser is in a normal code?""" return not self.isopener[self.indexbracket] or \ self.text[self.bracketing[self.indexbracket][0]] not in \ ('#', '"', "'") def get_surrounding_brackets(self, openers='([{'): """If the index given to the HyperParser is surrounded by a bracket defined in openers (or at least has one before it), return the indices of the opening bracket and the closing bracket. If it is not surrounded by brackets, return (None, None). If there is no closing bracket, return (before_index, None). """ bracketinglevel = self.bracketing[self.indexbracket][1] before = self.indexbracket while not self.isopener[before] or \ self.text[self.bracketing[before][0]] not in openers or \ self.bracketing[before][1] > bracketinglevel: before -= 1 if before < 0: return (None, None) bracketinglevel = min(bracketinglevel, self.bracketing[before][1]) after = self.indexbracket + 1 while after < len(self.bracketing) and \ self.bracketing[after][1] >= bracketinglevel: after += 1 beforeindex = self.bracketing[before][0] if after >= len(self.bracketing): afterindex = None else: # Return the index of the closing bracket char. afterindex = self.bracketing[after][0] - 1 return beforeindex, afterindex # This string includes all chars that may be in a white space _whitespace_chars = " \t\n\\" # This string includes all chars that may be in an identifier _id_chars = string.ascii_letters + string.digits + "_" # This string includes all chars that may be the first char of an identifier _id_first_chars = string.ascii_letters + "_" # Given a string and pos, return the number of chars in the identifier # which ends at pos, or 0 if there is no such one. Saved words are not # identifiers. def _eat_identifier(self, str, limit, pos): i = pos while i > limit and str[i-1] in self._id_chars: i -= 1 if i < pos and (str[i] not in self._id_first_chars or \ keyword.iskeyword(str[i:pos])): i = pos return pos - i def get_expression(self): """Return a string with the Python expression which ends at the given index, which is empty if there is no real one. """ if not self.is_in_code(): raise ValueError("get_expression should only be called if index "\ "is inside a code.") text = self.text bracketing = self.bracketing brck_index = self.indexbracket brck_limit = bracketing[brck_index][0] pos = self.index last_identifier_pos = pos postdot_phase = True while 1: # Eat whitespaces, comments, and if postdot_phase is False - one dot while 1: if pos>brck_limit and text[pos-1] in self._whitespace_chars: # Eat a whitespace pos -= 1 elif not postdot_phase and \ pos > brck_limit and text[pos-1] == '.': # Eat a dot pos -= 1 postdot_phase = True # The next line will fail if we are *inside* a comment, but we # shouldn't be. elif pos == brck_limit and brck_index > 0 and \ text[bracketing[brck_index-1][0]] == '#': # Eat a comment brck_index -= 2 brck_limit = bracketing[brck_index][0] pos = bracketing[brck_index+1][0] else: # If we didn't eat anything, quit. break if not postdot_phase: # We didn't find a dot, so the expression end at the last # identifier pos. break ret = self._eat_identifier(text, brck_limit, pos) if ret: # There is an identifier to eat pos = pos - ret last_identifier_pos = pos # Now, in order to continue the search, we must find a dot. postdot_phase = False # (the loop continues now) elif pos == brck_limit: # We are at a bracketing limit. If it is a closing bracket, # eat the bracket, otherwise, stop the search. level = bracketing[brck_index][1] while brck_index > 0 and bracketing[brck_index-1][1] > level: brck_index -= 1 if bracketing[brck_index][0] == brck_limit: # We were not at the end of a closing bracket break pos = bracketing[brck_index][0] brck_index -= 1 brck_limit = bracketing[brck_index][0] last_identifier_pos = pos if text[pos] in "([": # [] and () may be used after an identifier, so we # continue. postdot_phase is True, so we don't allow a dot. pass else: # We can't continue after other types of brackets break else: # We've found an operator or something. break return text[last_identifier_pos:self.index] dreampie-1.1.1/dreampielib/gui/autoparen.py0000644000175000017500000000715311443127314017365 0ustar noamnoam# Copyright 2010 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['Autoparen'] import string from keyword import iskeyword from .hyper_parser import HyperParser # These are all the chars that may be before the parens LAST_CHARS = set(string.ascii_letters + string.digits + "_)]") class Autoparen(object): """ Add parentheses if a space was pressed after a callable-only object. """ def __init__(self, sourcebuffer, is_callable_only, get_expects_str, show_call_tip, INDENT_WIDTH): self.sourcebuffer = sb = sourcebuffer self.is_callable_only = is_callable_only self.get_expects_str = get_expects_str self.show_call_tip = show_call_tip self.INDENT_WIDTH = INDENT_WIDTH # We place this mark at the end of the expression we added parens to, # so that if the user removes the paren and presses space, we won't # interfere another time. self.mark = sb.create_mark(None, sb.get_start_iter(), left_gravity=True) def add_parens(self): """ This is called if the user pressed space on the sourceview, and the subprocess is not executing commands (so is_callable_only can work.) Should return True if event-handling should stop, or False if it should continue as usual. Should be called only when is_callable_only can be called safely. """ sb = self.sourcebuffer # Quickly discard some cases insert = sb.get_iter_at_mark(sb.get_insert()) mark_it = sb.get_iter_at_mark(self.mark) if mark_it.equal(insert): return False it = insert.copy() it.backward_char() if it.get_char() not in LAST_CHARS: return False it.forward_char() it.backward_word_start() if iskeyword(it.get_text(insert).decode('utf8')): return False text = sb.get_slice(sb.get_start_iter(), sb.get_end_iter()).decode('utf8') index = sb.get_iter_at_mark(sb.get_insert()).get_offset() hp = HyperParser(text, index, self.INDENT_WIDTH) if not hp.is_in_code(): return False expr = hp.get_expression() if not expr: return False if '(' in expr: # Don't evaluate expressions which may contain a function call. return False is_callable_only, expects_str = self.is_callable_only(expr) if not is_callable_only: return False sb.move_mark(self.mark, insert) last_name = expr.rsplit('.', 1)[-1] sb.begin_user_action() if expects_str or last_name in self.get_expects_str(): sb.insert(insert, '("")') insert.backward_chars(2) else: sb.insert(insert, '()') insert.backward_char() sb.place_cursor(insert) sb.end_user_action() self.show_call_tip() return True dreampie-1.1.1/dreampielib/gui/file_dialogs.py0000644000175000017500000001063211415142507020004 0ustar noamnoam# Copyright 2010 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . __all__ = ['open_dialog', 'save_dialog'] """ Easy to use wrappers around GTK file dialogs. """ import os from os.path import abspath, dirname, basename, exists import gtk # Support translation in the future _ = lambda s: s def open_dialog(func, title, parent, filter_name, filter_pattern): """ Display the Open dialog. func - a function which gets a file name and does something. If it throws an IOError, it will be catched and the user will get another chance. title - window title parent - parent window, or None filter_name - "HTML Files" filter_pattern - "*.html" """ d = gtk.FileChooserDialog( title, parent, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) fil = gtk.FileFilter() fil.set_name(filter_name) fil.add_pattern(filter_pattern) d.add_filter(fil) while True: r = d.run() if r != gtk.RESPONSE_OK: break filename = abspath(d.get_filename().decode('utf8')) try: func(filename) except IOError, e: m = gtk.MessageDialog(d, gtk.DIALOG_MODAL, gtk.MESSAGE_WARNING, gtk.BUTTONS_OK) m.props.text = _('Error when loading file: %s') % e m.run() m.destroy() else: break d.destroy() def save_dialog(func, title, parent, filter_name, filter_pattern, auto_ext=None, prev_dir=None, prev_name=None): """ Display the Save As dialog. func - a function which gets a file name and does something. If it throws an IOError, it will be catched and the user will get another chance. title - window title parent - parent window, or None filter_name - "HTML Files" filter_pattern - "*.html" auto_ext - "html", if not None will be added if no extension given. prev_dir, prev_name - will set the default if given. """ d = gtk.FileChooserDialog( title, parent, gtk.FILE_CHOOSER_ACTION_SAVE, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) fil = gtk.FileFilter() fil.set_name(filter_name) fil.add_pattern(filter_pattern) d.add_filter(fil) if prev_dir: d.set_current_folder(prev_dir) if prev_name: d.set_current_name(prev_name) while True: r = d.run() if r != gtk.RESPONSE_OK: break filename = abspath(d.get_filename()).decode('utf8') if auto_ext and not os.path.splitext(filename)[1]: filename += os.path.extsep + auto_ext if exists(filename): m = gtk.MessageDialog(d, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION) m.props.text = _('A file named "%s" already exists. Do ' 'you want to replace it?' ) % basename(filename) m.props.secondary_text = _( 'The file already exists in "%s". Replacing it will ' 'overwrite its contents.' ) % basename(dirname(filename)) m.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) m.add_button(_('_Replace'), gtk.RESPONSE_OK) m.set_default_response(gtk.RESPONSE_CANCEL) mr = m.run() m.destroy() if mr == gtk.RESPONSE_CANCEL: continue try: func(filename) except IOError, e: m = gtk.MessageDialog(d, gtk.DIALOG_MODAL, gtk.MESSAGE_WARNING, gtk.BUTTONS_OK) m.props.text = _('Error when saving file: %s') % e m.run() m.destroy() else: break d.destroy() dreampie-1.1.1/dreampielib/gui/tags.py0000644000175000017500000002611311341427524016325 0ustar noamnoam# Copyright 2009 Noam Yorav-Raphael # # This file is part of DreamPie. # # DreamPie is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # DreamPie is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with DreamPie. If not, see . """ Tags for the textview and sourceview. """ import os import tempfile from gtk import gdk import gtksourceview2 # DEFAULT is not really a tag, but it means the default text colors DEFAULT = 'default' # Tags for marking output OUTPUT = 'output' STDIN = 'stdin'; STDOUT = 'stdout'; STDERR = 'stderr'; EXCEPTION = 'exception' RESULT_IND = 'result-ind'; RESULT = 'result' # Tags for marking commands PROMPT = 'prompt'; COMMAND = 'command'; COMMAND_DEFS='command-defs' COMMAND_SEP = 'commandsep' # Folding tags FOLDED = 'folded' FOLD_MESSAGE = 'fold-message' # The MESSAGE tag MESSAGE = 'message' # Tags for syntax highlighting KEYWORD = 'keyword'; BUILTIN = 'builtin'; STRING = 'string' NUMBER = 'number'; COMMENT = 'comment'; BRACKET_MATCH = 'bracket-match' # Constants to retrieve data from a theme. A theme is just a dict which maps # tuples to strings, and is used like this: # theme[KEYWORD, FG, COLOR], theme[COMMENT, BG, ISSET] FG = 'fg'; BG = 'bg' COLOR = 'color'; ISSET = 'isset' # Add this string to theme names to get the config section THEME_POSTFIX = ' theme' # Tags which affect appearence tag_desc = [ (DEFAULT, 'Default'), (KEYWORD, 'Keyword'), (BUILTIN, 'Builtin'), (STRING, 'String'), (NUMBER, 'Number'), (COMMENT, 'Comment'), (BRACKET_MATCH, 'Bracket Match'), (STDIN, 'Standard Input'), (STDOUT, 'Standard Output'), (STDERR, 'Standard Error'), (RESULT, 'Result'), (RESULT_IND, 'Result Index'), (EXCEPTION, 'Exception'), (PROMPT, 'Prompt'), (MESSAGE, 'Messages'), (FOLD_MESSAGE, 'Folded Text'), ] """ Some documentation about what's going on in the text buffer ----------------------------------------------------------- The text buffer has two purposes: to show the user what he expects, and to store all the information needed about the history. So here I try to document what's going on there. Most tags can be considered to only affect the highlight color. Here only the tags which affect the data model are described. The COMMAND tag is applied to code segments. The last newline is also tagged. After it comes a '\r' char marked with COMMAND_SEP, so it's invisible. It's used to separate two code segments even if there was no output in between. The prompt is marked with both the COMMAND and the PROMPT tag, so the Copy Code Only action copies only text which is marked by COMMAND and not by PROMPT (and adds a trailing newline.) lines inside def and class blocks are also marked with COMMAND_DEFS, so they can be hidden if the user wants to. STDIN is marked as STDIN, COMMAND and OUTPUT. It always ends with a \n. It is marked with COMMAND so that it will be history-searched. It is marked with OUTPUT so that it will be considered as output for folding purposes. The MESSAGE tag tags a message which is displayed at the beginning of each session (that is, subprocess). It's either the welcome message (Python version), a "New Session" message or a "History Discarded" message. When you discard previous sessions, the MESSAGE tag is used to understand what are the previous sessions. Text marked with OUTPUT was written by output.py. It includes stdout, stderr, result and exception. This text is written at the *output mark*, which means that if an output is produced after the code execution was finished (for example, by another thread), it will appear before the prompt. Also, ANSI escapes are removed. '\r' (carriage return) is handled (to a point - there's no support for having a part of a line overwritten - after a line starts to be overwritten, it's completely removed). When lines are too long (too many chars without a '\n', they are broken to a certain maximum length by '\r' chars. Original '\r' chars will never pass to the text buffer. These '\r' chars are treated by the text buffer just like '\n' chars, but are not copied, because they are not real chars - they were only inserted so that the text buffer won't become unresponsive. If there are enough lines in an output section, it gets automatically collapsed. There's always a '\n' after the output section which is marked as OUTPUT (unless nothing was written, which is not really an output section.) Folded output/code sections look like this: First few characters of the output/code section, which are truncated somewhe (The rest of the section, tagged with FOLDED so invisible) [About n more lines. Double-click to unfold] * From some point (which will be a '\n' if the first line isn't too long), the output is marked with FOLDED, so it is invisible. * The '\n' which always comes at the end of the output is also invisible. It doesn't make a lot of sense, because that way no newline is visible, but it turns out that if it's visible, you get an extra empty line. * Afterwards comes the FOLD_MESSAGE, which includes a '\n' at the end so that the background color is behind the entire line. """ def get_theme_names(config): for section in config.sections(): if section.endswith(THEME_POSTFIX): if config.get_bool('is-active', section): yield section[:-len(THEME_POSTFIX)] def get_theme(config, theme_name): """ Get a theme description (a dict of tuples, see above) from a config object. """ section = theme_name + THEME_POSTFIX if not config.get_bool('is-active', section): raise ValueError("Theme %s is not active" % theme_name) theme = {} for tag, _desc in tag_desc: theme[tag, FG, COLOR] = config.get('%s-fg' % tag, section) theme[tag, BG, COLOR] = config.get('%s-bg' % tag, section) if tag != DEFAULT: theme[tag, FG, ISSET] = config.get_bool('%s-fg-set' % tag, section) theme[tag, BG, ISSET] = config.get_bool('%s-bg-set' % tag, section) return theme def set_theme(config, theme_name, theme): """ Write a theme description to a config object. """ section = theme_name + THEME_POSTFIX if not config.has_section(section): config.add_section(section) config.set_bool('is-active', True, section) for tag, _desc in tag_desc: config.set('%s-fg' % tag, theme[tag, FG, COLOR], section) config.set('%s-bg' % tag, theme[tag, BG, COLOR], section) for tag, _desc in tag_desc: if tag != DEFAULT: config.set_bool('%s-fg-set' % tag, theme[tag, FG, ISSET], section) config.set_bool('%s-bg-set' % tag, theme[tag, BG, ISSET], section) def remove_themes(config): """ Remove all themes. """ for name in get_theme_names(config): # We replace the section with a section with 'is-active = False', so # that if the section is updated from default configuration values # it will not reappear. section = name + THEME_POSTFIX config.remove_section(section) config.add_section(section) config.set_bool('is-active', False, section) def get_actual_color(theme, tag, fg_or_bg): """ Get the actual color that will be displayed - taking ISSET into account. """ if tag == DEFAULT or theme[tag, fg_or_bg, ISSET]: return theme[tag, fg_or_bg, COLOR] else: return theme[DEFAULT, fg_or_bg, COLOR] def add_tags(textbuffer): """ Add the needed tags to a textbuffer """ for tag, _desc in tag_desc: if tag != DEFAULT: textbuffer.create_tag(tag) textbuffer.create_tag(OUTPUT) textbuffer.create_tag(COMMAND) textbuffer.create_tag(COMMAND_DEFS) tag = textbuffer.create_tag(COMMAND_SEP) tag.props.invisible = True tag = textbuffer.create_tag(FOLDED) tag.props.invisible = True def apply_theme_text(textview, textbuffer, theme): """ Apply the theme to the textbuffer. add_tags should have been called previously. """ for tag, _desc in tag_desc: if tag == DEFAULT: textview.modify_base(0, gdk.color_parse(theme[tag, BG, COLOR])) textview.modify_text(0, gdk.color_parse(theme[tag, FG, COLOR])) else: tt = textbuffer.get_tag_table().lookup(tag) tt.props.foreground = theme[tag, FG, COLOR] tt.props.foreground_set = theme[tag, FG, ISSET] tt.props.background = theme[tag, BG, COLOR] tt.props.background_set = theme[tag, BG, ISSET] tt.props.paragraph_background = theme[tag, BG, COLOR] tt.props.paragraph_background_set = theme[tag, BG, ISSET] def _make_style_scheme(spec): # Quite stupidly, there's no way to create a SourceStyleScheme without # reading a file from a search path. So this function creates a file in # a directory, to get you your style scheme. # # spec should be a dict of dicts, mapping style names to (attribute, value) # pairs. Color values will be converted using gdk.color_parse(). # Boolean values will be handled correctly. dir = tempfile.mkdtemp() filename = os.path.join(dir, 'scheme.xml') f = open(filename, 'w') f.write('\n') f.write('\n') for name, attributes in spec.iteritems(): f.write(' """) cur_tags = [] it = tb.get_start_iter() while True: new_tags = cur_tags[:] for tag in it.get_toggled_tags(False): new_tags.remove(tag) for tag in it.get_toggled_tags(True): new_tags.append(tag) new_tags.sort(key=lambda tag: -tag.get_priority()) shared_prefix = 0 while (len(cur_tags) > shared_prefix and len(new_tags) > shared_prefix and cur_tags[shared_prefix] is new_tags[shared_prefix]): shared_prefix += 1 for _i in range(len(cur_tags) - shared_prefix): f.write('') for tag in new_tags[shared_prefix:]: f.write('' % tag.props.name) if it.compare(tb.get_end_iter()) == 0: # We reached the end. We break here, because we want to close # the tags. break new_it = it.copy() new_it.forward_to_tag_toggle(None) text = tb.get_text(it, new_it).decode('utf8') text = _html_escape(text) f.write(text.encode('utf8')) it = new_it cur_tags = new_tags f.write("""\ """) class LoadError(Exception): pass class Parser(HTMLParser): def __init__(self, textbuffer): HTMLParser.__init__(self) self.textbuffer = tb = textbuffer self.reached_body = False self.version = None self.cur_tags = [] self.leftmark = tb.create_mark(None, tb.get_start_iter(), True) self.rightmark = tb.create_mark(None, tb.get_start_iter(), False) def handle_starttag(self, tag, attrs): attrs = dict(attrs) if not self.reached_body: if tag == 'meta': if 'name' in attrs and attrs['name'] == 'DreamPie Format': if attrs['content'] != '1': raise LoadError("Unrecognized DreamPie Format") self.version = 1 if tag == 'body': if self.version is None: raise LoadError("File is not a DreamPie history file.") self.reached_body = True else: if tag == 'span': if 'class' not in attrs: raise LoadError(" without a 'class' attribute") self.cur_tags.append(attrs['class']) def handle_endtag(self, tag): if tag == 'span': if not self.cur_tags: raise LoadError("Too many tags") self.cur_tags.pop() def insert(self, data): tb = self.textbuffer leftmark = self.leftmark; rightmark = self.rightmark # For some reasoin, insert_with_tags_by_name marks everything with the # message tag. So we do it all by ourselves... tb.insert(tb.get_iter_at_mark(leftmark), data) leftit = tb.get_iter_at_mark(leftmark) rightit = tb.get_iter_at_mark(rightmark) tb.remove_all_tags(leftit, rightit) for tag in self.cur_tags: tb.apply_tag_by_name(tag, leftit, rightit) tb.move_mark(leftmark, rightit) def handle_data(self, data): if self.reached_body: self.insert(data.decode('utf8')) def handle_charref(self, name): raise LoadError("Got a charref %r and not expecting it." % name) def handle_entityref(self, name): if self.reached_body: self.insert(unichr(name2codepoint[name])) def close(self): HTMLParser.close(self) tb = self.textbuffer tb.delete_mark(self.leftmark) tb.delete_mark(self.rightmark)