├── .gitignore ├── LICENSE ├── README.markdown ├── data ├── NotificationBar.qml ├── appTheme.js └── qml_error_display.qml ├── qml_error_display.py ├── qt_error_display.py ├── qt_producer_consumer.py └── util ├── __init__.py ├── algorithms.py ├── concurrent.py ├── coroutines.py ├── go_utils.py ├── gtk_utils.py ├── hildonize.py ├── io.py ├── linux.py ├── misc.py ├── overloading.py ├── qml_utils.py ├── qore_utils.py ├── qt_compat.py ├── qtpie.py ├── qtpieboard.py ├── qui_utils.py ├── qwrappers.py ├── time_utils.py └── tp_utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .profile 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 489 | 490 | Also add information on how to contact you by electronic and paper mail. 491 | 492 | You should also get your employer (if you work as a programmer) or your 493 | school, if any, to sign a "copyright disclaimer" for the library, if 494 | necessary. Here is a sample; alter the names: 495 | 496 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 497 | library `Frob' (a library for tweaking knobs) written by James Random Hacker. 498 | 499 | , 1 April 1990 500 | Ty Coon, President of Vice 501 | 502 | That's all there is to it! 503 | 504 | 505 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Python utilities 2 | ====================== 3 | 4 | PySide/PyQt 5 | 6 | * qt_compat.py - Centralize the differences between PySide and PyQt 7 | * qore_utils.py - Qt helpers that only depend on QtCore, including threading, models, QObject tools 8 | * qui_utils.py - Qt helpers that depend on QtGui, including standardized error reporting mechanism, HTML Delegate, a QMainWindow that provides more signals Qt-Maemo5 graceful degredation 9 | * qml_utils.py - I am still learning QML so these are still being defined 10 | * qtpie.py - QWidget-based pie menus (See QWidget version of ejpi for an example) 11 | * qtpieboard.py - QtPie keyboard (See QWidget version of ejpi for an example) 12 | * qwrappers.py - Default implementation of objects owning QApplication and QMainWindow objects 13 | 14 | PyGTK / Hildon Development: 15 | 16 | * go_utils.py - Threading, API version compat, etc 17 | * gtk_utils.py 18 | * hildonize.py - Gracefully fallback when Hildon features are unavailable 19 | * tp_utils - Telepathy tools 20 | 21 | Other 22 | 23 | * linux.py - XDG Helpers 24 | 25 | Everything else is misc tools that fill in gaps for Python. I don't really end up using them except for misc.log_exception I place around every single slot so to ease debugging users' applications. 26 | 27 | Usage 28 | ====================== 29 | Copy what files you need into your tool 30 | 31 | As you can tell by my skeleton project (https://github.com/epage/MaemoPythonSkeleton) I copy the whole folder to make it easier to diff between projects to make sure everything is up to date. 32 | 33 | Why no installer? These are subject to change and I don't want to break people 34 | 35 | The LICENSE is LGPL v2.1 but I am willing to negotiate on that. 36 | -------------------------------------------------------------------------------- /data/NotificationBar.qml: -------------------------------------------------------------------------------- 1 | import Qt 4.7 2 | 3 | import com.nokia.meego 1.0 4 | //import Qt.labs.components.native 1.0 5 | 6 | import "appTheme.js" as AppTheme 7 | 8 | Rectangle 9 | { 10 | id: errorDisplay 11 | color: AppTheme.errorBGColor 12 | visible: errorLog.hasMessages 13 | anchors.top: parent.top 14 | width: parent.width 15 | height: errorLog.hasMessages? 32 : 0 16 | 17 | Row 18 | { 19 | anchors.top: parent.top 20 | anchors.verticalCenter: parent.verticalCenter 21 | width: parent.width 22 | 23 | Image 24 | { 25 | anchors.verticalCenter: parent.verticalCenter 26 | } 27 | Text 28 | { 29 | text: errorLog.currentMessage.message 30 | color: AppTheme.errorFGColor 31 | elide: Text.ElideRight 32 | anchors.verticalCenter: parent.verticalCenter 33 | width: parent.width 34 | height: 32 35 | } 36 | } 37 | 38 | Text 39 | { 40 | text: "X" 41 | anchors.top: parent.top 42 | anchors.right: parent.right 43 | anchors.verticalCenter: parent.verticalCenter 44 | 45 | MouseArea 46 | { 47 | anchors.fill: parent 48 | onClicked: {errorLog.pop()} 49 | } 50 | } 51 | } 52 | 53 | 54 | -------------------------------------------------------------------------------- /data/appTheme.js: -------------------------------------------------------------------------------- 1 | var labelMHeight = 15 2 | var labelMWidth = 15 3 | var entryMHeight = 30 4 | var entryMWidth = 30 5 | 6 | var dominantColor = "#e14f9e" 7 | var disabledColor = "#ffb3f2" 8 | var pressedColor = "#8a4168" 9 | 10 | 11 | var mainFGColor = "black" 12 | var mainBGColor = "white" 13 | var altBGColor = "#DDBBFF" 14 | 15 | var highlightFGColor = "white" 16 | var pressedBGColor = pressedColor 17 | var disabledBGColor = disabledColor 18 | var dominantBGColor = dominantColor 19 | 20 | var listViewSpacing = 5 21 | var listViewHeaderHeight = 32 22 | var listViewItemHeight = 32 23 | 24 | var errorFGColor = "black" 25 | var errorBGColor = "#d7d559" 26 | -------------------------------------------------------------------------------- /data/qml_error_display.qml: -------------------------------------------------------------------------------- 1 | import Qt 4.7 2 | 3 | import com.nokia.meego 1.0 4 | //import Qt.labs.components.native 1.0 5 | 6 | PageStackWindow 7 | { 8 | id: stackWindow 9 | 10 | initialPage: Page 11 | { 12 | id: titleScreenPage 13 | 14 | NotificationBar 15 | { 16 | id: notificationBar 17 | } 18 | 19 | Text 20 | { 21 | text: "Hello World!" 22 | font.bold: true 23 | font.pointSize: 30 24 | anchors.top: notificationBar.bottom 25 | anchors.topMargin: 10 26 | width: parent.width 27 | horizontalAlignment: Text.AlignHCenter 28 | verticalAlignment: Text.AlignVCenter 29 | } 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /qml_error_display.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: UTF8 -*- 3 | 4 | from __future__ import with_statement 5 | 6 | import os 7 | import logging 8 | 9 | import util.qt_compat as qt_compat 10 | QtCore = qt_compat.QtCore 11 | QtGui = qt_compat.import_module("QtGui") 12 | QtDeclarative = qt_compat.import_module("QtDeclarative") 13 | 14 | from util import qore_utils 15 | from util import io as io_utils 16 | 17 | 18 | _moduleLogger = logging.getLogger(__name__) 19 | 20 | 21 | def run(args): 22 | logFormat = '(%(relativeCreated)5d) %(levelname)-5s %(threadName)s.%(name)s.%(funcName)s: %(message)s' 23 | logging.basicConfig(level=logging.DEBUG, format=logFormat) 24 | 25 | errorLog = qore_utils.QErrorLog() 26 | errorLoghandler = io_utils.ErrorLogHandler(errorLog, level=logging.INFO) 27 | 28 | root = logging.getLogger() 29 | root.addHandler(errorLoghandler) 30 | _moduleLogger.debug("OS: %s", os.uname()[0]) 31 | _moduleLogger.debug("Kernel: %s (%s) for %s", *os.uname()[2:]) 32 | _moduleLogger.debug("Hostname: %s", os.uname()[1]) 33 | 34 | _moduleLogger.error("Logging with notification bars!") 35 | 36 | app = QtGui.QApplication(args) 37 | 38 | view = QtDeclarative.QDeclarativeView() 39 | view.setResizeMode(QtDeclarative.QDeclarativeView.SizeRootObjectToView) 40 | view.setWindowTitle(os.path.basename(__file__)) 41 | 42 | engine = view.engine() 43 | 44 | context = view.rootContext() 45 | context.setContextProperty("errorLog", errorLog) 46 | 47 | topLevelQMLFile = os.path.join(os.path.dirname(__file__), "data", os.path.basename(__file__).replace(".py", ".qml")) 48 | view.setSource(topLevelQMLFile) 49 | 50 | view.show() 51 | return app.exec_() 52 | 53 | 54 | if __name__ == "__main__": 55 | import sys 56 | 57 | val = run(sys.argv) 58 | sys.exit(val) 59 | -------------------------------------------------------------------------------- /qt_error_display.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Showcases: 5 | * qt_compat 6 | * comncurrent's async functions which builds on go_utils / qore_utils task pool 7 | * qui_util's error log 8 | * logging slot exceptions 9 | * qui_util's QSignalingMainWindow which exposes some additional signals 10 | """ 11 | 12 | from __future__ import with_statement 13 | from __future__ import division 14 | 15 | import time 16 | import logging 17 | 18 | from util import qt_compat 19 | QtCore = qt_compat.QtCore 20 | QtGui = qt_compat.import_module("QtGui") 21 | from util import qore_utils 22 | from util import qui_utils 23 | from util import concurrent 24 | from util import misc 25 | 26 | 27 | _moduleLogger = logging.getLogger(__name__) 28 | 29 | 30 | class BusyWait(object): 31 | 32 | def __init__(self): 33 | # Specifically make this variable thread unsafe 34 | self._value = 0 35 | 36 | def wait(self, t): 37 | self._value = t 38 | while self._value < (t+10): 39 | self._value += 1 40 | print self._value 41 | time.sleep(0.1) 42 | 43 | 44 | class Window(object): 45 | 46 | def __init__(self): 47 | self._errorLog = qore_utils.QErrorLog() 48 | self._errorDisplay = qui_utils.ErrorDisplay(self._errorLog) 49 | self._taskPool = qore_utils.FutureThread() 50 | self._taskPool.start() 51 | self._busyWait = BusyWait() 52 | 53 | exceptionButton = QtGui.QPushButton("Throw") 54 | exceptionButton.clicked.connect(self._on_exception) 55 | 56 | backgroundButton = QtGui.QPushButton("Background Task") 57 | backgroundButton.clicked.connect(self._on_background) 58 | 59 | self._layout = QtGui.QBoxLayout(QtGui.QBoxLayout.LeftToRight) 60 | self._layout.addWidget(exceptionButton) 61 | self._layout.addWidget(backgroundButton) 62 | self._layout.setContentsMargins(0, 0, 0, 0) 63 | 64 | self._superLayout = QtGui.QVBoxLayout() 65 | self._superLayout.addWidget(self._errorDisplay.toplevel) 66 | self._superLayout.addLayout(self._layout) 67 | self._superLayout.setContentsMargins(0, 0, 0, 0) 68 | 69 | centralWidget = QtGui.QWidget() 70 | centralWidget.setLayout(self._superLayout) 71 | centralWidget.setContentsMargins(0, 0, 0, 0) 72 | 73 | self._qwindow = qui_utils.QSignalingMainWindow() 74 | self._qwindow.shown.connect(self._on_shown) 75 | self._qwindow.hidden.connect(self._on_hidden) 76 | self._qwindow.closed.connect(self._on_closed) 77 | self._qwindow.destroyed.connect(self._on_destroyed) 78 | self._qwindow.setCentralWidget(centralWidget) 79 | qui_utils.set_stackable(self._qwindow, True) 80 | 81 | def show(self): 82 | self._qwindow.show() 83 | 84 | def destroy(self): 85 | self._taskPool.stop() 86 | self._qwindow.destroy() 87 | 88 | @misc.log_exception(_moduleLogger) 89 | def _on_background(self, *args): 90 | print "background" 91 | # Works perfectly well with a GObject task pool 92 | agt = concurrent.AsyncGeneratorTask(self._taskPool, self._call_into_background) 93 | agt.start(10) 94 | 95 | def _call_into_background(self, t): 96 | with qore_utils.notify_error(self._errorLog): 97 | with qore_utils.notify_busy(self._errorLog, "Counting..."): 98 | print "Start count" 99 | yield self._busyWait.wait, (t, ), {} 100 | print "Count done" 101 | 102 | @misc.log_exception(_moduleLogger) 103 | def _on_exception(self, *args): 104 | with qore_utils.notify_error(self._errorLog): 105 | l = [1, 2, 3] 106 | print l["a"] 107 | 108 | @misc.log_exception(_moduleLogger) 109 | def _on_shown(self): 110 | self._errorLog.push_info("shown") 111 | print "shown" 112 | 113 | @misc.log_exception(_moduleLogger) 114 | def _on_hidden(self): 115 | self._errorLog.push_info("hidden") 116 | print "hidden" 117 | 118 | @misc.log_exception(_moduleLogger) 119 | def _on_closed(self): 120 | self._errorLog.push_info("closed") 121 | print "closed" 122 | 123 | @misc.log_exception(_moduleLogger) 124 | def _on_destroyed(self): 125 | self._errorLog.push_info("destroyed") 126 | print "destroyed" 127 | 128 | 129 | def run(): 130 | logFormat = '(%(relativeCreated)5d) %(levelname)-5s %(threadName)s.%(name)s.%(funcName)s: %(message)s' 131 | logging.basicConfig(level=logging.DEBUG, format=logFormat) 132 | 133 | app = QtGui.QApplication([]) 134 | 135 | win = Window() 136 | win.show() 137 | 138 | ret = app.exec_() 139 | 140 | win.destroy() 141 | 142 | return ret 143 | 144 | 145 | if __name__ == "__main__": 146 | import sys 147 | 148 | val = run() 149 | sys.exit(val) 150 | -------------------------------------------------------------------------------- /qt_producer_consumer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Simple example of using signals/slots to send messages across threads 5 | """ 6 | 7 | from __future__ import with_statement 8 | from __future__ import division 9 | 10 | import logging 11 | import time 12 | 13 | from util import qt_compat 14 | QtCore = qt_compat.QtCore 15 | from util import qore_utils 16 | from util import misc 17 | 18 | 19 | _moduleLogger = logging.getLogger(__name__) 20 | 21 | 22 | class Producer(QtCore.QObject): 23 | 24 | data = qt_compat.Signal(int) 25 | done = qt_compat.Signal() 26 | 27 | def __init__(self): 28 | QtCore.QObject.__init__(self) 29 | 30 | @qt_compat.Slot() 31 | @misc.log_exception(_moduleLogger) 32 | def process(self): 33 | print "Starting producer" 34 | for i in xrange(10): 35 | self.data.emit(i) 36 | time.sleep(0.1) 37 | self.done.emit() 38 | 39 | 40 | class Consumer(QtCore.QObject): 41 | 42 | def __init__(self): 43 | QtCore.QObject.__init__(self) 44 | 45 | @qt_compat.Slot() 46 | @misc.log_exception(_moduleLogger) 47 | def process(self): 48 | print "Starting consumer" 49 | 50 | @qt_compat.Slot() 51 | @misc.log_exception(_moduleLogger) 52 | def print_done(self): 53 | print "Done" 54 | 55 | @qt_compat.Slot(int) 56 | @misc.log_exception(_moduleLogger) 57 | def print_data(self, i): 58 | print i 59 | 60 | 61 | def run_producer_consumer(): 62 | app = QtCore.QCoreApplication([]) 63 | 64 | producerThread = qore_utils.QThread44() 65 | producer = Producer() 66 | producer.moveToThread(producerThread) 67 | producerThread.started.connect(producer.process) 68 | 69 | consumerThread = qore_utils.QThread44() 70 | consumer = Consumer() 71 | consumer.moveToThread(consumerThread) 72 | consumerThread.started.connect(consumer.process) 73 | 74 | producer.data.connect(consumer.print_data) 75 | producer.done.connect(consumer.print_done) 76 | 77 | @qt_compat.Slot() 78 | @misc.log_exception(_moduleLogger) 79 | def producer_done(): 80 | print "Shutting down" 81 | producerThread.quit() 82 | consumerThread.quit() 83 | producerThread.wait() 84 | consumerThread.wait() 85 | print "Done" 86 | producer.done.connect(producer_done) 87 | 88 | count = [0] 89 | 90 | @qt_compat.Slot() 91 | @misc.log_exception(_moduleLogger) 92 | def thread_done(): 93 | print "Thread done" 94 | count[0] += 1 95 | if count[0] == 2: 96 | print "Quitting" 97 | app.exit(0) 98 | print "Done" 99 | producerThread.finished.connect(thread_done) 100 | consumerThread.finished.connect(thread_done) 101 | 102 | producerThread.start() 103 | consumerThread.start() 104 | print "Status %s" % app.exec_() 105 | 106 | 107 | if __name__ == "__main__": 108 | run_producer_consumer() 109 | -------------------------------------------------------------------------------- /util/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | -------------------------------------------------------------------------------- /util/algorithms.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | @note Source http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66448 5 | """ 6 | 7 | import itertools 8 | import functools 9 | import datetime 10 | import types 11 | import array 12 | import random 13 | 14 | 15 | def ordered_itr(collection): 16 | """ 17 | >>> [v for v in ordered_itr({"a": 1, "b": 2})] 18 | [('a', 1), ('b', 2)] 19 | >>> [v for v in ordered_itr([3, 1, 10, -20])] 20 | [-20, 1, 3, 10] 21 | """ 22 | if isinstance(collection, types.DictType): 23 | keys = list(collection.iterkeys()) 24 | keys.sort() 25 | for key in keys: 26 | yield key, collection[key] 27 | else: 28 | values = list(collection) 29 | values.sort() 30 | for value in values: 31 | yield value 32 | 33 | 34 | def itercat(*iterators): 35 | """ 36 | Concatenate several iterators into one. 37 | 38 | >>> [v for v in itercat([1, 2, 3], [4, 1, 3])] 39 | [1, 2, 3, 4, 1, 3] 40 | """ 41 | for i in iterators: 42 | for x in i: 43 | yield x 44 | 45 | 46 | def product(*args, **kwds): 47 | # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy 48 | # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111 49 | pools = map(tuple, args) * kwds.get('repeat', 1) 50 | result = [[]] 51 | for pool in pools: 52 | result = [x+[y] for x in result for y in pool] 53 | for prod in result: 54 | yield tuple(prod) 55 | 56 | 57 | def iterwhile(func, iterator): 58 | """ 59 | Iterate for as long as func(value) returns true. 60 | >>> through = lambda b: b 61 | >>> [v for v in iterwhile(through, [True, True, False])] 62 | [True, True] 63 | """ 64 | iterator = iter(iterator) 65 | while 1: 66 | next = iterator.next() 67 | if not func(next): 68 | raise StopIteration 69 | yield next 70 | 71 | 72 | def iterfirst(iterator, count=1): 73 | """ 74 | Iterate through 'count' first values. 75 | 76 | >>> [v for v in iterfirst([1, 2, 3, 4, 5], 3)] 77 | [1, 2, 3] 78 | """ 79 | iterator = iter(iterator) 80 | for i in xrange(count): 81 | yield iterator.next() 82 | 83 | 84 | def iterstep(iterator, n): 85 | """ 86 | Iterate every nth value. 87 | 88 | >>> [v for v in iterstep([1, 2, 3, 4, 5], 1)] 89 | [1, 2, 3, 4, 5] 90 | >>> [v for v in iterstep([1, 2, 3, 4, 5], 2)] 91 | [1, 3, 5] 92 | >>> [v for v in iterstep([1, 2, 3, 4, 5], 3)] 93 | [1, 4] 94 | """ 95 | iterator = iter(iterator) 96 | while True: 97 | yield iterator.next() 98 | # skip n-1 values 99 | for dummy in xrange(n-1): 100 | iterator.next() 101 | 102 | 103 | def itergroup(iterator, count, padValue = None): 104 | """ 105 | Iterate in groups of 'count' values. If there 106 | aren't enough values, the last result is padded with 107 | None. 108 | 109 | >>> for val in itergroup([1, 2, 3, 4, 5, 6], 3): 110 | ... print tuple(val) 111 | (1, 2, 3) 112 | (4, 5, 6) 113 | >>> for val in itergroup([1, 2, 3, 4, 5, 6], 3): 114 | ... print list(val) 115 | [1, 2, 3] 116 | [4, 5, 6] 117 | >>> for val in itergroup([1, 2, 3, 4, 5, 6, 7], 3): 118 | ... print tuple(val) 119 | (1, 2, 3) 120 | (4, 5, 6) 121 | (7, None, None) 122 | >>> for val in itergroup("123456", 3): 123 | ... print tuple(val) 124 | ('1', '2', '3') 125 | ('4', '5', '6') 126 | >>> for val in itergroup("123456", 3): 127 | ... print repr("".join(val)) 128 | '123' 129 | '456' 130 | """ 131 | paddedIterator = itertools.chain(iterator, itertools.repeat(padValue, count-1)) 132 | nIterators = (paddedIterator, ) * count 133 | return itertools.izip(*nIterators) 134 | 135 | 136 | def xzip(*iterators): 137 | """Iterative version of builtin 'zip'.""" 138 | iterators = itertools.imap(iter, iterators) 139 | while 1: 140 | yield tuple([x.next() for x in iterators]) 141 | 142 | 143 | def xmap(func, *iterators): 144 | """Iterative version of builtin 'map'.""" 145 | iterators = itertools.imap(iter, iterators) 146 | values_left = [1] 147 | 148 | def values(): 149 | # Emulate map behaviour, i.e. shorter 150 | # sequences are padded with None when 151 | # they run out of values. 152 | values_left[0] = 0 153 | for i in range(len(iterators)): 154 | iterator = iterators[i] 155 | if iterator is None: 156 | yield None 157 | else: 158 | try: 159 | yield iterator.next() 160 | values_left[0] = 1 161 | except StopIteration: 162 | iterators[i] = None 163 | yield None 164 | while 1: 165 | args = tuple(values()) 166 | if not values_left[0]: 167 | raise StopIteration 168 | yield func(*args) 169 | 170 | 171 | def xfilter(func, iterator): 172 | """Iterative version of builtin 'filter'.""" 173 | iterator = iter(iterator) 174 | while 1: 175 | next = iterator.next() 176 | if func(next): 177 | yield next 178 | 179 | 180 | def xreduce(func, iterator, default=None): 181 | """Iterative version of builtin 'reduce'.""" 182 | iterator = iter(iterator) 183 | try: 184 | prev = iterator.next() 185 | except StopIteration: 186 | return default 187 | single = 1 188 | for next in iterator: 189 | single = 0 190 | prev = func(prev, next) 191 | if single: 192 | return func(prev, default) 193 | return prev 194 | 195 | 196 | def daterange(begin, end, delta = datetime.timedelta(1)): 197 | """ 198 | Form a range of dates and iterate over them. 199 | 200 | Arguments: 201 | begin -- a date (or datetime) object; the beginning of the range. 202 | end -- a date (or datetime) object; the end of the range. 203 | delta -- (optional) a datetime.timedelta object; how much to step each iteration. 204 | Default step is 1 day. 205 | 206 | Usage: 207 | """ 208 | if not isinstance(delta, datetime.timedelta): 209 | delta = datetime.timedelta(delta) 210 | 211 | ZERO = datetime.timedelta(0) 212 | 213 | if begin < end: 214 | if delta <= ZERO: 215 | raise StopIteration 216 | test = end.__gt__ 217 | else: 218 | if delta >= ZERO: 219 | raise StopIteration 220 | test = end.__lt__ 221 | 222 | while test(begin): 223 | yield begin 224 | begin += delta 225 | 226 | 227 | class LazyList(object): 228 | """ 229 | A Sequence whose values are computed lazily by an iterator. 230 | 231 | Module for the creation and use of iterator-based lazy lists. 232 | this module defines a class LazyList which can be used to represent sequences 233 | of values generated lazily. One can also create recursively defined lazy lists 234 | that generate their values based on ones previously generated. 235 | 236 | Backport to python 2.5 by Michael Pust 237 | """ 238 | 239 | __author__ = 'Dan Spitz' 240 | 241 | def __init__(self, iterable): 242 | self._exhausted = False 243 | self._iterator = iter(iterable) 244 | self._data = [] 245 | 246 | def __len__(self): 247 | """Get the length of a LazyList's computed data.""" 248 | return len(self._data) 249 | 250 | def __getitem__(self, i): 251 | """Get an item from a LazyList. 252 | i should be a positive integer or a slice object.""" 253 | if isinstance(i, int): 254 | #index has not yet been yielded by iterator (or iterator exhausted 255 | #before reaching that index) 256 | if i >= len(self): 257 | self.exhaust(i) 258 | elif i < 0: 259 | raise ValueError('cannot index LazyList with negative number') 260 | return self._data[i] 261 | 262 | #LazyList slices are iterators over a portion of the list. 263 | elif isinstance(i, slice): 264 | start, stop, step = i.start, i.stop, i.step 265 | if any(x is not None and x < 0 for x in (start, stop, step)): 266 | raise ValueError('cannot index or step through a LazyList with' 267 | 'a negative number') 268 | #set start and step to their integer defaults if they are None. 269 | if start is None: 270 | start = 0 271 | if step is None: 272 | step = 1 273 | 274 | def LazyListIterator(): 275 | count = start 276 | predicate = ( 277 | (lambda: True) 278 | if stop is None 279 | else (lambda: count < stop) 280 | ) 281 | while predicate(): 282 | try: 283 | yield self[count] 284 | #slices can go out of actual index range without raising an 285 | #error 286 | except IndexError: 287 | break 288 | count += step 289 | return LazyListIterator() 290 | 291 | raise TypeError('i must be an integer or slice') 292 | 293 | def __iter__(self): 294 | """return an iterator over each value in the sequence, 295 | whether it has been computed yet or not.""" 296 | return self[:] 297 | 298 | def computed(self): 299 | """Return an iterator over the values in a LazyList that have 300 | already been computed.""" 301 | return self[:len(self)] 302 | 303 | def exhaust(self, index = None): 304 | """Exhaust the iterator generating this LazyList's values. 305 | if index is None, this will exhaust the iterator completely. 306 | Otherwise, it will iterate over the iterator until either the list 307 | has a value for index or the iterator is exhausted. 308 | """ 309 | if self._exhausted: 310 | return 311 | if index is None: 312 | ind_range = itertools.count(len(self)) 313 | else: 314 | ind_range = range(len(self), index + 1) 315 | 316 | for ind in ind_range: 317 | try: 318 | self._data.append(self._iterator.next()) 319 | except StopIteration: #iterator is fully exhausted 320 | self._exhausted = True 321 | break 322 | 323 | 324 | class RecursiveLazyList(LazyList): 325 | 326 | def __init__(self, prod, *args, **kwds): 327 | super(RecursiveLazyList, self).__init__(prod(self, *args, **kwds)) 328 | 329 | 330 | class RecursiveLazyListFactory: 331 | 332 | def __init__(self, producer): 333 | self._gen = producer 334 | 335 | def __call__(self, *a, **kw): 336 | return RecursiveLazyList(self._gen, *a, **kw) 337 | 338 | 339 | def lazylist(gen): 340 | """ 341 | Decorator for creating a RecursiveLazyList subclass. 342 | This should decorate a generator function taking the LazyList object as its 343 | first argument which yields the contents of the list in order. 344 | 345 | >>> #fibonnacci sequence in a lazy list. 346 | >>> @lazylist 347 | ... def fibgen(lst): 348 | ... yield 0 349 | ... yield 1 350 | ... for a, b in itertools.izip(lst, lst[1:]): 351 | ... yield a + b 352 | ... 353 | >>> #now fibs can be indexed or iterated over as if it were an infinitely long list containing the fibonnaci sequence 354 | >>> fibs = fibgen() 355 | >>> 356 | >>> #prime numbers in a lazy list. 357 | >>> @lazylist 358 | ... def primegen(lst): 359 | ... yield 2 360 | ... for candidate in itertools.count(3): #start at next number after 2 361 | ... #if candidate is not divisible by any smaller prime numbers, 362 | ... #it is a prime. 363 | ... if all(candidate % p for p in lst.computed()): 364 | ... yield candidate 365 | ... 366 | >>> #same for primes- treat it like an infinitely long list containing all prime numbers. 367 | >>> primes = primegen() 368 | >>> print fibs[0], fibs[1], fibs[2], primes[0], primes[1], primes[2] 369 | 0 1 1 2 3 5 370 | >>> print list(fibs[:10]), list(primes[:10]) 371 | [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] 372 | """ 373 | return RecursiveLazyListFactory(gen) 374 | 375 | 376 | def map_func(f): 377 | """ 378 | >>> import misc 379 | >>> misc.validate_decorator(map_func) 380 | """ 381 | 382 | @functools.wraps(f) 383 | def wrapper(*args): 384 | result = itertools.imap(f, args) 385 | return result 386 | return wrapper 387 | 388 | 389 | def reduce_func(function): 390 | """ 391 | >>> import misc 392 | >>> misc.validate_decorator(reduce_func(lambda x: x)) 393 | """ 394 | 395 | def decorator(f): 396 | 397 | @functools.wraps(f) 398 | def wrapper(*args): 399 | result = reduce(function, f(args)) 400 | return result 401 | return wrapper 402 | return decorator 403 | 404 | 405 | def any_(iterable): 406 | """ 407 | @note Python Version <2.5 408 | 409 | >>> any_([True, True]) 410 | True 411 | >>> any_([True, False]) 412 | True 413 | >>> any_([False, False]) 414 | False 415 | """ 416 | 417 | for element in iterable: 418 | if element: 419 | return True 420 | return False 421 | 422 | 423 | def all_(iterable): 424 | """ 425 | @note Python Version <2.5 426 | 427 | >>> all_([True, True]) 428 | True 429 | >>> all_([True, False]) 430 | False 431 | >>> all_([False, False]) 432 | False 433 | """ 434 | 435 | for element in iterable: 436 | if not element: 437 | return False 438 | return True 439 | 440 | 441 | def for_every(pred, seq): 442 | """ 443 | for_every takes a one argument predicate function and a sequence. 444 | @param pred The predicate function should return true or false. 445 | @returns true if every element in seq returns true for predicate, else returns false. 446 | 447 | >>> for_every (lambda c: c > 5,(6,7,8,9)) 448 | True 449 | 450 | @author Source:http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52907 451 | """ 452 | 453 | for i in seq: 454 | if not pred(i): 455 | return False 456 | return True 457 | 458 | 459 | def there_exists(pred, seq): 460 | """ 461 | there_exists takes a one argument predicate function and a sequence. 462 | @param pred The predicate function should return true or false. 463 | @returns true if any element in seq returns true for predicate, else returns false. 464 | 465 | >>> there_exists (lambda c: c > 5,(6,7,8,9)) 466 | True 467 | 468 | @author Source:http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52907 469 | """ 470 | 471 | for i in seq: 472 | if pred(i): 473 | return True 474 | return False 475 | 476 | 477 | def func_repeat(quantity, func, *args, **kwd): 478 | """ 479 | Meant to be in connection with "reduce" 480 | """ 481 | for i in xrange(quantity): 482 | yield func(*args, **kwd) 483 | 484 | 485 | def function_map(preds, item): 486 | """ 487 | Meant to be in connection with "reduce" 488 | """ 489 | results = (pred(item) for pred in preds) 490 | 491 | return results 492 | 493 | 494 | def functional_if(combiner, preds, item): 495 | """ 496 | Combines the result of a list of predicates applied to item according to combiner 497 | 498 | @see any, every for example combiners 499 | """ 500 | pass_bool = lambda b: b 501 | 502 | bool_results = function_map(preds, item) 503 | return combiner(pass_bool, bool_results) 504 | 505 | 506 | def pushback_itr(itr): 507 | """ 508 | >>> list(pushback_itr(xrange(5))) 509 | [0, 1, 2, 3, 4] 510 | >>> 511 | >>> first = True 512 | >>> itr = pushback_itr(xrange(5)) 513 | >>> for i in itr: 514 | ... print i 515 | ... if first and i == 2: 516 | ... first = False 517 | ... print itr.send(i) 518 | 0 519 | 1 520 | 2 521 | None 522 | 2 523 | 3 524 | 4 525 | >>> 526 | >>> first = True 527 | >>> itr = pushback_itr(xrange(5)) 528 | >>> for i in itr: 529 | ... print i 530 | ... if first and i == 2: 531 | ... first = False 532 | ... print itr.send(i) 533 | ... print itr.send(i) 534 | 0 535 | 1 536 | 2 537 | None 538 | None 539 | 2 540 | 2 541 | 3 542 | 4 543 | >>> 544 | >>> itr = pushback_itr(xrange(5)) 545 | >>> print itr.next() 546 | 0 547 | >>> print itr.next() 548 | 1 549 | >>> print itr.send(10) 550 | None 551 | >>> print itr.next() 552 | 10 553 | >>> print itr.next() 554 | 2 555 | >>> print itr.send(20) 556 | None 557 | >>> print itr.send(30) 558 | None 559 | >>> print itr.send(40) 560 | None 561 | >>> print itr.next() 562 | 40 563 | >>> print itr.next() 564 | 30 565 | >>> print itr.send(50) 566 | None 567 | >>> print itr.next() 568 | 50 569 | >>> print itr.next() 570 | 20 571 | >>> print itr.next() 572 | 3 573 | >>> print itr.next() 574 | 4 575 | """ 576 | for item in itr: 577 | maybePushedBack = yield item 578 | queue = [] 579 | while queue or maybePushedBack is not None: 580 | if maybePushedBack is not None: 581 | queue.append(maybePushedBack) 582 | maybePushedBack = yield None 583 | else: 584 | item = queue.pop() 585 | maybePushedBack = yield item 586 | 587 | 588 | def itr_available(queue, initiallyBlock = False): 589 | if initiallyBlock: 590 | yield queue.get() 591 | while not queue.empty(): 592 | yield queue.get_nowait() 593 | 594 | 595 | class BloomFilter(object): 596 | """ 597 | http://en.wikipedia.org/wiki/Bloom_filter 598 | Sources: 599 | http://code.activestate.com/recipes/577684-bloom-filter/ 600 | http://code.activestate.com/recipes/577686-bloom-filter/ 601 | 602 | >>> from random import sample 603 | >>> from string import ascii_letters 604 | >>> states = '''Alabama Alaska Arizona Arkansas California Colorado Connecticut 605 | ... Delaware Florida Georgia Hawaii Idaho Illinois Indiana Iowa Kansas 606 | ... Kentucky Louisiana Maine Maryland Massachusetts Michigan Minnesota 607 | ... Mississippi Missouri Montana Nebraska Nevada NewHampshire NewJersey 608 | ... NewMexico NewYork NorthCarolina NorthDakota Ohio Oklahoma Oregon 609 | ... Pennsylvania RhodeIsland SouthCarolina SouthDakota Tennessee Texas Utah 610 | ... Vermont Virginia Washington WestVirginia Wisconsin Wyoming'''.split() 611 | >>> bf = BloomFilter(num_bits=1000, num_probes=14) 612 | >>> for state in states: 613 | ... bf.add(state) 614 | >>> numStatesFound = sum(state in bf for state in states) 615 | >>> numStatesFound, len(states) 616 | (50, 50) 617 | >>> trials = 100 618 | >>> numGarbageFound = sum(''.join(sample(ascii_letters, 5)) in bf for i in range(trials)) 619 | >>> numGarbageFound, trials 620 | (0, 100) 621 | """ 622 | 623 | def __init__(self, num_bits, num_probes): 624 | num_words = (num_bits + 31) // 32 625 | self._arr = array.array('B', [0]) * num_words 626 | self._num_probes = num_probes 627 | 628 | def add(self, key): 629 | for i, mask in self._get_probes(key): 630 | self._arr[i] |= mask 631 | 632 | def union(self, bfilter): 633 | if self._match_template(bfilter): 634 | for i, b in enumerate(bfilter._arr): 635 | self._arr[i] |= b 636 | else: 637 | # Union b/w two unrelated bloom filter raises this 638 | raise ValueError("Mismatched bloom filters") 639 | 640 | def intersection(self, bfilter): 641 | if self._match_template(bfilter): 642 | for i, b in enumerate(bfilter._arr): 643 | self._arr[i] &= b 644 | else: 645 | # Intersection b/w two unrelated bloom filter raises this 646 | raise ValueError("Mismatched bloom filters") 647 | 648 | def __contains__(self, key): 649 | return all(self._arr[i] & mask for i, mask in self._get_probes(key)) 650 | 651 | def _match_template(self, bfilter): 652 | return self.num_bits == bfilter.num_bits and self.num_probes == bfilter.num_probes 653 | 654 | def _get_probes(self, key): 655 | hasher = random.Random(key).randrange 656 | for _ in range(self._num_probes): 657 | array_index = hasher(len(self._arr)) 658 | bit_index = hasher(32) 659 | yield array_index, 1 << bit_index 660 | 661 | 662 | if __name__ == "__main__": 663 | import doctest 664 | print doctest.testmod() 665 | -------------------------------------------------------------------------------- /util/concurrent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import with_statement 4 | 5 | import os 6 | import errno 7 | import time 8 | import functools 9 | import contextlib 10 | import logging 11 | 12 | import misc 13 | 14 | 15 | _moduleLogger = logging.getLogger(__name__) 16 | 17 | 18 | class AsyncTaskQueue(object): 19 | 20 | def __init__(self, taskPool): 21 | self._asyncs = [] 22 | self._taskPool = taskPool 23 | 24 | def add_async(self, func): 25 | self.flush() 26 | a = AsyncGeneratorTask(self._taskPool, func) 27 | self._asyncs.append(a) 28 | return a 29 | 30 | def flush(self): 31 | self._asyncs = [a for a in self._asyncs if not a.isDone] 32 | 33 | 34 | class AsyncGeneratorTask(object): 35 | 36 | def __init__(self, pool, func): 37 | self._pool = pool 38 | self._func = func 39 | self._run = None 40 | self._isDone = False 41 | 42 | @property 43 | def isDone(self): 44 | return self._isDone 45 | 46 | def start(self, *args, **kwds): 47 | assert self._run is None, "Task already started" 48 | self._run = self._func(*args, **kwds) 49 | trampoline, args, kwds = self._run.send(None) # priming the function 50 | self._pool.add_task( 51 | trampoline, 52 | args, 53 | kwds, 54 | self.on_success, 55 | self.on_error, 56 | ) 57 | 58 | @misc.log_exception(_moduleLogger) 59 | def on_success(self, result): 60 | _moduleLogger.debug("Processing success for: %r", self._func) 61 | try: 62 | trampoline, args, kwds = self._run.send(result) 63 | except StopIteration, e: 64 | self._isDone = True 65 | else: 66 | self._pool.add_task( 67 | trampoline, 68 | args, 69 | kwds, 70 | self.on_success, 71 | self.on_error, 72 | ) 73 | 74 | @misc.log_exception(_moduleLogger) 75 | def on_error(self, error): 76 | _moduleLogger.debug("Processing error for: %r", self._func) 77 | try: 78 | trampoline, args, kwds = self._run.throw(error) 79 | except StopIteration, e: 80 | self._isDone = True 81 | else: 82 | self._pool.add_task( 83 | trampoline, 84 | args, 85 | kwds, 86 | self.on_success, 87 | self.on_error, 88 | ) 89 | 90 | def __repr__(self): 91 | return "" % (self._func.__name__, id(self)) 92 | 93 | def __hash__(self): 94 | return hash(self._func) 95 | 96 | def __eq__(self, other): 97 | return self._func == other._func 98 | 99 | def __ne__(self, other): 100 | return self._func != other._func 101 | 102 | 103 | def synchronized(lock): 104 | """ 105 | Synchronization decorator. 106 | 107 | >>> import misc 108 | >>> misc.validate_decorator(synchronized(object())) 109 | """ 110 | 111 | def wrap(f): 112 | 113 | @functools.wraps(f) 114 | def newFunction(*args, **kw): 115 | lock.acquire() 116 | try: 117 | return f(*args, **kw) 118 | finally: 119 | lock.release() 120 | return newFunction 121 | return wrap 122 | 123 | 124 | @contextlib.contextmanager 125 | def qlock(queue, gblock = True, gtimeout = None, pblock = True, ptimeout = None): 126 | """ 127 | Locking with a queue, good for when you want to lock an item passed around 128 | 129 | >>> import Queue 130 | >>> item = 5 131 | >>> lock = Queue.Queue() 132 | >>> lock.put(item) 133 | >>> with qlock(lock) as i: 134 | ... print i 135 | 5 136 | """ 137 | item = queue.get(gblock, gtimeout) 138 | try: 139 | yield item 140 | finally: 141 | queue.put(item, pblock, ptimeout) 142 | 143 | 144 | @contextlib.contextmanager 145 | def flock(path, timeout=-1): 146 | WAIT_FOREVER = -1 147 | DELAY = 0.1 148 | timeSpent = 0 149 | 150 | acquired = False 151 | 152 | while timeSpent <= timeout or timeout == WAIT_FOREVER: 153 | try: 154 | fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR) 155 | acquired = True 156 | break 157 | except OSError, e: 158 | if e.errno != errno.EEXIST: 159 | raise 160 | time.sleep(DELAY) 161 | timeSpent += DELAY 162 | 163 | assert acquired, "Failed to grab file-lock %s within timeout %d" % (path, timeout) 164 | 165 | try: 166 | yield fd 167 | finally: 168 | os.unlink(path) 169 | -------------------------------------------------------------------------------- /util/coroutines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Uses for generators 5 | * Pull pipelining (iterators) 6 | * Push pipelining (coroutines) 7 | * State machines (coroutines) 8 | * "Cooperative multitasking" (coroutines) 9 | * Algorithm -> Object transform for cohesiveness (for example context managers) (coroutines) 10 | 11 | Design considerations 12 | * When should a stage pass on exceptions or have it thrown within it? 13 | * When should a stage pass on GeneratorExits? 14 | * Is there a way to either turn a push generator into a iterator or to use 15 | comprehensions syntax for push generators (I doubt it) 16 | * When should the stage try and send data in both directions 17 | * Since pull generators (generators), push generators (coroutines), subroutines, and coroutines are all coroutines, maybe we should rename the push generators to not confuse them, like signals/slots? and then refer to two-way generators as coroutines 18 | ** If so, make s* and co* implementation of functions 19 | """ 20 | 21 | import threading 22 | import Queue 23 | import pickle 24 | import functools 25 | import itertools 26 | import xml.sax 27 | import xml.parsers.expat 28 | 29 | 30 | def autostart(func): 31 | """ 32 | >>> @autostart 33 | ... def grep_sink(pattern): 34 | ... print "Looking for %s" % pattern 35 | ... while True: 36 | ... line = yield 37 | ... if pattern in line: 38 | ... print line, 39 | >>> g = grep_sink("python") 40 | Looking for python 41 | >>> g.send("Yeah but no but yeah but no") 42 | >>> g.send("A series of tubes") 43 | >>> g.send("python generators rock!") 44 | python generators rock! 45 | >>> g.close() 46 | """ 47 | 48 | @functools.wraps(func) 49 | def start(*args, **kwargs): 50 | cr = func(*args, **kwargs) 51 | cr.next() 52 | return cr 53 | 54 | return start 55 | 56 | 57 | @autostart 58 | def printer_sink(format = "%s"): 59 | """ 60 | >>> pr = printer_sink("%r") 61 | >>> pr.send("Hello") 62 | 'Hello' 63 | >>> pr.send("5") 64 | '5' 65 | >>> pr.send(5) 66 | 5 67 | >>> p = printer_sink() 68 | >>> p.send("Hello") 69 | Hello 70 | >>> p.send("World") 71 | World 72 | >>> # p.throw(RuntimeError, "Goodbye") 73 | >>> # p.send("Meh") 74 | >>> # p.close() 75 | """ 76 | while True: 77 | item = yield 78 | print format % (item, ) 79 | 80 | 81 | @autostart 82 | def null_sink(): 83 | """ 84 | Good for uses like with cochain to pick up any slack 85 | """ 86 | while True: 87 | item = yield 88 | 89 | 90 | def itr_source(itr, target): 91 | """ 92 | >>> itr_source(xrange(2), printer_sink()) 93 | 0 94 | 1 95 | """ 96 | for item in itr: 97 | target.send(item) 98 | 99 | 100 | @autostart 101 | def cofilter(predicate, target): 102 | """ 103 | >>> p = printer_sink() 104 | >>> cf = cofilter(None, p) 105 | >>> cf.send("") 106 | >>> cf.send("Hello") 107 | Hello 108 | >>> cf.send([]) 109 | >>> cf.send([1, 2]) 110 | [1, 2] 111 | >>> cf.send(False) 112 | >>> cf.send(True) 113 | True 114 | >>> cf.send(0) 115 | >>> cf.send(1) 116 | 1 117 | >>> # cf.throw(RuntimeError, "Goodbye") 118 | >>> # cf.send(False) 119 | >>> # cf.send(True) 120 | >>> # cf.close() 121 | """ 122 | if predicate is None: 123 | predicate = bool 124 | 125 | while True: 126 | try: 127 | item = yield 128 | if predicate(item): 129 | target.send(item) 130 | except StandardError, e: 131 | target.throw(e.__class__, e.message) 132 | 133 | 134 | @autostart 135 | def comap(function, target): 136 | """ 137 | >>> p = printer_sink() 138 | >>> cm = comap(lambda x: x+1, p) 139 | >>> cm.send(0) 140 | 1 141 | >>> cm.send(1.0) 142 | 2.0 143 | >>> cm.send(-2) 144 | -1 145 | >>> # cm.throw(RuntimeError, "Goodbye") 146 | >>> # cm.send(0) 147 | >>> # cm.send(1.0) 148 | >>> # cm.close() 149 | """ 150 | while True: 151 | try: 152 | item = yield 153 | mappedItem = function(item) 154 | target.send(mappedItem) 155 | except StandardError, e: 156 | target.throw(e.__class__, e.message) 157 | 158 | 159 | def func_sink(function): 160 | return comap(function, null_sink()) 161 | 162 | 163 | def expand_positional(function): 164 | 165 | @functools.wraps(function) 166 | def expander(item): 167 | return function(*item) 168 | 169 | return expander 170 | 171 | 172 | @autostart 173 | def append_sink(l): 174 | """ 175 | >>> l = [] 176 | >>> apps = append_sink(l) 177 | >>> apps.send(1) 178 | >>> apps.send(2) 179 | >>> apps.send(3) 180 | >>> print l 181 | [1, 2, 3] 182 | """ 183 | while True: 184 | item = yield 185 | l.append(item) 186 | 187 | 188 | @autostart 189 | def last_n_sink(l, n = 1): 190 | """ 191 | >>> l = [] 192 | >>> lns = last_n_sink(l) 193 | >>> lns.send(1) 194 | >>> lns.send(2) 195 | >>> lns.send(3) 196 | >>> print l 197 | [3] 198 | """ 199 | del l[:] 200 | while True: 201 | item = yield 202 | extraCount = len(l) - n + 1 203 | if 0 < extraCount: 204 | del l[0:extraCount] 205 | l.append(item) 206 | 207 | 208 | @autostart 209 | def coreduce(target, function, initializer = None): 210 | """ 211 | >>> reduceResult = [] 212 | >>> lns = last_n_sink(reduceResult) 213 | >>> cr = coreduce(lns, lambda x, y: x + y, 0) 214 | >>> cr.send(1) 215 | >>> cr.send(2) 216 | >>> cr.send(3) 217 | >>> print reduceResult 218 | [6] 219 | >>> cr = coreduce(lns, lambda x, y: x + y) 220 | >>> cr.send(1) 221 | >>> cr.send(2) 222 | >>> cr.send(3) 223 | >>> print reduceResult 224 | [6] 225 | """ 226 | isFirst = True 227 | cumulativeRef = initializer 228 | while True: 229 | item = yield 230 | if isFirst and initializer is None: 231 | cumulativeRef = item 232 | else: 233 | cumulativeRef = function(cumulativeRef, item) 234 | target.send(cumulativeRef) 235 | isFirst = False 236 | 237 | 238 | @autostart 239 | def cotee(targets): 240 | """ 241 | Takes a sequence of coroutines and sends the received items to all of them 242 | 243 | >>> ct = cotee((printer_sink("1 %s"), printer_sink("2 %s"))) 244 | >>> ct.send("Hello") 245 | 1 Hello 246 | 2 Hello 247 | >>> ct.send("World") 248 | 1 World 249 | 2 World 250 | >>> # ct.throw(RuntimeError, "Goodbye") 251 | >>> # ct.send("Meh") 252 | >>> # ct.close() 253 | """ 254 | while True: 255 | try: 256 | item = yield 257 | for target in targets: 258 | target.send(item) 259 | except StandardError, e: 260 | for target in targets: 261 | target.throw(e.__class__, e.message) 262 | 263 | 264 | class CoTee(object): 265 | """ 266 | >>> ct = CoTee() 267 | >>> ct.register_sink(printer_sink("1 %s")) 268 | >>> ct.register_sink(printer_sink("2 %s")) 269 | >>> ct.stage.send("Hello") 270 | 1 Hello 271 | 2 Hello 272 | >>> ct.stage.send("World") 273 | 1 World 274 | 2 World 275 | >>> ct.register_sink(printer_sink("3 %s")) 276 | >>> ct.stage.send("Foo") 277 | 1 Foo 278 | 2 Foo 279 | 3 Foo 280 | >>> # ct.stage.throw(RuntimeError, "Goodbye") 281 | >>> # ct.stage.send("Meh") 282 | >>> # ct.stage.close() 283 | """ 284 | 285 | def __init__(self): 286 | self.stage = self._stage() 287 | self._targets = [] 288 | 289 | def register_sink(self, sink): 290 | self._targets.append(sink) 291 | 292 | def unregister_sink(self, sink): 293 | self._targets.remove(sink) 294 | 295 | def restart(self): 296 | self.stage = self._stage() 297 | 298 | @autostart 299 | def _stage(self): 300 | while True: 301 | try: 302 | item = yield 303 | for target in self._targets: 304 | target.send(item) 305 | except StandardError, e: 306 | for target in self._targets: 307 | target.throw(e.__class__, e.message) 308 | 309 | 310 | def _flush_queue(queue): 311 | while not queue.empty(): 312 | yield queue.get() 313 | 314 | 315 | @autostart 316 | def cocount(target, start = 0): 317 | """ 318 | >>> cc = cocount(printer_sink("%s")) 319 | >>> cc.send("a") 320 | 0 321 | >>> cc.send(None) 322 | 1 323 | >>> cc.send([]) 324 | 2 325 | >>> cc.send(0) 326 | 3 327 | """ 328 | for i in itertools.count(start): 329 | item = yield 330 | target.send(i) 331 | 332 | 333 | @autostart 334 | def coenumerate(target, start = 0): 335 | """ 336 | >>> ce = coenumerate(printer_sink("%r")) 337 | >>> ce.send("a") 338 | (0, 'a') 339 | >>> ce.send(None) 340 | (1, None) 341 | >>> ce.send([]) 342 | (2, []) 343 | >>> ce.send(0) 344 | (3, 0) 345 | """ 346 | for i in itertools.count(start): 347 | item = yield 348 | decoratedItem = i, item 349 | target.send(decoratedItem) 350 | 351 | 352 | @autostart 353 | def corepeat(target, elem): 354 | """ 355 | >>> cr = corepeat(printer_sink("%s"), "Hello World") 356 | >>> cr.send("a") 357 | Hello World 358 | >>> cr.send(None) 359 | Hello World 360 | >>> cr.send([]) 361 | Hello World 362 | >>> cr.send(0) 363 | Hello World 364 | """ 365 | while True: 366 | item = yield 367 | target.send(elem) 368 | 369 | 370 | @autostart 371 | def cointercept(target, elems): 372 | """ 373 | >>> cr = cointercept(printer_sink("%s"), [1, 2, 3, 4]) 374 | >>> cr.send("a") 375 | 1 376 | >>> cr.send(None) 377 | 2 378 | >>> cr.send([]) 379 | 3 380 | >>> cr.send(0) 381 | 4 382 | >>> cr.send("Bye") 383 | Traceback (most recent call last): 384 | File "/usr/lib/python2.5/doctest.py", line 1228, in __run 385 | compileflags, 1) in test.globs 386 | File "", line 1, in 387 | cr.send("Bye") 388 | StopIteration 389 | """ 390 | item = yield 391 | for elem in elems: 392 | target.send(elem) 393 | item = yield 394 | 395 | 396 | @autostart 397 | def codropwhile(target, pred): 398 | """ 399 | >>> cdw = codropwhile(printer_sink("%s"), lambda x: x) 400 | >>> cdw.send([0, 1, 2]) 401 | >>> cdw.send(1) 402 | >>> cdw.send(True) 403 | >>> cdw.send(False) 404 | >>> cdw.send([0, 1, 2]) 405 | [0, 1, 2] 406 | >>> cdw.send(1) 407 | 1 408 | >>> cdw.send(True) 409 | True 410 | """ 411 | while True: 412 | item = yield 413 | if not pred(item): 414 | break 415 | 416 | while True: 417 | item = yield 418 | target.send(item) 419 | 420 | 421 | @autostart 422 | def cotakewhile(target, pred): 423 | """ 424 | >>> ctw = cotakewhile(printer_sink("%s"), lambda x: x) 425 | >>> ctw.send([0, 1, 2]) 426 | [0, 1, 2] 427 | >>> ctw.send(1) 428 | 1 429 | >>> ctw.send(True) 430 | True 431 | >>> ctw.send(False) 432 | >>> ctw.send([0, 1, 2]) 433 | >>> ctw.send(1) 434 | >>> ctw.send(True) 435 | """ 436 | while True: 437 | item = yield 438 | if not pred(item): 439 | break 440 | target.send(item) 441 | 442 | while True: 443 | item = yield 444 | 445 | 446 | @autostart 447 | def coslice(target, lower, upper): 448 | """ 449 | >>> cs = coslice(printer_sink("%r"), 3, 5) 450 | >>> cs.send("0") 451 | >>> cs.send("1") 452 | >>> cs.send("2") 453 | >>> cs.send("3") 454 | '3' 455 | >>> cs.send("4") 456 | '4' 457 | >>> cs.send("5") 458 | >>> cs.send("6") 459 | """ 460 | for i in xrange(lower): 461 | item = yield 462 | for i in xrange(upper - lower): 463 | item = yield 464 | target.send(item) 465 | while True: 466 | item = yield 467 | 468 | 469 | @autostart 470 | def cochain(targets): 471 | """ 472 | >>> cr = cointercept(printer_sink("good %s"), [1, 2, 3, 4]) 473 | >>> cc = cochain([cr, printer_sink("end %s")]) 474 | >>> cc.send("a") 475 | good 1 476 | >>> cc.send(None) 477 | good 2 478 | >>> cc.send([]) 479 | good 3 480 | >>> cc.send(0) 481 | good 4 482 | >>> cc.send("Bye") 483 | end Bye 484 | """ 485 | behind = [] 486 | for target in targets: 487 | try: 488 | while behind: 489 | item = behind.pop() 490 | target.send(item) 491 | while True: 492 | item = yield 493 | target.send(item) 494 | except StopIteration: 495 | behind.append(item) 496 | 497 | 498 | @autostart 499 | def queue_sink(queue): 500 | """ 501 | >>> q = Queue.Queue() 502 | >>> qs = queue_sink(q) 503 | >>> qs.send("Hello") 504 | >>> qs.send("World") 505 | >>> qs.throw(RuntimeError, "Goodbye") 506 | >>> qs.send("Meh") 507 | >>> qs.close() 508 | >>> print [i for i in _flush_queue(q)] 509 | [(None, 'Hello'), (None, 'World'), (, 'Goodbye'), (None, 'Meh'), (, None)] 510 | """ 511 | while True: 512 | try: 513 | item = yield 514 | queue.put((None, item)) 515 | except StandardError, e: 516 | queue.put((e.__class__, e.message)) 517 | except GeneratorExit: 518 | queue.put((GeneratorExit, None)) 519 | raise 520 | 521 | 522 | def decode_item(item, target): 523 | if item[0] is None: 524 | target.send(item[1]) 525 | return False 526 | elif item[0] is GeneratorExit: 527 | target.close() 528 | return True 529 | else: 530 | target.throw(item[0], item[1]) 531 | return False 532 | 533 | 534 | def queue_source(queue, target): 535 | """ 536 | >>> q = Queue.Queue() 537 | >>> for i in [ 538 | ... (None, 'Hello'), 539 | ... (None, 'World'), 540 | ... (GeneratorExit, None), 541 | ... ]: 542 | ... q.put(i) 543 | >>> qs = queue_source(q, printer_sink()) 544 | Hello 545 | World 546 | """ 547 | isDone = False 548 | while not isDone: 549 | item = queue.get() 550 | isDone = decode_item(item, target) 551 | 552 | 553 | def threaded_stage(target, thread_factory = threading.Thread): 554 | messages = Queue.Queue() 555 | 556 | run_source = functools.partial(queue_source, messages, target) 557 | thread_factory(target=run_source).start() 558 | 559 | # Sink running in current thread 560 | return functools.partial(queue_sink, messages) 561 | 562 | 563 | @autostart 564 | def pickle_sink(f): 565 | while True: 566 | try: 567 | item = yield 568 | pickle.dump((None, item), f) 569 | except StandardError, e: 570 | pickle.dump((e.__class__, e.message), f) 571 | except GeneratorExit: 572 | pickle.dump((GeneratorExit, ), f) 573 | raise 574 | except StopIteration: 575 | f.close() 576 | return 577 | 578 | 579 | def pickle_source(f, target): 580 | try: 581 | isDone = False 582 | while not isDone: 583 | item = pickle.load(f) 584 | isDone = decode_item(item, target) 585 | except EOFError: 586 | target.close() 587 | 588 | 589 | class EventHandler(object, xml.sax.ContentHandler): 590 | 591 | START = "start" 592 | TEXT = "text" 593 | END = "end" 594 | 595 | def __init__(self, target): 596 | object.__init__(self) 597 | xml.sax.ContentHandler.__init__(self) 598 | self._target = target 599 | 600 | def startElement(self, name, attrs): 601 | self._target.send((self.START, (name, attrs._attrs))) 602 | 603 | def characters(self, text): 604 | self._target.send((self.TEXT, text)) 605 | 606 | def endElement(self, name): 607 | self._target.send((self.END, name)) 608 | 609 | 610 | def expat_parse(f, target): 611 | parser = xml.parsers.expat.ParserCreate() 612 | parser.buffer_size = 65536 613 | parser.buffer_text = True 614 | parser.returns_unicode = False 615 | parser.StartElementHandler = lambda name, attrs: target.send(('start', (name, attrs))) 616 | parser.EndElementHandler = lambda name: target.send(('end', name)) 617 | parser.CharacterDataHandler = lambda data: target.send(('text', data)) 618 | parser.ParseFile(f) 619 | 620 | 621 | if __name__ == "__main__": 622 | import doctest 623 | doctest.testmod() 624 | -------------------------------------------------------------------------------- /util/go_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import with_statement 4 | 5 | import time 6 | import functools 7 | import threading 8 | import Queue 9 | import logging 10 | 11 | import gobject 12 | 13 | import algorithms 14 | import misc 15 | 16 | 17 | _moduleLogger = logging.getLogger(__name__) 18 | 19 | 20 | def make_idler(func): 21 | """ 22 | Decorator that makes a generator-function into a function that will continue execution on next call 23 | """ 24 | a = [] 25 | 26 | @functools.wraps(func) 27 | def decorated_func(*args, **kwds): 28 | if not a: 29 | a.append(func(*args, **kwds)) 30 | try: 31 | a[0].next() 32 | return True 33 | except StopIteration: 34 | del a[:] 35 | return False 36 | 37 | return decorated_func 38 | 39 | 40 | def async(func): 41 | """ 42 | Make a function mainloop friendly. the function will be called at the 43 | next mainloop idle state. 44 | 45 | >>> import misc 46 | >>> misc.validate_decorator(async) 47 | """ 48 | 49 | @functools.wraps(func) 50 | def new_function(*args, **kwargs): 51 | 52 | def async_function(): 53 | func(*args, **kwargs) 54 | return False 55 | 56 | gobject.idle_add(async_function) 57 | 58 | return new_function 59 | 60 | 61 | class Async(object): 62 | 63 | def __init__(self, func, once = True): 64 | self.__func = func 65 | self.__idleId = None 66 | self.__once = once 67 | 68 | def start(self): 69 | assert self.__idleId is None 70 | if self.__once: 71 | self.__idleId = gobject.idle_add(self._on_once) 72 | else: 73 | self.__idleId = gobject.idle_add(self.__func) 74 | 75 | def is_running(self): 76 | return self.__idleId is not None 77 | 78 | def cancel(self): 79 | if self.__idleId is not None: 80 | gobject.source_remove(self.__idleId) 81 | self.__idleId = None 82 | 83 | def __call__(self): 84 | return self.start() 85 | 86 | @misc.log_exception(_moduleLogger) 87 | def _on_once(self): 88 | self.cancel() 89 | try: 90 | self.__func() 91 | except Exception: 92 | pass 93 | return False 94 | 95 | 96 | class Timeout(object): 97 | 98 | def __init__(self, func, once = True): 99 | self.__func = func 100 | self.__timeoutId = None 101 | self.__once = once 102 | 103 | def start(self, **kwds): 104 | assert self.__timeoutId is None 105 | 106 | callback = self._on_once if self.__once else self.__func 107 | 108 | assert len(kwds) == 1 109 | timeoutInSeconds = kwds["seconds"] 110 | assert 0 <= timeoutInSeconds 111 | 112 | if timeoutInSeconds == 0: 113 | self.__timeoutId = gobject.idle_add(callback) 114 | else: 115 | self.__timeoutId = timeout_add_seconds(timeoutInSeconds, callback) 116 | 117 | def is_running(self): 118 | return self.__timeoutId is not None 119 | 120 | def cancel(self): 121 | if self.__timeoutId is not None: 122 | gobject.source_remove(self.__timeoutId) 123 | self.__timeoutId = None 124 | 125 | def __call__(self, **kwds): 126 | return self.start(**kwds) 127 | 128 | @misc.log_exception(_moduleLogger) 129 | def _on_once(self): 130 | self.cancel() 131 | try: 132 | self.__func() 133 | except Exception: 134 | pass 135 | return False 136 | 137 | 138 | _QUEUE_EMPTY = object() 139 | 140 | 141 | class FutureThread(object): 142 | 143 | def __init__(self): 144 | self.__workQueue = Queue.Queue() 145 | self.__thread = threading.Thread( 146 | name = type(self).__name__, 147 | target = self.__consume_queue, 148 | ) 149 | self.__isRunning = True 150 | 151 | def start(self): 152 | self.__thread.start() 153 | 154 | def stop(self): 155 | self.__isRunning = False 156 | for _ in algorithms.itr_available(self.__workQueue): 157 | pass # eat up queue to cut down dumb work 158 | self.__workQueue.put(_QUEUE_EMPTY) 159 | 160 | def clear_tasks(self): 161 | for _ in algorithms.itr_available(self.__workQueue): 162 | pass # eat up queue to cut down dumb work 163 | 164 | def add_task(self, func, args, kwds, on_success, on_error): 165 | task = func, args, kwds, on_success, on_error 166 | self.__workQueue.put(task) 167 | 168 | @misc.log_exception(_moduleLogger) 169 | def __trampoline_callback(self, on_success, on_error, isError, result): 170 | if not self.__isRunning: 171 | if isError: 172 | _moduleLogger.error("Masking: %s" % (result, )) 173 | isError = True 174 | result = StopIteration("Cancelling all callbacks") 175 | callback = on_success if not isError else on_error 176 | try: 177 | callback(result) 178 | except Exception: 179 | _moduleLogger.exception("Callback errored") 180 | return False 181 | 182 | @misc.log_exception(_moduleLogger) 183 | def __consume_queue(self): 184 | while True: 185 | task = self.__workQueue.get() 186 | if task is _QUEUE_EMPTY: 187 | break 188 | func, args, kwds, on_success, on_error = task 189 | 190 | try: 191 | result = func(*args, **kwds) 192 | isError = False 193 | except Exception, e: 194 | _moduleLogger.error("Error, passing it back to the main thread") 195 | result = e 196 | isError = True 197 | self.__workQueue.task_done() 198 | 199 | gobject.idle_add(self.__trampoline_callback, on_success, on_error, isError, result) 200 | _moduleLogger.debug("Shutting down worker thread") 201 | 202 | 203 | class AutoSignal(object): 204 | 205 | def __init__(self, toplevel): 206 | self.__disconnectPool = [] 207 | toplevel.connect("destroy", self.__on_destroy) 208 | 209 | def connect_auto(self, widget, *args): 210 | id = widget.connect(*args) 211 | self.__disconnectPool.append((widget, id)) 212 | 213 | @misc.log_exception(_moduleLogger) 214 | def __on_destroy(self, widget): 215 | _moduleLogger.debug("Destroy: %r (%s to clean up)" % (self, len(self.__disconnectPool))) 216 | for widget, id in self.__disconnectPool: 217 | widget.disconnect(id) 218 | del self.__disconnectPool[:] 219 | 220 | 221 | def throttled(minDelay, queue): 222 | """ 223 | Throttle the calls to a function by queueing all the calls that happen 224 | before the minimum delay 225 | 226 | >>> import misc 227 | >>> import Queue 228 | >>> misc.validate_decorator(throttled(0, Queue.Queue())) 229 | """ 230 | 231 | def actual_decorator(func): 232 | 233 | lastCallTime = [None] 234 | 235 | def process_queue(): 236 | if 0 < len(queue): 237 | func, args, kwargs = queue.pop(0) 238 | lastCallTime[0] = time.time() * 1000 239 | func(*args, **kwargs) 240 | return False 241 | 242 | @functools.wraps(func) 243 | def new_function(*args, **kwargs): 244 | now = time.time() * 1000 245 | if ( 246 | lastCallTime[0] is None or 247 | (now - lastCallTime >= minDelay) 248 | ): 249 | lastCallTime[0] = now 250 | func(*args, **kwargs) 251 | else: 252 | queue.append((func, args, kwargs)) 253 | lastCallDelta = now - lastCallTime[0] 254 | processQueueTimeout = int(minDelay * len(queue) - lastCallDelta) 255 | gobject.timeout_add(processQueueTimeout, process_queue) 256 | 257 | return new_function 258 | 259 | return actual_decorator 260 | 261 | 262 | def _old_timeout_add_seconds(timeout, callback): 263 | return gobject.timeout_add(timeout * 1000, callback) 264 | 265 | 266 | def _timeout_add_seconds(timeout, callback): 267 | return gobject.timeout_add_seconds(timeout, callback) 268 | 269 | 270 | try: 271 | gobject.timeout_add_seconds 272 | timeout_add_seconds = _timeout_add_seconds 273 | except AttributeError: 274 | timeout_add_seconds = _old_timeout_add_seconds 275 | -------------------------------------------------------------------------------- /util/gtk_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import with_statement 4 | from __future__ import division 5 | 6 | import contextlib 7 | import logging 8 | 9 | import gtk 10 | 11 | 12 | _moduleLogger = logging.getLogger(__name__) 13 | 14 | 15 | @contextlib.contextmanager 16 | def gtk_lock(): 17 | gtk.gdk.threads_enter() 18 | try: 19 | yield 20 | finally: 21 | gtk.gdk.threads_leave() 22 | 23 | 24 | def find_parent_window(widget): 25 | while True: 26 | parent = widget.get_parent() 27 | if isinstance(parent, gtk.Window): 28 | return parent 29 | widget = parent 30 | 31 | 32 | if __name__ == "__main__": 33 | pass 34 | 35 | -------------------------------------------------------------------------------- /util/hildonize.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Open Issues 5 | @bug not all of a message is shown 6 | @bug Buttons are too small 7 | """ 8 | 9 | 10 | import gobject 11 | import gtk 12 | import dbus 13 | 14 | 15 | class _NullHildonModule(object): 16 | pass 17 | 18 | 19 | try: 20 | import hildon as _hildon 21 | hildon = _hildon # Dumb but gets around pyflakiness 22 | except (ImportError, OSError): 23 | hildon = _NullHildonModule 24 | 25 | 26 | IS_HILDON_SUPPORTED = hildon is not _NullHildonModule 27 | 28 | 29 | class _NullHildonProgram(object): 30 | 31 | def add_window(self, window): 32 | pass 33 | 34 | 35 | def _hildon_get_app_class(): 36 | return hildon.Program 37 | 38 | 39 | def _null_get_app_class(): 40 | return _NullHildonProgram 41 | 42 | 43 | try: 44 | hildon.Program 45 | get_app_class = _hildon_get_app_class 46 | except AttributeError: 47 | get_app_class = _null_get_app_class 48 | 49 | 50 | def _hildon_set_application_name(name): 51 | gtk.set_application_name(name) 52 | 53 | 54 | def _null_set_application_name(name): 55 | pass 56 | 57 | 58 | try: 59 | gtk.set_application_name 60 | set_application_name = _hildon_set_application_name 61 | except AttributeError: 62 | set_application_name = _null_set_application_name 63 | 64 | 65 | def _fremantle_hildonize_window(app, window): 66 | oldWindow = window 67 | newWindow = hildon.StackableWindow() 68 | if oldWindow.get_child() is not None: 69 | oldWindow.get_child().reparent(newWindow) 70 | app.add_window(newWindow) 71 | return newWindow 72 | 73 | 74 | def _hildon_hildonize_window(app, window): 75 | oldWindow = window 76 | newWindow = hildon.Window() 77 | if oldWindow.get_child() is not None: 78 | oldWindow.get_child().reparent(newWindow) 79 | app.add_window(newWindow) 80 | return newWindow 81 | 82 | 83 | def _null_hildonize_window(app, window): 84 | return window 85 | 86 | 87 | try: 88 | hildon.StackableWindow 89 | hildonize_window = _fremantle_hildonize_window 90 | except AttributeError: 91 | try: 92 | hildon.Window 93 | hildonize_window = _hildon_hildonize_window 94 | except AttributeError: 95 | hildonize_window = _null_hildonize_window 96 | 97 | 98 | def _fremantle_hildonize_menu(window, gtkMenu): 99 | appMenu = hildon.AppMenu() 100 | window.set_app_menu(appMenu) 101 | gtkMenu.get_parent().remove(gtkMenu) 102 | return appMenu 103 | 104 | 105 | def _hildon_hildonize_menu(window, gtkMenu): 106 | hildonMenu = gtk.Menu() 107 | for child in gtkMenu.get_children(): 108 | child.reparent(hildonMenu) 109 | window.set_menu(hildonMenu) 110 | gtkMenu.destroy() 111 | return hildonMenu 112 | 113 | 114 | def _null_hildonize_menu(window, gtkMenu): 115 | return gtkMenu 116 | 117 | 118 | try: 119 | hildon.AppMenu 120 | GTK_MENU_USED = False 121 | IS_FREMANTLE_SUPPORTED = True 122 | hildonize_menu = _fremantle_hildonize_menu 123 | except AttributeError: 124 | GTK_MENU_USED = True 125 | IS_FREMANTLE_SUPPORTED = False 126 | if IS_HILDON_SUPPORTED: 127 | hildonize_menu = _hildon_hildonize_menu 128 | else: 129 | hildonize_menu = _null_hildonize_menu 130 | 131 | 132 | def _hildon_set_button_auto_selectable(button): 133 | button.set_theme_size(hildon.HILDON_SIZE_AUTO_HEIGHT) 134 | 135 | 136 | def _null_set_button_auto_selectable(button): 137 | pass 138 | 139 | 140 | try: 141 | hildon.HILDON_SIZE_AUTO_HEIGHT 142 | gtk.Button.set_theme_size 143 | set_button_auto_selectable = _hildon_set_button_auto_selectable 144 | except AttributeError: 145 | set_button_auto_selectable = _null_set_button_auto_selectable 146 | 147 | 148 | def _hildon_set_button_finger_selectable(button): 149 | button.set_theme_size(hildon.HILDON_SIZE_FINGER_HEIGHT) 150 | 151 | 152 | def _null_set_button_finger_selectable(button): 153 | pass 154 | 155 | 156 | try: 157 | hildon.HILDON_SIZE_FINGER_HEIGHT 158 | gtk.Button.set_theme_size 159 | set_button_finger_selectable = _hildon_set_button_finger_selectable 160 | except AttributeError: 161 | set_button_finger_selectable = _null_set_button_finger_selectable 162 | 163 | 164 | def _hildon_set_button_thumb_selectable(button): 165 | button.set_theme_size(hildon.HILDON_SIZE_THUMB_HEIGHT) 166 | 167 | 168 | def _null_set_button_thumb_selectable(button): 169 | pass 170 | 171 | 172 | try: 173 | hildon.HILDON_SIZE_THUMB_HEIGHT 174 | gtk.Button.set_theme_size 175 | set_button_thumb_selectable = _hildon_set_button_thumb_selectable 176 | except AttributeError: 177 | set_button_thumb_selectable = _null_set_button_thumb_selectable 178 | 179 | 180 | def _hildon_set_cell_thumb_selectable(renderer): 181 | renderer.set_property("scale", 1.5) 182 | 183 | 184 | def _null_set_cell_thumb_selectable(renderer): 185 | pass 186 | 187 | 188 | if IS_HILDON_SUPPORTED: 189 | set_cell_thumb_selectable = _hildon_set_cell_thumb_selectable 190 | else: 191 | set_cell_thumb_selectable = _null_set_cell_thumb_selectable 192 | 193 | 194 | def _hildon_set_pix_cell_thumb_selectable(renderer): 195 | renderer.set_property("stock-size", 48) 196 | 197 | 198 | def _null_set_pix_cell_thumb_selectable(renderer): 199 | pass 200 | 201 | 202 | if IS_HILDON_SUPPORTED: 203 | set_pix_cell_thumb_selectable = _hildon_set_pix_cell_thumb_selectable 204 | else: 205 | set_pix_cell_thumb_selectable = _null_set_pix_cell_thumb_selectable 206 | 207 | 208 | def _fremantle_show_information_banner(parent, message): 209 | hildon.hildon_banner_show_information(parent, "", message) 210 | 211 | 212 | def _hildon_show_information_banner(parent, message): 213 | hildon.hildon_banner_show_information(parent, None, message) 214 | 215 | 216 | def _null_show_information_banner(parent, message): 217 | pass 218 | 219 | 220 | if IS_FREMANTLE_SUPPORTED: 221 | show_information_banner = _fremantle_show_information_banner 222 | else: 223 | try: 224 | hildon.hildon_banner_show_information 225 | show_information_banner = _hildon_show_information_banner 226 | except AttributeError: 227 | show_information_banner = _null_show_information_banner 228 | 229 | 230 | def _fremantle_show_busy_banner_start(parent, message): 231 | hildon.hildon_gtk_window_set_progress_indicator(parent, True) 232 | return parent 233 | 234 | 235 | def _fremantle_show_busy_banner_end(banner): 236 | hildon.hildon_gtk_window_set_progress_indicator(banner, False) 237 | 238 | 239 | def _hildon_show_busy_banner_start(parent, message): 240 | return hildon.hildon_banner_show_animation(parent, None, message) 241 | 242 | 243 | def _hildon_show_busy_banner_end(banner): 244 | banner.destroy() 245 | 246 | 247 | def _null_show_busy_banner_start(parent, message): 248 | return None 249 | 250 | 251 | def _null_show_busy_banner_end(banner): 252 | assert banner is None 253 | 254 | 255 | try: 256 | hildon.hildon_gtk_window_set_progress_indicator 257 | show_busy_banner_start = _fremantle_show_busy_banner_start 258 | show_busy_banner_end = _fremantle_show_busy_banner_end 259 | except AttributeError: 260 | try: 261 | hildon.hildon_banner_show_animation 262 | show_busy_banner_start = _hildon_show_busy_banner_start 263 | show_busy_banner_end = _hildon_show_busy_banner_end 264 | except AttributeError: 265 | show_busy_banner_start = _null_show_busy_banner_start 266 | show_busy_banner_end = _null_show_busy_banner_end 267 | 268 | 269 | def _hildon_hildonize_text_entry(textEntry): 270 | textEntry.set_property('hildon-input-mode', 7) 271 | 272 | 273 | def _null_hildonize_text_entry(textEntry): 274 | pass 275 | 276 | 277 | if IS_HILDON_SUPPORTED: 278 | hildonize_text_entry = _hildon_hildonize_text_entry 279 | else: 280 | hildonize_text_entry = _null_hildonize_text_entry 281 | 282 | 283 | def _hildon_window_to_portrait(window): 284 | # gtk documentation is unclear whether this does a "=" or a "|=" 285 | flags = hildon.PORTRAIT_MODE_SUPPORT | hildon.PORTRAIT_MODE_REQUEST 286 | hildon.hildon_gtk_window_set_portrait_flags(window, flags) 287 | 288 | 289 | def _hildon_window_to_landscape(window): 290 | # gtk documentation is unclear whether this does a "=" or a "&= ~" 291 | flags = hildon.PORTRAIT_MODE_SUPPORT 292 | hildon.hildon_gtk_window_set_portrait_flags(window, flags) 293 | 294 | 295 | def _null_window_to_portrait(window): 296 | pass 297 | 298 | 299 | def _null_window_to_landscape(window): 300 | pass 301 | 302 | 303 | try: 304 | hildon.PORTRAIT_MODE_SUPPORT 305 | hildon.PORTRAIT_MODE_REQUEST 306 | hildon.hildon_gtk_window_set_portrait_flags 307 | 308 | window_to_portrait = _hildon_window_to_portrait 309 | window_to_landscape = _hildon_window_to_landscape 310 | except AttributeError: 311 | window_to_portrait = _null_window_to_portrait 312 | window_to_landscape = _null_window_to_landscape 313 | 314 | 315 | def get_device_orientation(): 316 | bus = dbus.SystemBus() 317 | try: 318 | rawMceRequest = bus.get_object("com.nokia.mce", "/com/nokia/mce/request") 319 | mceRequest = dbus.Interface(rawMceRequest, dbus_interface="com.nokia.mce.request") 320 | orientation, standState, faceState, xAxis, yAxis, zAxis = mceRequest.get_device_orientation() 321 | except dbus.exception.DBusException: 322 | # catching for documentation purposes that when a system doesn't 323 | # support this, this is what to expect 324 | raise 325 | 326 | if orientation == "": 327 | return gtk.ORIENTATION_HORIZONTAL 328 | elif orientation == "": 329 | return gtk.ORIENTATION_VERTICAL 330 | else: 331 | raise RuntimeError("Unknown orientation: %s" % orientation) 332 | 333 | 334 | def _hildon_hildonize_password_entry(textEntry): 335 | textEntry.set_property('hildon-input-mode', 7 | (1 << 29)) 336 | 337 | 338 | def _null_hildonize_password_entry(textEntry): 339 | pass 340 | 341 | 342 | if IS_HILDON_SUPPORTED: 343 | hildonize_password_entry = _hildon_hildonize_password_entry 344 | else: 345 | hildonize_password_entry = _null_hildonize_password_entry 346 | 347 | 348 | def _hildon_hildonize_combo_entry(comboEntry): 349 | comboEntry.set_property('hildon-input-mode', 1 << 4) 350 | 351 | 352 | def _null_hildonize_combo_entry(textEntry): 353 | pass 354 | 355 | 356 | if IS_HILDON_SUPPORTED: 357 | hildonize_combo_entry = _hildon_hildonize_combo_entry 358 | else: 359 | hildonize_combo_entry = _null_hildonize_combo_entry 360 | 361 | 362 | def _null_create_seekbar(): 363 | adjustment = gtk.Adjustment(0, 0, 101, 1, 5, 1) 364 | seek = gtk.HScale(adjustment) 365 | seek.set_draw_value(False) 366 | return seek 367 | 368 | 369 | def _fremantle_create_seekbar(): 370 | seek = hildon.Seekbar() 371 | seek.set_range(0.0, 100) 372 | seek.set_draw_value(False) 373 | seek.set_update_policy(gtk.UPDATE_DISCONTINUOUS) 374 | return seek 375 | 376 | 377 | try: 378 | hildon.Seekbar 379 | create_seekbar = _fremantle_create_seekbar 380 | except AttributeError: 381 | create_seekbar = _null_create_seekbar 382 | 383 | 384 | def _fremantle_hildonize_scrollwindow(scrolledWindow): 385 | pannableWindow = hildon.PannableArea() 386 | 387 | child = scrolledWindow.get_child() 388 | scrolledWindow.remove(child) 389 | pannableWindow.add(child) 390 | 391 | parent = scrolledWindow.get_parent() 392 | if parent is not None: 393 | parent.remove(scrolledWindow) 394 | parent.add(pannableWindow) 395 | 396 | return pannableWindow 397 | 398 | 399 | def _hildon_hildonize_scrollwindow(scrolledWindow): 400 | hildon.hildon_helper_set_thumb_scrollbar(scrolledWindow, True) 401 | return scrolledWindow 402 | 403 | 404 | def _null_hildonize_scrollwindow(scrolledWindow): 405 | return scrolledWindow 406 | 407 | 408 | try: 409 | hildon.PannableArea 410 | hildonize_scrollwindow = _fremantle_hildonize_scrollwindow 411 | hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow 412 | except AttributeError: 413 | try: 414 | hildon.hildon_helper_set_thumb_scrollbar 415 | hildonize_scrollwindow = _hildon_hildonize_scrollwindow 416 | hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow 417 | except AttributeError: 418 | hildonize_scrollwindow = _null_hildonize_scrollwindow 419 | hildonize_scrollwindow_with_viewport = _null_hildonize_scrollwindow 420 | 421 | 422 | def _hildon_request_number(parent, title, range, default): 423 | spinner = hildon.NumberEditor(*range) 424 | spinner.set_value(default) 425 | 426 | dialog = gtk.Dialog( 427 | title, 428 | parent, 429 | gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, 430 | (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), 431 | ) 432 | dialog.set_default_response(gtk.RESPONSE_CANCEL) 433 | dialog.get_child().add(spinner) 434 | 435 | try: 436 | dialog.show_all() 437 | response = dialog.run() 438 | 439 | if response == gtk.RESPONSE_OK: 440 | return spinner.get_value() 441 | elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: 442 | raise RuntimeError("User cancelled request") 443 | else: 444 | raise RuntimeError("Unrecognized response %r", response) 445 | finally: 446 | dialog.hide() 447 | dialog.destroy() 448 | 449 | 450 | def _null_request_number(parent, title, range, default): 451 | adjustment = gtk.Adjustment(default, range[0], range[1], 1, 5, 0) 452 | spinner = gtk.SpinButton(adjustment, 0, 0) 453 | spinner.set_wrap(False) 454 | 455 | dialog = gtk.Dialog( 456 | title, 457 | parent, 458 | gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, 459 | (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), 460 | ) 461 | dialog.set_default_response(gtk.RESPONSE_CANCEL) 462 | dialog.get_child().add(spinner) 463 | 464 | try: 465 | dialog.show_all() 466 | response = dialog.run() 467 | 468 | if response == gtk.RESPONSE_OK: 469 | return spinner.get_value_as_int() 470 | elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: 471 | raise RuntimeError("User cancelled request") 472 | else: 473 | raise RuntimeError("Unrecognized response %r", response) 474 | finally: 475 | dialog.hide() 476 | dialog.destroy() 477 | 478 | 479 | try: 480 | hildon.NumberEditor # TODO deprecated in fremantle 481 | request_number = _hildon_request_number 482 | except AttributeError: 483 | request_number = _null_request_number 484 | 485 | 486 | def _hildon_touch_selector(parent, title, items, defaultIndex): 487 | model = gtk.ListStore(gobject.TYPE_STRING) 488 | for item in items: 489 | model.append((item, )) 490 | 491 | selector = hildon.TouchSelector() 492 | selector.append_text_column(model, True) 493 | selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE) 494 | selector.set_active(0, defaultIndex) 495 | 496 | dialog = hildon.PickerDialog(parent) 497 | dialog.set_selector(selector) 498 | 499 | try: 500 | dialog.show_all() 501 | response = dialog.run() 502 | 503 | if response == gtk.RESPONSE_OK: 504 | return selector.get_active(0) 505 | elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: 506 | raise RuntimeError("User cancelled request") 507 | else: 508 | raise RuntimeError("Unrecognized response %r", response) 509 | finally: 510 | dialog.hide() 511 | dialog.destroy() 512 | 513 | 514 | def _on_null_touch_selector_activated(treeView, path, column, dialog, pathData): 515 | dialog.response(gtk.RESPONSE_OK) 516 | pathData[0] = path 517 | 518 | 519 | def _null_touch_selector(parent, title, items, defaultIndex = -1): 520 | parentSize = parent.get_size() 521 | 522 | model = gtk.ListStore(gobject.TYPE_STRING) 523 | for item in items: 524 | model.append((item, )) 525 | 526 | cell = gtk.CellRendererText() 527 | set_cell_thumb_selectable(cell) 528 | column = gtk.TreeViewColumn(title) 529 | column.pack_start(cell, expand=True) 530 | column.add_attribute(cell, "text", 0) 531 | 532 | treeView = gtk.TreeView() 533 | treeView.set_model(model) 534 | treeView.append_column(column) 535 | selection = treeView.get_selection() 536 | selection.set_mode(gtk.SELECTION_SINGLE) 537 | if 0 < defaultIndex: 538 | selection.select_path((defaultIndex, )) 539 | 540 | scrolledWin = gtk.ScrolledWindow() 541 | scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) 542 | scrolledWin.add(treeView) 543 | 544 | dialog = gtk.Dialog( 545 | title, 546 | parent, 547 | gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, 548 | (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), 549 | ) 550 | dialog.set_default_response(gtk.RESPONSE_CANCEL) 551 | dialog.get_child().add(scrolledWin) 552 | dialog.resize(parentSize[0], max(parentSize[1]-100, 100)) 553 | 554 | scrolledWin = hildonize_scrollwindow(scrolledWin) 555 | pathData = [None] 556 | treeView.connect("row-activated", _on_null_touch_selector_activated, dialog, pathData) 557 | 558 | try: 559 | dialog.show_all() 560 | response = dialog.run() 561 | 562 | if response == gtk.RESPONSE_OK: 563 | if pathData[0] is None: 564 | raise RuntimeError("No selection made") 565 | return pathData[0][0] 566 | elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: 567 | raise RuntimeError("User cancelled request") 568 | else: 569 | raise RuntimeError("Unrecognized response %r", response) 570 | finally: 571 | dialog.hide() 572 | dialog.destroy() 573 | 574 | 575 | try: 576 | hildon.PickerDialog 577 | hildon.TouchSelector 578 | touch_selector = _hildon_touch_selector 579 | except AttributeError: 580 | touch_selector = _null_touch_selector 581 | 582 | 583 | def _hildon_touch_selector_entry(parent, title, items, defaultItem): 584 | # Got a segfault when using append_text_column with TouchSelectorEntry, so using this way 585 | try: 586 | selector = hildon.TouchSelectorEntry(text=True) 587 | except TypeError: 588 | selector = hildon.hildon_touch_selector_entry_new_text() 589 | defaultIndex = -1 590 | for i, item in enumerate(items): 591 | selector.append_text(item) 592 | if item == defaultItem: 593 | defaultIndex = i 594 | 595 | dialog = hildon.PickerDialog(parent) 596 | dialog.set_selector(selector) 597 | 598 | if 0 < defaultIndex: 599 | selector.set_active(0, defaultIndex) 600 | else: 601 | selector.get_entry().set_text(defaultItem) 602 | 603 | try: 604 | dialog.show_all() 605 | response = dialog.run() 606 | finally: 607 | dialog.hide() 608 | 609 | if response == gtk.RESPONSE_OK: 610 | return selector.get_entry().get_text() 611 | elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: 612 | raise RuntimeError("User cancelled request") 613 | else: 614 | raise RuntimeError("Unrecognized response %r", response) 615 | 616 | 617 | def _on_null_touch_selector_entry_entry_changed(entry, result, selection, defaultIndex): 618 | custom = entry.get_text().strip() 619 | if custom: 620 | result[0] = custom 621 | selection.unselect_all() 622 | else: 623 | result[0] = None 624 | selection.select_path((defaultIndex, )) 625 | 626 | 627 | def _on_null_touch_selector_entry_entry_activated(customEntry, dialog, result): 628 | dialog.response(gtk.RESPONSE_OK) 629 | result[0] = customEntry.get_text() 630 | 631 | 632 | def _on_null_touch_selector_entry_tree_activated(treeView, path, column, dialog, result): 633 | dialog.response(gtk.RESPONSE_OK) 634 | model = treeView.get_model() 635 | itr = model.get_iter(path) 636 | if itr is not None: 637 | result[0] = model.get_value(itr, 0) 638 | 639 | 640 | def _null_touch_selector_entry(parent, title, items, defaultItem): 641 | parentSize = parent.get_size() 642 | 643 | model = gtk.ListStore(gobject.TYPE_STRING) 644 | defaultIndex = -1 645 | for i, item in enumerate(items): 646 | model.append((item, )) 647 | if item == defaultItem: 648 | defaultIndex = i 649 | 650 | cell = gtk.CellRendererText() 651 | set_cell_thumb_selectable(cell) 652 | column = gtk.TreeViewColumn(title) 653 | column.pack_start(cell, expand=True) 654 | column.add_attribute(cell, "text", 0) 655 | 656 | treeView = gtk.TreeView() 657 | treeView.set_model(model) 658 | treeView.append_column(column) 659 | selection = treeView.get_selection() 660 | selection.set_mode(gtk.SELECTION_SINGLE) 661 | 662 | scrolledWin = gtk.ScrolledWindow() 663 | scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) 664 | scrolledWin.add(treeView) 665 | 666 | customEntry = gtk.Entry() 667 | 668 | layout = gtk.VBox() 669 | layout.pack_start(customEntry, expand=False) 670 | layout.pack_start(scrolledWin) 671 | 672 | dialog = gtk.Dialog( 673 | title, 674 | parent, 675 | gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, 676 | (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), 677 | ) 678 | dialog.set_default_response(gtk.RESPONSE_CANCEL) 679 | dialog.get_child().add(layout) 680 | dialog.resize(parentSize[0], max(parentSize[1]-100, 100)) 681 | 682 | scrolledWin = hildonize_scrollwindow(scrolledWin) 683 | 684 | result = [None] 685 | if 0 < defaultIndex: 686 | selection.select_path((defaultIndex, )) 687 | result[0] = defaultItem 688 | else: 689 | customEntry.set_text(defaultItem) 690 | 691 | customEntry.connect("activate", _on_null_touch_selector_entry_entry_activated, dialog, result) 692 | customEntry.connect("changed", _on_null_touch_selector_entry_entry_changed, result, selection, defaultIndex) 693 | treeView.connect("row-activated", _on_null_touch_selector_entry_tree_activated, dialog, result) 694 | 695 | try: 696 | dialog.show_all() 697 | response = dialog.run() 698 | 699 | if response == gtk.RESPONSE_OK: 700 | _, itr = selection.get_selected() 701 | if itr is not None: 702 | return model.get_value(itr, 0) 703 | else: 704 | enteredText = customEntry.get_text().strip() 705 | if enteredText: 706 | return enteredText 707 | elif result[0] is not None: 708 | return result[0] 709 | else: 710 | raise RuntimeError("No selection made") 711 | elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: 712 | raise RuntimeError("User cancelled request") 713 | else: 714 | raise RuntimeError("Unrecognized response %r", response) 715 | finally: 716 | dialog.hide() 717 | dialog.destroy() 718 | 719 | 720 | try: 721 | hildon.PickerDialog 722 | hildon.TouchSelectorEntry 723 | touch_selector_entry = _hildon_touch_selector_entry 724 | except AttributeError: 725 | touch_selector_entry = _null_touch_selector_entry 726 | 727 | 728 | if __name__ == "__main__": 729 | app = get_app_class()() 730 | 731 | label = gtk.Label("Hello World from a Label!") 732 | 733 | win = gtk.Window() 734 | win.add(label) 735 | win = hildonize_window(app, win) 736 | if False and IS_FREMANTLE_SUPPORTED: 737 | appMenu = hildon.AppMenu() 738 | for i in xrange(5): 739 | b = gtk.Button(str(i)) 740 | appMenu.append(b) 741 | win.set_app_menu(appMenu) 742 | win.show_all() 743 | appMenu.show_all() 744 | gtk.main() 745 | elif False: 746 | print touch_selector(win, "Test", ["A", "B", "C", "D"], 2) 747 | elif False: 748 | print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "C") 749 | print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "Blah") 750 | elif False: 751 | import pprint 752 | name, value = "", "" 753 | goodLocals = [ 754 | (name, value) for (name, value) in locals().iteritems() 755 | if not name.startswith("_") 756 | ] 757 | pprint.pprint(goodLocals) 758 | elif False: 759 | import time 760 | show_information_banner(win, "Hello World") 761 | time.sleep(5) 762 | elif False: 763 | import time 764 | banner = show_busy_banner_start(win, "Hello World") 765 | time.sleep(5) 766 | show_busy_banner_end(banner) 767 | -------------------------------------------------------------------------------- /util/io.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | 4 | from __future__ import with_statement 5 | 6 | import os 7 | import pickle 8 | import contextlib 9 | import itertools 10 | import codecs 11 | import logging 12 | from xml.sax import saxutils 13 | import csv 14 | try: 15 | import cStringIO as StringIO 16 | except ImportError: 17 | import StringIO 18 | 19 | 20 | @contextlib.contextmanager 21 | def change_directory(directory): 22 | previousDirectory = os.getcwd() 23 | os.chdir(directory) 24 | currentDirectory = os.getcwd() 25 | 26 | try: 27 | yield previousDirectory, currentDirectory 28 | finally: 29 | os.chdir(previousDirectory) 30 | 31 | 32 | @contextlib.contextmanager 33 | def pickled(filename): 34 | """ 35 | Here is an example usage: 36 | with pickled("foo.db") as p: 37 | p("users", list).append(["srid", "passwd", 23]) 38 | """ 39 | 40 | if os.path.isfile(filename): 41 | data = pickle.load(open(filename)) 42 | else: 43 | data = {} 44 | 45 | def getter(item, factory): 46 | if item in data: 47 | return data[item] 48 | else: 49 | data[item] = factory() 50 | return data[item] 51 | 52 | yield getter 53 | 54 | pickle.dump(data, open(filename, "w")) 55 | 56 | 57 | @contextlib.contextmanager 58 | def redirect(object_, attr, value): 59 | """ 60 | >>> import sys 61 | ... with redirect(sys, 'stdout', open('stdout', 'w')): 62 | ... print "hello" 63 | ... 64 | >>> print "we're back" 65 | we're back 66 | """ 67 | orig = getattr(object_, attr) 68 | setattr(object_, attr, value) 69 | try: 70 | yield 71 | finally: 72 | setattr(object_, attr, orig) 73 | 74 | 75 | def pathsplit(path): 76 | """ 77 | >>> pathsplit("/a/b/c") 78 | ['', 'a', 'b', 'c'] 79 | >>> pathsplit("./plugins/builtins.ini") 80 | ['.', 'plugins', 'builtins.ini'] 81 | """ 82 | pathParts = path.split(os.path.sep) 83 | return pathParts 84 | 85 | 86 | def commonpath(l1, l2, common=None): 87 | """ 88 | >>> commonpath(pathsplit('/a/b/c/d'), pathsplit('/a/b/c1/d1')) 89 | (['', 'a', 'b'], ['c', 'd'], ['c1', 'd1']) 90 | >>> commonpath(pathsplit("./plugins/"), pathsplit("./plugins/builtins.ini")) 91 | (['.', 'plugins'], [''], ['builtins.ini']) 92 | >>> commonpath(pathsplit("./plugins/builtins"), pathsplit("./plugins")) 93 | (['.', 'plugins'], ['builtins'], []) 94 | """ 95 | if common is None: 96 | common = [] 97 | 98 | if l1 == l2: 99 | return l1, [], [] 100 | 101 | for i, (leftDir, rightDir) in enumerate(zip(l1, l2)): 102 | if leftDir != rightDir: 103 | return l1[0:i], l1[i:], l2[i:] 104 | else: 105 | if leftDir == rightDir: 106 | i += 1 107 | return l1[0:i], l1[i:], l2[i:] 108 | 109 | 110 | def relpath(p1, p2): 111 | """ 112 | >>> relpath('/', '/') 113 | './' 114 | >>> relpath('/a/b/c/d', '/') 115 | '../../../../' 116 | >>> relpath('/a/b/c/d', '/a/b/c1/d1') 117 | '../../c1/d1' 118 | >>> relpath('/a/b/c/d', '/a/b/c1/d1/') 119 | '../../c1/d1' 120 | >>> relpath("./plugins/builtins", "./plugins") 121 | '../' 122 | >>> relpath("./plugins/", "./plugins/builtins.ini") 123 | 'builtins.ini' 124 | """ 125 | sourcePath = os.path.normpath(p1) 126 | destPath = os.path.normpath(p2) 127 | 128 | (common, sourceOnly, destOnly) = commonpath(pathsplit(sourcePath), pathsplit(destPath)) 129 | if len(sourceOnly) or len(destOnly): 130 | relParts = itertools.chain( 131 | (('..' + os.sep) * len(sourceOnly), ), 132 | destOnly, 133 | ) 134 | return os.path.join(*relParts) 135 | else: 136 | return "."+os.sep 137 | 138 | 139 | class UTF8Recoder(object): 140 | """ 141 | Iterator that reads an encoded stream and reencodes the input to UTF-8 142 | """ 143 | def __init__(self, f, encoding): 144 | self.reader = codecs.getreader(encoding)(f) 145 | 146 | def __iter__(self): 147 | return self 148 | 149 | def next(self): 150 | return self.reader.next().encode("utf-8") 151 | 152 | 153 | class UnicodeReader(object): 154 | """ 155 | A CSV reader which will iterate over lines in the CSV file "f", 156 | which is encoded in the given encoding. 157 | """ 158 | 159 | def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds): 160 | f = UTF8Recoder(f, encoding) 161 | self.reader = csv.reader(f, dialect=dialect, **kwds) 162 | 163 | def next(self): 164 | row = self.reader.next() 165 | return [unicode(s, "utf-8") for s in row] 166 | 167 | def __iter__(self): 168 | return self 169 | 170 | class UnicodeWriter(object): 171 | """ 172 | A CSV writer which will write rows to CSV file "f", 173 | which is encoded in the given encoding. 174 | """ 175 | 176 | def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds): 177 | # Redirect output to a queue 178 | self.queue = StringIO.StringIO() 179 | self.writer = csv.writer(self.queue, dialect=dialect, **kwds) 180 | self.stream = f 181 | self.encoder = codecs.getincrementalencoder(encoding)() 182 | 183 | def writerow(self, row): 184 | self.writer.writerow([s.encode("utf-8") for s in row]) 185 | # Fetch UTF-8 output from the queue ... 186 | data = self.queue.getvalue() 187 | data = data.decode("utf-8") 188 | # ... and reencode it into the target encoding 189 | data = self.encoder.encode(data) 190 | # write to the target stream 191 | self.stream.write(data) 192 | # empty queue 193 | self.queue.truncate(0) 194 | 195 | def writerows(self, rows): 196 | for row in rows: 197 | self.writerow(row) 198 | 199 | 200 | def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, **kwargs): 201 | # csv.py doesn't do Unicode; encode temporarily as UTF-8: 202 | csv_reader = csv.reader(utf_8_encoder(unicode_csv_data), 203 | dialect=dialect, **kwargs) 204 | for row in csv_reader: 205 | # decode UTF-8 back to Unicode, cell by cell: 206 | yield [unicode(cell, 'utf-8') for cell in row] 207 | 208 | 209 | def utf_8_encoder(unicode_csv_data): 210 | for line in unicode_csv_data: 211 | yield line.encode('utf-8') 212 | 213 | 214 | _UNESCAPE_ENTITIES = { 215 | """: '"', 216 | " ": " ", 217 | "'": "'", 218 | } 219 | 220 | 221 | _ESCAPE_ENTITIES = dict((v, k) for (v, k) in zip(_UNESCAPE_ENTITIES.itervalues(), _UNESCAPE_ENTITIES.iterkeys())) 222 | del _ESCAPE_ENTITIES[" "] 223 | 224 | 225 | def unescape(text): 226 | plain = saxutils.unescape(text, _UNESCAPE_ENTITIES) 227 | return plain 228 | 229 | 230 | def escape(text): 231 | fancy = saxutils.escape(text, _ESCAPE_ENTITIES) 232 | return fancy 233 | 234 | 235 | class ErrorLogHandler(logging.Handler): 236 | 237 | def __init__(self, errorLog, level = logging.NOTSET): 238 | logging.Handler.__init__(self, level = level) 239 | self._errorLog = errorLog 240 | 241 | def emit(self, record): 242 | try: 243 | # We don't want to write a custom formatter just to avoid tracebacks 244 | exc, exc_text = record.exc_info, record.exc_text 245 | record.exc_info, record.exc_text = None, None 246 | msg = self.format(record) 247 | record.exc_info, record.exc_text = exc, exc_text 248 | 249 | self._errorLog.push_message(msg, record.levelno) 250 | except (KeyboardInterrupt, SystemExit): 251 | raise 252 | except: 253 | self.handleError(record) 254 | 255 | def createLock(self): 256 | self.lock = None 257 | -------------------------------------------------------------------------------- /util/linux.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | 4 | import os 5 | import logging 6 | 7 | try: 8 | from xdg import BaseDirectory as _BaseDirectory 9 | BaseDirectory = _BaseDirectory 10 | except ImportError: 11 | BaseDirectory = None 12 | 13 | 14 | _moduleLogger = logging.getLogger(__name__) 15 | 16 | 17 | _libc = None 18 | 19 | 20 | def set_process_name(name): 21 | try: # change process name for killall 22 | global _libc 23 | if _libc is None: 24 | import ctypes 25 | _libc = ctypes.CDLL('libc.so.6') 26 | _libc.prctl(15, name, 0, 0, 0) 27 | except Exception, e: 28 | _moduleLogger.warning('Unable to set processName: %s" % e') 29 | 30 | 31 | def _get_xdg_path(resourceType): 32 | if BaseDirectory is not None: 33 | if resourceType == "data": 34 | base = BaseDirectory.xdg_data_home 35 | if base == "/usr/share/mime": 36 | # Ugly hack because somehow Maemo 4.1 seems to be set to this 37 | base = None 38 | elif resourceType == "config": 39 | base = BaseDirectory.xdg_config_home 40 | elif resourceType == "cache": 41 | base = BaseDirectory.xdg_cache_home 42 | else: 43 | raise RuntimeError("Unknown type: "+resourceType) 44 | else: 45 | base = None 46 | 47 | return base 48 | 49 | 50 | def get_resource_path(resourceType, resource, name = None): 51 | base = _get_xdg_path(resourceType) 52 | if base is not None: 53 | dirPath = os.path.join(base, resource) 54 | else: 55 | base = os.path.join(os.path.expanduser("~"), ".%s" % resource) 56 | dirPath = base 57 | if name is not None: 58 | dirPath = os.path.join(dirPath, name) 59 | return dirPath 60 | 61 | 62 | def get_new_resource(resourceType, resource, name): 63 | dirPath = get_resource_path(resourceType, resource) 64 | filePath = os.path.join(dirPath, name) 65 | if not os.path.exists(dirPath): 66 | # Looking before I leap to not mask errors 67 | os.makedirs(dirPath) 68 | 69 | return filePath 70 | 71 | 72 | def get_existing_resource(resourceType, resource, name): 73 | base = _get_xdg_path(resourceType) 74 | 75 | if base is not None: 76 | finalPath = os.path.join(base, name) 77 | if os.path.exists(finalPath): 78 | return finalPath 79 | 80 | altBase = os.path.join(os.path.expanduser("~"), ".%s" % resource) 81 | finalPath = os.path.join(altBase, name) 82 | if os.path.exists(finalPath): 83 | return finalPath 84 | else: 85 | raise RuntimeError("Resource not found: %r" % ((resourceType, resource, name), )) 86 | -------------------------------------------------------------------------------- /util/misc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import with_statement 4 | 5 | import sys 6 | import re 7 | import cPickle 8 | 9 | import functools 10 | import contextlib 11 | import inspect 12 | 13 | import optparse 14 | import traceback 15 | import warnings 16 | import string 17 | 18 | 19 | class AnyData(object): 20 | 21 | pass 22 | 23 | 24 | _indentationLevel = [0] 25 | 26 | 27 | def log_call(logger): 28 | 29 | def log_call_decorator(func): 30 | 31 | @functools.wraps(func) 32 | def wrapper(*args, **kwds): 33 | logger.debug("%s> %s" % (" " * _indentationLevel[0], func.__name__, )) 34 | _indentationLevel[0] += 1 35 | try: 36 | return func(*args, **kwds) 37 | finally: 38 | _indentationLevel[0] -= 1 39 | logger.debug("%s< %s" % (" " * _indentationLevel[0], func.__name__, )) 40 | 41 | return wrapper 42 | 43 | return log_call_decorator 44 | 45 | 46 | def log_exception(logger): 47 | 48 | def log_exception_decorator(func): 49 | 50 | @functools.wraps(func) 51 | def wrapper(*args, **kwds): 52 | try: 53 | return func(*args, **kwds) 54 | except Exception as e: 55 | logger.exception("Internal Error: %s" % e) 56 | raise 57 | 58 | return wrapper 59 | 60 | return log_exception_decorator 61 | 62 | 63 | def printfmt(template): 64 | """ 65 | This hides having to create the Template object and call substitute/safe_substitute on it. For example: 66 | 67 | >>> num = 10 68 | >>> word = "spam" 69 | >>> printfmt("I would like to order $num units of $word, please") #doctest: +SKIP 70 | I would like to order 10 units of spam, please 71 | """ 72 | frame = inspect.stack()[-1][0] 73 | try: 74 | print string.Template(template).safe_substitute(frame.f_locals) 75 | finally: 76 | del frame 77 | 78 | 79 | def is_special(name): 80 | return name.startswith("__") and name.endswith("__") 81 | 82 | 83 | def is_private(name): 84 | return name.startswith("_") and not is_special(name) 85 | 86 | 87 | def privatize(clsName, attributeName): 88 | """ 89 | At runtime, make an attributeName private 90 | 91 | Example: 92 | >>> class Test(object): 93 | ... pass 94 | ... 95 | >>> try: 96 | ... dir(Test).index("_Test__me") 97 | ... print dir(Test) 98 | ... except: 99 | ... print "Not Found" 100 | Not Found 101 | >>> setattr(Test, privatize(Test.__name__, "me"), "Hello World") 102 | >>> try: 103 | ... dir(Test).index("_Test__me") 104 | ... print "Found" 105 | ... except: 106 | ... print dir(Test) 107 | 0 108 | Found 109 | >>> print getattr(Test, obfuscate(Test.__name__, "__me")) 110 | Hello World 111 | >>> 112 | >>> is_private(privatize(Test.__name__, "me")) 113 | True 114 | >>> is_special(privatize(Test.__name__, "me")) 115 | False 116 | """ 117 | return "".join(["_", clsName, "__", attributeName]) 118 | 119 | 120 | def obfuscate(clsName, attributeName): 121 | """ 122 | At runtime, turn a private name into the obfuscated form 123 | 124 | Example: 125 | >>> class Test(object): 126 | ... __me = "Hello World" 127 | ... 128 | >>> try: 129 | ... dir(Test).index("_Test__me") 130 | ... print "Found" 131 | ... except: 132 | ... print dir(Test) 133 | 0 134 | Found 135 | >>> print getattr(Test, obfuscate(Test.__name__, "__me")) 136 | Hello World 137 | >>> is_private(obfuscate(Test.__name__, "__me")) 138 | True 139 | >>> is_special(obfuscate(Test.__name__, "__me")) 140 | False 141 | """ 142 | return "".join(["_", clsName, attributeName]) 143 | 144 | 145 | class PAOptionParser(optparse.OptionParser, object): 146 | """ 147 | >>> if __name__ == '__main__': 148 | ... #parser = PAOptionParser("My usage str") 149 | ... parser = PAOptionParser() 150 | ... parser.add_posarg("Foo", help="Foo usage") 151 | ... parser.add_posarg("Bar", dest="bar_dest") 152 | ... parser.add_posarg("Language", dest='tr_type', type="choice", choices=("Python", "Other")) 153 | ... parser.add_option('--stocksym', dest='symbol') 154 | ... values, args = parser.parse_args() 155 | ... print values, args 156 | ... 157 | 158 | python mycp.py -h 159 | python mycp.py 160 | python mycp.py foo 161 | python mycp.py foo bar 162 | 163 | python mycp.py foo bar lava 164 | Usage: pa.py [options] 165 | 166 | Positional Arguments: 167 | Foo: Foo usage 168 | Bar: 169 | Language: 170 | 171 | pa.py: error: option --Language: invalid choice: 'lava' (choose from 'Python', 'Other' 172 | """ 173 | 174 | def __init__(self, *args, **kw): 175 | self.posargs = [] 176 | super(PAOptionParser, self).__init__(*args, **kw) 177 | 178 | def add_posarg(self, *args, **kw): 179 | pa_help = kw.get("help", "") 180 | kw["help"] = optparse.SUPPRESS_HELP 181 | o = self.add_option("--%s" % args[0], *args[1:], **kw) 182 | self.posargs.append((args[0], pa_help)) 183 | 184 | def get_usage(self, *args, **kwargs): 185 | params = (' '.join(["<%s>" % arg[0] for arg in self.posargs]), '\n '.join(["%s: %s" % (arg) for arg in self.posargs])) 186 | self.usage = "%%prog %s [options]\n\nPositional Arguments:\n %s" % params 187 | return super(PAOptionParser, self).get_usage(*args, **kwargs) 188 | 189 | def parse_args(self, *args, **kwargs): 190 | args = sys.argv[1:] 191 | args0 = [] 192 | for p, v in zip(self.posargs, args): 193 | args0.append("--%s" % p[0]) 194 | args0.append(v) 195 | args = args0 + args 196 | options, args = super(PAOptionParser, self).parse_args(args, **kwargs) 197 | if len(args) < len(self.posargs): 198 | msg = 'Missing value(s) for "%s"\n' % ", ".join([arg[0] for arg in self.posargs][len(args):]) 199 | self.error(msg) 200 | return options, args 201 | 202 | 203 | def explicitly(name, stackadd=0): 204 | """ 205 | This is an alias for adding to '__all__'. Less error-prone than using 206 | __all__ itself, since setting __all__ directly is prone to stomping on 207 | things implicitly exported via L{alias}. 208 | 209 | @note Taken from PyExport (which could turn out pretty cool): 210 | @li @a http://codebrowse.launchpad.net/~glyph/ 211 | @li @a http://glyf.livejournal.com/74356.html 212 | """ 213 | packageVars = sys._getframe(1+stackadd).f_locals 214 | globalAll = packageVars.setdefault('__all__', []) 215 | globalAll.append(name) 216 | 217 | 218 | def public(thunk): 219 | """ 220 | This is a decorator, for convenience. Rather than typing the name of your 221 | function twice, you can decorate a function with this. 222 | 223 | To be real, @public would need to work on methods as well, which gets into 224 | supporting types... 225 | 226 | @note Taken from PyExport (which could turn out pretty cool): 227 | @li @a http://codebrowse.launchpad.net/~glyph/ 228 | @li @a http://glyf.livejournal.com/74356.html 229 | """ 230 | explicitly(thunk.__name__, 1) 231 | return thunk 232 | 233 | 234 | def _append_docstring(obj, message): 235 | if obj.__doc__ is None: 236 | obj.__doc__ = message 237 | else: 238 | obj.__doc__ += message 239 | 240 | 241 | def validate_decorator(decorator): 242 | 243 | def simple(x): 244 | return x 245 | 246 | f = simple 247 | f.__name__ = "name" 248 | f.__doc__ = "doc" 249 | f.__dict__["member"] = True 250 | 251 | g = decorator(f) 252 | 253 | if f.__name__ != g.__name__: 254 | print f.__name__, "!=", g.__name__ 255 | 256 | if g.__doc__ is None: 257 | print decorator.__name__, "has no doc string" 258 | elif not g.__doc__.startswith(f.__doc__): 259 | print g.__doc__, "didn't start with", f.__doc__ 260 | 261 | if not ("member" in g.__dict__ and g.__dict__["member"]): 262 | print "'member' not in ", g.__dict__ 263 | 264 | 265 | def deprecated_api(func): 266 | """ 267 | This is a decorator which can be used to mark functions 268 | as deprecated. It will result in a warning being emitted 269 | when the function is used. 270 | 271 | >>> validate_decorator(deprecated_api) 272 | """ 273 | 274 | @functools.wraps(func) 275 | def newFunc(*args, **kwargs): 276 | warnings.warn("Call to deprecated function %s." % func.__name__, category=DeprecationWarning) 277 | return func(*args, **kwargs) 278 | 279 | _append_docstring(newFunc, "\n@deprecated") 280 | return newFunc 281 | 282 | 283 | def unstable_api(func): 284 | """ 285 | This is a decorator which can be used to mark functions 286 | as deprecated. It will result in a warning being emitted 287 | when the function is used. 288 | 289 | >>> validate_decorator(unstable_api) 290 | """ 291 | 292 | @functools.wraps(func) 293 | def newFunc(*args, **kwargs): 294 | warnings.warn("Call to unstable API function %s." % func.__name__, category=FutureWarning) 295 | return func(*args, **kwargs) 296 | _append_docstring(newFunc, "\n@unstable") 297 | return newFunc 298 | 299 | 300 | def enabled(func): 301 | """ 302 | This decorator doesn't add any behavior 303 | 304 | >>> validate_decorator(enabled) 305 | """ 306 | return func 307 | 308 | 309 | def disabled(func): 310 | """ 311 | This decorator disables the provided function, and does nothing 312 | 313 | >>> validate_decorator(disabled) 314 | """ 315 | 316 | @functools.wraps(func) 317 | def emptyFunc(*args, **kargs): 318 | pass 319 | _append_docstring(emptyFunc, "\n@note Temporarily Disabled") 320 | return emptyFunc 321 | 322 | 323 | def metadata(document=True, **kwds): 324 | """ 325 | >>> validate_decorator(metadata(author="Ed")) 326 | """ 327 | 328 | def decorate(func): 329 | for k, v in kwds.iteritems(): 330 | setattr(func, k, v) 331 | if document: 332 | _append_docstring(func, "\n@"+k+" "+v) 333 | return func 334 | return decorate 335 | 336 | 337 | def prop(func): 338 | """Function decorator for defining property attributes 339 | 340 | The decorated function is expected to return a dictionary 341 | containing one or more of the following pairs: 342 | fget - function for getting attribute value 343 | fset - function for setting attribute value 344 | fdel - function for deleting attribute 345 | This can be conveniently constructed by the locals() builtin 346 | function; see: 347 | http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/205183 348 | @author http://kbyanc.blogspot.com/2007/06/python-property-attribute-tricks.html 349 | 350 | Example: 351 | >>> #Due to transformation from function to property, does not need to be validated 352 | >>> #validate_decorator(prop) 353 | >>> class MyExampleClass(object): 354 | ... @prop 355 | ... def foo(): 356 | ... "The foo property attribute's doc-string" 357 | ... def fget(self): 358 | ... print "GET" 359 | ... return self._foo 360 | ... def fset(self, value): 361 | ... print "SET" 362 | ... self._foo = value 363 | ... return locals() 364 | ... 365 | >>> me = MyExampleClass() 366 | >>> me.foo = 10 367 | SET 368 | >>> print me.foo 369 | GET 370 | 10 371 | """ 372 | return property(doc=func.__doc__, **func()) 373 | 374 | 375 | def print_handler(e): 376 | """ 377 | @see ExpHandler 378 | """ 379 | print "%s: %s" % (type(e).__name__, e) 380 | 381 | 382 | def print_ignore(e): 383 | """ 384 | @see ExpHandler 385 | """ 386 | print 'Ignoring %s exception: %s' % (type(e).__name__, e) 387 | 388 | 389 | def print_traceback(e): 390 | """ 391 | @see ExpHandler 392 | """ 393 | #print sys.exc_info() 394 | traceback.print_exc(file=sys.stdout) 395 | 396 | 397 | def ExpHandler(handler = print_handler, *exceptions): 398 | """ 399 | An exception handling idiom using decorators 400 | Examples 401 | Specify exceptions in order, first one is handled first 402 | last one last. 403 | 404 | >>> validate_decorator(ExpHandler()) 405 | >>> @ExpHandler(print_ignore, ZeroDivisionError) 406 | ... @ExpHandler(None, AttributeError, ValueError) 407 | ... def f1(): 408 | ... 1/0 409 | >>> @ExpHandler(print_traceback, ZeroDivisionError) 410 | ... def f2(): 411 | ... 1/0 412 | >>> @ExpHandler() 413 | ... def f3(*pargs): 414 | ... l = pargs 415 | ... return l[10] 416 | >>> @ExpHandler(print_traceback, ZeroDivisionError) 417 | ... def f4(): 418 | ... return 1 419 | >>> 420 | >>> 421 | >>> f1() 422 | Ignoring ZeroDivisionError exception: integer division or modulo by zero 423 | >>> f2() # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE 424 | Traceback (most recent call last): 425 | ... 426 | ZeroDivisionError: integer division or modulo by zero 427 | >>> f3() 428 | IndexError: tuple index out of range 429 | >>> f4() 430 | 1 431 | """ 432 | 433 | def wrapper(f): 434 | localExceptions = exceptions 435 | if not localExceptions: 436 | localExceptions = [Exception] 437 | t = [(ex, handler) for ex in localExceptions] 438 | t.reverse() 439 | 440 | def newfunc(t, *args, **kwargs): 441 | ex, handler = t[0] 442 | try: 443 | if len(t) == 1: 444 | return f(*args, **kwargs) 445 | else: 446 | #Recurse for embedded try/excepts 447 | dec_func = functools.partial(newfunc, t[1:]) 448 | dec_func = functools.update_wrapper(dec_func, f) 449 | return dec_func(*args, **kwargs) 450 | except ex, e: 451 | return handler(e) 452 | 453 | dec_func = functools.partial(newfunc, t) 454 | dec_func = functools.update_wrapper(dec_func, f) 455 | return dec_func 456 | return wrapper 457 | 458 | 459 | def into_debugger(func): 460 | """ 461 | >>> validate_decorator(into_debugger) 462 | """ 463 | 464 | @functools.wraps(func) 465 | def newFunc(*args, **kwargs): 466 | try: 467 | return func(*args, **kwargs) 468 | except: 469 | import pdb 470 | pdb.post_mortem() 471 | 472 | return newFunc 473 | 474 | 475 | class bindclass(object): 476 | """ 477 | >>> validate_decorator(bindclass) 478 | >>> class Foo(BoundObject): 479 | ... @bindclass 480 | ... def foo(this_class, self): 481 | ... return this_class, self 482 | ... 483 | >>> class Bar(Foo): 484 | ... @bindclass 485 | ... def bar(this_class, self): 486 | ... return this_class, self 487 | ... 488 | >>> f = Foo() 489 | >>> b = Bar() 490 | >>> 491 | >>> f.foo() # doctest: +ELLIPSIS 492 | (, <...Foo object at ...>) 493 | >>> b.foo() # doctest: +ELLIPSIS 494 | (, <...Bar object at ...>) 495 | >>> b.bar() # doctest: +ELLIPSIS 496 | (, <...Bar object at ...>) 497 | """ 498 | 499 | def __init__(self, f): 500 | self.f = f 501 | self.__name__ = f.__name__ 502 | self.__doc__ = f.__doc__ 503 | self.__dict__.update(f.__dict__) 504 | self.m = None 505 | 506 | def bind(self, cls, attr): 507 | 508 | def bound_m(*args, **kwargs): 509 | return self.f(cls, *args, **kwargs) 510 | bound_m.__name__ = attr 511 | self.m = bound_m 512 | 513 | def __get__(self, obj, objtype=None): 514 | return self.m.__get__(obj, objtype) 515 | 516 | 517 | class ClassBindingSupport(type): 518 | "@see bindclass" 519 | 520 | def __init__(mcs, name, bases, attrs): 521 | type.__init__(mcs, name, bases, attrs) 522 | for attr, val in attrs.iteritems(): 523 | if isinstance(val, bindclass): 524 | val.bind(mcs, attr) 525 | 526 | 527 | class BoundObject(object): 528 | "@see bindclass" 529 | __metaclass__ = ClassBindingSupport 530 | 531 | 532 | def bindfunction(f): 533 | """ 534 | >>> validate_decorator(bindfunction) 535 | >>> @bindfunction 536 | ... def factorial(thisfunction, n): 537 | ... # Within this function the name 'thisfunction' refers to the factorial 538 | ... # function(with only one argument), even after 'factorial' is bound 539 | ... # to another object 540 | ... if n > 0: 541 | ... return n * thisfunction(n - 1) 542 | ... else: 543 | ... return 1 544 | ... 545 | >>> factorial(3) 546 | 6 547 | """ 548 | 549 | @functools.wraps(f) 550 | def bound_f(*args, **kwargs): 551 | return f(bound_f, *args, **kwargs) 552 | return bound_f 553 | 554 | 555 | class Memoize(object): 556 | """ 557 | Memoize(fn) - an instance which acts like fn but memoizes its arguments 558 | Will only work on functions with non-mutable arguments 559 | @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52201 560 | 561 | >>> validate_decorator(Memoize) 562 | """ 563 | 564 | def __init__(self, fn): 565 | self.fn = fn 566 | self.__name__ = fn.__name__ 567 | self.__doc__ = fn.__doc__ 568 | self.__dict__.update(fn.__dict__) 569 | self.memo = {} 570 | 571 | def __call__(self, *args): 572 | if args not in self.memo: 573 | self.memo[args] = self.fn(*args) 574 | return self.memo[args] 575 | 576 | 577 | class MemoizeMutable(object): 578 | """Memoize(fn) - an instance which acts like fn but memoizes its arguments 579 | Will work on functions with mutable arguments(slower than Memoize) 580 | @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52201 581 | 582 | >>> validate_decorator(MemoizeMutable) 583 | """ 584 | 585 | def __init__(self, fn): 586 | self.fn = fn 587 | self.__name__ = fn.__name__ 588 | self.__doc__ = fn.__doc__ 589 | self.__dict__.update(fn.__dict__) 590 | self.memo = {} 591 | 592 | def __call__(self, *args, **kw): 593 | text = cPickle.dumps((args, kw)) 594 | if text not in self.memo: 595 | self.memo[text] = self.fn(*args, **kw) 596 | return self.memo[text] 597 | 598 | 599 | callTraceIndentationLevel = 0 600 | 601 | 602 | def call_trace(f): 603 | """ 604 | Synchronization decorator. 605 | 606 | >>> validate_decorator(call_trace) 607 | >>> @call_trace 608 | ... def a(a, b, c): 609 | ... pass 610 | >>> a(1, 2, c=3) 611 | Entering a((1, 2), {'c': 3}) 612 | Exiting a((1, 2), {'c': 3}) 613 | """ 614 | 615 | @functools.wraps(f) 616 | def verboseTrace(*args, **kw): 617 | global callTraceIndentationLevel 618 | 619 | print "%sEntering %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw) 620 | callTraceIndentationLevel += 1 621 | try: 622 | result = f(*args, **kw) 623 | except: 624 | callTraceIndentationLevel -= 1 625 | print "%sException %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw) 626 | raise 627 | callTraceIndentationLevel -= 1 628 | print "%sExiting %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw) 629 | return result 630 | 631 | @functools.wraps(f) 632 | def smallTrace(*args, **kw): 633 | global callTraceIndentationLevel 634 | 635 | print "%sEntering %s" % ("\t"*callTraceIndentationLevel, f.__name__) 636 | callTraceIndentationLevel += 1 637 | try: 638 | result = f(*args, **kw) 639 | except: 640 | callTraceIndentationLevel -= 1 641 | print "%sException %s" % ("\t"*callTraceIndentationLevel, f.__name__) 642 | raise 643 | callTraceIndentationLevel -= 1 644 | print "%sExiting %s" % ("\t"*callTraceIndentationLevel, f.__name__) 645 | return result 646 | 647 | #return smallTrace 648 | return verboseTrace 649 | 650 | 651 | @contextlib.contextmanager 652 | def nested_break(): 653 | """ 654 | >>> with nested_break() as mylabel: 655 | ... for i in xrange(3): 656 | ... print "Outer", i 657 | ... for j in xrange(3): 658 | ... if i == 2: raise mylabel 659 | ... if j == 2: break 660 | ... print "Inner", j 661 | ... print "more processing" 662 | Outer 0 663 | Inner 0 664 | Inner 1 665 | Outer 1 666 | Inner 0 667 | Inner 1 668 | Outer 2 669 | """ 670 | 671 | class NestedBreakException(Exception): 672 | pass 673 | 674 | try: 675 | yield NestedBreakException 676 | except NestedBreakException: 677 | pass 678 | 679 | 680 | @contextlib.contextmanager 681 | def lexical_scope(*args): 682 | """ 683 | @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/520586 684 | Example: 685 | >>> b = 0 686 | >>> with lexical_scope(1) as (a): 687 | ... print a 688 | ... 689 | 1 690 | >>> with lexical_scope(1,2,3) as (a,b,c): 691 | ... print a,b,c 692 | ... 693 | 1 2 3 694 | >>> with lexical_scope(): 695 | ... d = 10 696 | ... def foo(): 697 | ... pass 698 | ... 699 | >>> print b 700 | 2 701 | """ 702 | 703 | frame = inspect.currentframe().f_back.f_back 704 | saved = frame.f_locals.keys() 705 | try: 706 | if not args: 707 | yield 708 | elif len(args) == 1: 709 | yield args[0] 710 | else: 711 | yield args 712 | finally: 713 | f_locals = frame.f_locals 714 | for key in (x for x in f_locals.keys() if x not in saved): 715 | del f_locals[key] 716 | del frame 717 | 718 | 719 | def normalize_number(prettynumber): 720 | """ 721 | function to take a phone number and strip out all non-numeric 722 | characters 723 | 724 | >>> normalize_number("+012-(345)-678-90") 725 | '+01234567890' 726 | >>> normalize_number("1-(345)-678-9000") 727 | '+13456789000' 728 | >>> normalize_number("+1-(345)-678-9000") 729 | '+13456789000' 730 | """ 731 | uglynumber = re.sub('[^0-9+]', '', prettynumber) 732 | if uglynumber.startswith("+"): 733 | pass 734 | elif uglynumber.startswith("1"): 735 | uglynumber = "+"+uglynumber 736 | elif 10 <= len(uglynumber): 737 | assert uglynumber[0] not in ("+", "1"), "Number format confusing" 738 | uglynumber = "+1"+uglynumber 739 | else: 740 | pass 741 | 742 | return uglynumber 743 | 744 | 745 | _VALIDATE_RE = re.compile("^\+?[0-9]{10,}$") 746 | 747 | 748 | def is_valid_number(number): 749 | """ 750 | @returns If This number be called ( syntax validation only ) 751 | """ 752 | return _VALIDATE_RE.match(number) is not None 753 | 754 | 755 | def make_ugly(prettynumber): 756 | """ 757 | function to take a phone number and strip out all non-numeric 758 | characters 759 | 760 | >>> make_ugly("+012-(345)-678-90") 761 | '+01234567890' 762 | """ 763 | return normalize_number(prettynumber) 764 | 765 | 766 | def _make_pretty_with_areacode(phonenumber): 767 | prettynumber = "(%s)" % (phonenumber[0:3], ) 768 | if 3 < len(phonenumber): 769 | prettynumber += " %s" % (phonenumber[3:6], ) 770 | if 6 < len(phonenumber): 771 | prettynumber += "-%s" % (phonenumber[6:], ) 772 | return prettynumber 773 | 774 | 775 | def _make_pretty_local(phonenumber): 776 | prettynumber = "%s" % (phonenumber[0:3], ) 777 | if 3 < len(phonenumber): 778 | prettynumber += "-%s" % (phonenumber[3:], ) 779 | return prettynumber 780 | 781 | 782 | def _make_pretty_international(phonenumber): 783 | prettynumber = phonenumber 784 | if phonenumber.startswith("1"): 785 | prettynumber = "1 " 786 | prettynumber += _make_pretty_with_areacode(phonenumber[1:]) 787 | return prettynumber 788 | 789 | 790 | def make_pretty(phonenumber): 791 | """ 792 | Function to take a phone number and return the pretty version 793 | pretty numbers: 794 | if phonenumber begins with 0: 795 | ...-(...)-...-.... 796 | if phonenumber begins with 1: ( for gizmo callback numbers ) 797 | 1 (...)-...-.... 798 | if phonenumber is 13 digits: 799 | (...)-...-.... 800 | if phonenumber is 10 digits: 801 | ...-.... 802 | >>> make_pretty("12") 803 | '12' 804 | >>> make_pretty("1234567") 805 | '123-4567' 806 | >>> make_pretty("2345678901") 807 | '+1 (234) 567-8901' 808 | >>> make_pretty("12345678901") 809 | '+1 (234) 567-8901' 810 | >>> make_pretty("01234567890") 811 | '+012 (345) 678-90' 812 | >>> make_pretty("+01234567890") 813 | '+012 (345) 678-90' 814 | >>> make_pretty("+12") 815 | '+1 (2)' 816 | >>> make_pretty("+123") 817 | '+1 (23)' 818 | >>> make_pretty("+1234") 819 | '+1 (234)' 820 | """ 821 | if phonenumber is None or phonenumber == "": 822 | return "" 823 | 824 | phonenumber = normalize_number(phonenumber) 825 | 826 | if phonenumber == "": 827 | return "" 828 | elif phonenumber[0] == "+": 829 | prettynumber = _make_pretty_international(phonenumber[1:]) 830 | if not prettynumber.startswith("+"): 831 | prettynumber = "+"+prettynumber 832 | elif 8 < len(phonenumber) and phonenumber[0] in ("1", ): 833 | prettynumber = _make_pretty_international(phonenumber) 834 | elif 7 < len(phonenumber): 835 | prettynumber = _make_pretty_with_areacode(phonenumber) 836 | elif 3 < len(phonenumber): 837 | prettynumber = _make_pretty_local(phonenumber) 838 | else: 839 | prettynumber = phonenumber 840 | return prettynumber.strip() 841 | 842 | 843 | def similar_ugly_numbers(lhs, rhs): 844 | return ( 845 | lhs == rhs or 846 | lhs[1:] == rhs and lhs.startswith("1") or 847 | lhs[2:] == rhs and lhs.startswith("+1") or 848 | lhs == rhs[1:] and rhs.startswith("1") or 849 | lhs == rhs[2:] and rhs.startswith("+1") 850 | ) 851 | 852 | 853 | def abbrev_relative_date(date): 854 | """ 855 | >>> abbrev_relative_date("42 hours ago") 856 | '42 h' 857 | >>> abbrev_relative_date("2 days ago") 858 | '2 d' 859 | >>> abbrev_relative_date("4 weeks ago") 860 | '4 w' 861 | """ 862 | parts = date.split(" ") 863 | return "%s %s" % (parts[0], parts[1][0]) 864 | 865 | 866 | def parse_version(versionText): 867 | """ 868 | >>> parse_version("0.5.2") 869 | [0, 5, 2] 870 | """ 871 | return [ 872 | int(number) 873 | for number in versionText.split(".") 874 | ] 875 | 876 | 877 | def compare_versions(leftParsedVersion, rightParsedVersion): 878 | """ 879 | >>> compare_versions([0, 1, 2], [0, 1, 2]) 880 | 0 881 | >>> compare_versions([0, 1, 2], [0, 1, 3]) 882 | -1 883 | >>> compare_versions([0, 1, 2], [0, 2, 2]) 884 | -1 885 | >>> compare_versions([0, 1, 2], [1, 1, 2]) 886 | -1 887 | >>> compare_versions([0, 1, 3], [0, 1, 2]) 888 | 1 889 | >>> compare_versions([0, 2, 2], [0, 1, 2]) 890 | 1 891 | >>> compare_versions([1, 1, 2], [0, 1, 2]) 892 | 1 893 | """ 894 | for left, right in zip(leftParsedVersion, rightParsedVersion): 895 | if left < right: 896 | return -1 897 | elif right < left: 898 | return 1 899 | else: 900 | return 0 901 | -------------------------------------------------------------------------------- /util/overloading.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import new 3 | 4 | # Make the environment more like Python 3.0 5 | __metaclass__ = type 6 | from itertools import izip as zip 7 | import textwrap 8 | import inspect 9 | 10 | 11 | __all__ = [ 12 | "AnyType", 13 | "overloaded" 14 | ] 15 | 16 | 17 | AnyType = object 18 | 19 | 20 | class overloaded: 21 | """ 22 | Dynamically overloaded functions. 23 | 24 | This is an implementation of (dynamically, or run-time) overloaded 25 | functions; also known as generic functions or multi-methods. 26 | 27 | The dispatch algorithm uses the types of all argument for dispatch, 28 | similar to (compile-time) overloaded functions or methods in C++ and 29 | Java. 30 | 31 | Most of the complexity in the algorithm comes from the need to support 32 | subclasses in call signatures. For example, if an function is 33 | registered for a signature (T1, T2), then a call with a signature (S1, 34 | S2) is acceptable, assuming that S1 is a subclass of T1, S2 a subclass 35 | of T2, and there are no other more specific matches (see below). 36 | 37 | If there are multiple matches and one of those doesn't *dominate* all 38 | others, the match is deemed ambiguous and an exception is raised. A 39 | subtlety here: if, after removing the dominated matches, there are 40 | still multiple matches left, but they all map to the same function, 41 | then the match is not deemed ambiguous and that function is used. 42 | Read the method find_func() below for details. 43 | 44 | @note Python 2.5 is required due to the use of predicates any() and all(). 45 | @note only supports positional arguments 46 | 47 | @author http://www.artima.com/weblogs/viewpost.jsp?thread=155514 48 | 49 | >>> import misc 50 | >>> misc.validate_decorator (overloaded) 51 | >>> 52 | >>> 53 | >>> 54 | >>> 55 | >>> ################# 56 | >>> #Basics, with reusing names and without 57 | >>> @overloaded 58 | ... def foo(x): 59 | ... "prints x" 60 | ... print x 61 | ... 62 | >>> @foo.register(int) 63 | ... def foo(x): 64 | ... "prints the hex representation of x" 65 | ... print hex(x) 66 | ... 67 | >>> from types import DictType 68 | >>> @foo.register(DictType) 69 | ... def foo_dict(x): 70 | ... "prints the keys of x" 71 | ... print [k for k in x.iterkeys()] 72 | ... 73 | >>> #combines all of the doc strings to help keep track of the specializations 74 | >>> foo.__doc__ # doctest: +ELLIPSIS 75 | "prints x\\n\\n...overloading.foo ():\\n\\tprints the hex representation of x\\n\\n...overloading.foo_dict ():\\n\\tprints the keys of x" 76 | >>> foo ("text") 77 | text 78 | >>> foo (10) #calling the specialized foo 79 | 0xa 80 | >>> foo ({3:5, 6:7}) #calling the specialization foo_dict 81 | [3, 6] 82 | >>> foo_dict ({3:5, 6:7}) #with using a unique name, you still have the option of calling the function directly 83 | [3, 6] 84 | >>> 85 | >>> 86 | >>> 87 | >>> 88 | >>> ################# 89 | >>> #Multiple arguments, accessing the default, and function finding 90 | >>> @overloaded 91 | ... def two_arg (x, y): 92 | ... print x,y 93 | ... 94 | >>> @two_arg.register(int, int) 95 | ... def two_arg_int_int (x, y): 96 | ... print hex(x), hex(y) 97 | ... 98 | >>> @two_arg.register(float, int) 99 | ... def two_arg_float_int (x, y): 100 | ... print x, hex(y) 101 | ... 102 | >>> @two_arg.register(int, float) 103 | ... def two_arg_int_float (x, y): 104 | ... print hex(x), y 105 | ... 106 | >>> two_arg.__doc__ # doctest: +ELLIPSIS 107 | "...overloading.two_arg_int_int (, ):\\n\\n...overloading.two_arg_float_int (, ):\\n\\n...overloading.two_arg_int_float (, ):" 108 | >>> two_arg(9, 10) 109 | 0x9 0xa 110 | >>> two_arg(9.0, 10) 111 | 9.0 0xa 112 | >>> two_arg(15, 16.0) 113 | 0xf 16.0 114 | >>> two_arg.default_func(9, 10) 115 | 9 10 116 | >>> two_arg.find_func ((int, float)) == two_arg_int_float 117 | True 118 | >>> (int, float) in two_arg 119 | True 120 | >>> (str, int) in two_arg 121 | False 122 | >>> 123 | >>> 124 | >>> 125 | >>> ################# 126 | >>> #wildcard 127 | >>> @two_arg.register(AnyType, str) 128 | ... def two_arg_any_str (x, y): 129 | ... print x, y.lower() 130 | ... 131 | >>> two_arg("Hello", "World") 132 | Hello world 133 | >>> two_arg(500, "World") 134 | 500 world 135 | """ 136 | 137 | def __init__(self, default_func): 138 | # Decorator to declare new overloaded function. 139 | self.registry = {} 140 | self.cache = {} 141 | self.default_func = default_func 142 | self.__name__ = self.default_func.__name__ 143 | self.__doc__ = self.default_func.__doc__ 144 | self.__dict__.update (self.default_func.__dict__) 145 | 146 | def __get__(self, obj, type=None): 147 | if obj is None: 148 | return self 149 | return new.instancemethod(self, obj) 150 | 151 | def register(self, *types): 152 | """ 153 | Decorator to register an implementation for a specific set of types. 154 | 155 | .register(t1, t2)(f) is equivalent to .register_func((t1, t2), f). 156 | """ 157 | 158 | def helper(func): 159 | self.register_func(types, func) 160 | 161 | originalDoc = self.__doc__ if self.__doc__ is not None else "" 162 | typeNames = ", ".join ([str(type) for type in types]) 163 | typeNames = "".join ([func.__module__+".", func.__name__, " (", typeNames, "):"]) 164 | overloadedDoc = "" 165 | if func.__doc__ is not None: 166 | overloadedDoc = textwrap.fill (func.__doc__, width=60, initial_indent="\t", subsequent_indent="\t") 167 | self.__doc__ = "\n".join ([originalDoc, "", typeNames, overloadedDoc]).strip() 168 | 169 | new_func = func 170 | 171 | #Masking the function, so we want to take on its traits 172 | if func.__name__ == self.__name__: 173 | self.__dict__.update (func.__dict__) 174 | new_func = self 175 | return new_func 176 | 177 | return helper 178 | 179 | def register_func(self, types, func): 180 | """Helper to register an implementation.""" 181 | self.registry[tuple(types)] = func 182 | self.cache = {} # Clear the cache (later we can optimize this). 183 | 184 | def __call__(self, *args): 185 | """Call the overloaded function.""" 186 | types = tuple(map(type, args)) 187 | func = self.cache.get(types) 188 | if func is None: 189 | self.cache[types] = func = self.find_func(types) 190 | return func(*args) 191 | 192 | def __contains__ (self, types): 193 | return self.find_func(types) is not self.default_func 194 | 195 | def find_func(self, types): 196 | """Find the appropriate overloaded function; don't call it. 197 | 198 | @note This won't work for old-style classes or classes without __mro__ 199 | """ 200 | func = self.registry.get(types) 201 | if func is not None: 202 | # Easy case -- direct hit in registry. 203 | return func 204 | 205 | # Phillip Eby suggests to use issubclass() instead of __mro__. 206 | # There are advantages and disadvantages. 207 | 208 | # I can't help myself -- this is going to be intense functional code. 209 | # Find all possible candidate signatures. 210 | mros = tuple(inspect.getmro(t) for t in types) 211 | n = len(mros) 212 | candidates = [sig for sig in self.registry 213 | if len(sig) == n and 214 | all(t in mro for t, mro in zip(sig, mros))] 215 | 216 | if not candidates: 217 | # No match at all -- use the default function. 218 | return self.default_func 219 | elif len(candidates) == 1: 220 | # Unique match -- that's an easy case. 221 | return self.registry[candidates[0]] 222 | 223 | # More than one match -- weed out the subordinate ones. 224 | 225 | def dominates(dom, sub, 226 | orders=tuple(dict((t, i) for i, t in enumerate(mro)) 227 | for mro in mros)): 228 | # Predicate to decide whether dom strictly dominates sub. 229 | # Strict domination is defined as domination without equality. 230 | # The arguments dom and sub are type tuples of equal length. 231 | # The orders argument is a precomputed auxiliary data structure 232 | # giving dicts of ordering information corresponding to the 233 | # positions in the type tuples. 234 | # A type d dominates a type s iff order[d] <= order[s]. 235 | # A type tuple (d1, d2, ...) dominates a type tuple of equal length 236 | # (s1, s2, ...) iff d1 dominates s1, d2 dominates s2, etc. 237 | if dom is sub: 238 | return False 239 | return all(order[d] <= order[s] for d, s, order in zip(dom, sub, orders)) 240 | 241 | # I suppose I could inline dominates() but it wouldn't get any clearer. 242 | candidates = [cand 243 | for cand in candidates 244 | if not any(dominates(dom, cand) for dom in candidates)] 245 | if len(candidates) == 1: 246 | # There's exactly one candidate left. 247 | return self.registry[candidates[0]] 248 | 249 | # Perhaps these multiple candidates all have the same implementation? 250 | funcs = set(self.registry[cand] for cand in candidates) 251 | if len(funcs) == 1: 252 | return funcs.pop() 253 | 254 | # No, the situation is irreducibly ambiguous. 255 | raise TypeError("ambigous call; types=%r; candidates=%r" % 256 | (types, candidates)) 257 | -------------------------------------------------------------------------------- /util/qml_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | QML Tips: 5 | Large images: 6 | QML asynchronous = true; cache = false; [1] 7 | Insert properties at top of element declarations [1] 8 | Non-visible items: set opacity to 0 [2] 9 | Use Loader [1] 10 | Keep QML files small [1] 11 | 12 | [1] http://sf2011.meego.com/program/sessions/performance-tips-and-tricks-qtqml-applications-0 13 | [2] http://doc.qt.nokia.com/4.7/qdeclarativeperformance.html 14 | """ 15 | 16 | from __future__ import with_statement 17 | from __future__ import division 18 | 19 | import os 20 | import logging 21 | 22 | import qt_compat 23 | QtCore = qt_compat.QtCore 24 | QtGui = qt_compat.import_module("QtGui") 25 | QtDeclarative = qt_compat.import_module("QtDeclarative") 26 | 27 | 28 | _moduleLogger = logging.getLogger(__name__) 29 | 30 | 31 | class DeclarativeView(QtDeclarative.QDeclarativeView): 32 | 33 | def __init__(self): 34 | QtDeclarative.QDeclarativeView.__init__(self) 35 | 36 | closing = qt_compat.Signal() 37 | 38 | def closeEvent(self, event): 39 | self.closing.emit() 40 | event.ignore() 41 | 42 | 43 | def disable_default_window_painting(view): 44 | """ 45 | See http://doc.qt.nokia.com/4.7-snapshot/qdeclarativeperformance.html 46 | """ 47 | view.setAttribute(QtCore.Qt.WA_OpaquePaintEvent) 48 | view.setAttribute(QtCore.Qt.WA_NoSystemBackground) 49 | view.viewport().setAttribute(QtCore.Qt.WA_OpaquePaintEvent) 50 | view.viewport().setAttribute(QtCore.Qt.WA_NoSystemBackground) 51 | 52 | 53 | class SystemThemeIconProvider(QtDeclarative.QDeclarativeImageProvider): 54 | 55 | IMAGE_TYPE = QtDeclarative.QDeclarativeImageProvider.ImageType.Pixmap 56 | 57 | def __init__(self): 58 | QtDeclarative.QDeclarativeImageProvider.__init__(self, self.IMAGE_TYPE) 59 | 60 | def requestPixmap(self, id, size, requestedSize): 61 | icon = QtGui.QIcon.fromTheme(id) 62 | pixmap = icon.pixmap(requestedSize) 63 | return pixmap 64 | 65 | 66 | class LocalImageProvider(QtDeclarative.QDeclarativeImageProvider): 67 | 68 | IMAGE_TYPE = QtDeclarative.QDeclarativeImageProvider.ImageType.Image 69 | 70 | def __init__(self, path): 71 | QtDeclarative.QDeclarativeImageProvider.__init__(self, self.IMAGE_TYPE) 72 | self._path = path 73 | 74 | def requestImage(self, id, size, requestedSize): 75 | image = QtGui.QImage(os.path.join(self._path, id)) 76 | return image 77 | 78 | 79 | if __name__ == "__main__": 80 | pass 81 | 82 | -------------------------------------------------------------------------------- /util/qore_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import with_statement 4 | 5 | import sys 6 | import inspect 7 | import contextlib 8 | import logging 9 | 10 | import qt_compat 11 | QtCore = qt_compat.QtCore 12 | 13 | import misc 14 | 15 | 16 | _moduleLogger = logging.getLogger(__name__) 17 | 18 | 19 | class QThread44(QtCore.QThread): 20 | """ 21 | This is to imitate QThread in Qt 4.4+ for when running on older version 22 | See http://labs.trolltech.com/blogs/2010/06/17/youre-doing-it-wrong 23 | (On Lucid I have Qt 4.7 and this is still an issue) 24 | """ 25 | 26 | def __init__(self, parent = None): 27 | QtCore.QThread.__init__(self, parent) 28 | 29 | def run(self): 30 | self.exec_() 31 | 32 | 33 | class _WorkerThread(QtCore.QObject): 34 | 35 | _taskComplete = qt_compat.Signal(object) 36 | 37 | def __init__(self, futureThread): 38 | QtCore.QObject.__init__(self) 39 | self._futureThread = futureThread 40 | self._futureThread._addTask.connect(self._on_task_added) 41 | self._taskComplete.connect(self._futureThread._on_task_complete) 42 | 43 | @qt_compat.Slot(object) 44 | @misc.log_exception(_moduleLogger) 45 | def _on_task_added(self, task): 46 | self.__on_task_added(task) 47 | 48 | @misc.log_exception(_moduleLogger) 49 | def __on_task_added(self, task): 50 | if not self._futureThread._isRunning: 51 | _moduleLogger.error("Dropping task") 52 | 53 | func, args, kwds, on_success, on_error = task 54 | 55 | try: 56 | result = func(*args, **kwds) 57 | isError = False 58 | except Exception, e: 59 | _moduleLogger.error("Error, passing it back to the main thread") 60 | result = e 61 | isError = True 62 | 63 | taskResult = on_success, on_error, isError, result 64 | self._taskComplete.emit(taskResult) 65 | 66 | 67 | class FutureThread(QtCore.QObject): 68 | 69 | _addTask = qt_compat.Signal(object) 70 | 71 | def __init__(self): 72 | QtCore.QObject.__init__(self) 73 | self._thread = QThread44() 74 | self._isRunning = False 75 | self._worker = _WorkerThread(self) 76 | self._worker.moveToThread(self._thread) 77 | 78 | def start(self): 79 | self._thread.start() 80 | self._isRunning = True 81 | 82 | def stop(self): 83 | self._isRunning = False 84 | self._thread.quit() 85 | self._thread.wait() 86 | 87 | def add_task(self, func, args, kwds, on_success, on_error): 88 | assert self._isRunning, "Task queue not started" 89 | task = func, args, kwds, on_success, on_error 90 | self._addTask.emit(task) 91 | 92 | @qt_compat.Slot(object) 93 | @misc.log_exception(_moduleLogger) 94 | def _on_task_complete(self, taskResult): 95 | self.__on_task_complete(taskResult) 96 | 97 | @misc.log_exception(_moduleLogger) 98 | def __on_task_complete(self, taskResult): 99 | on_success, on_error, isError, result = taskResult 100 | if not self._isRunning: 101 | if isError: 102 | _moduleLogger.error("Masking: %s" % (result, )) 103 | isError = True 104 | result = StopIteration("Cancelling all callbacks") 105 | callback = on_success if not isError else on_error 106 | try: 107 | callback(result) 108 | except Exception: 109 | _moduleLogger.exception("Callback errored") 110 | 111 | 112 | @contextlib.contextmanager 113 | def notify_error(log): 114 | try: 115 | yield 116 | except: 117 | log.push_exception() 118 | 119 | 120 | @contextlib.contextmanager 121 | def notify_busy(log, message): 122 | log.push_busy(message) 123 | try: 124 | yield 125 | finally: 126 | log.pop(message) 127 | 128 | 129 | class QErrorMessage(QtCore.QObject): 130 | 131 | LEVEL_ERROR = logging.ERROR 132 | LEVEL_WARNING = logging.WARNING 133 | LEVEL_INFO = logging.INFO 134 | LEVEL_BUSY = -1 135 | 136 | def __init__(self, message, level): 137 | QtCore.QObject.__init__(self) 138 | self._message = message 139 | self._level = level 140 | 141 | changed = qt_compat.Signal() 142 | level = qt_compat.Property(int, lambda self: self._level, notify=changed) 143 | message = qt_compat.Property(unicode, lambda self: self._message, notify=changed) 144 | 145 | def __repr__(self): 146 | return "%s.%s(%r, %r)" % (__name__, self.__class__.__name__, self._message, self._level) 147 | 148 | 149 | class QErrorLog(QtCore.QObject): 150 | 151 | messagePushed = qt_compat.Signal() 152 | messagePopped = qt_compat.Signal() 153 | currentMessageChanged = qt_compat.Signal() 154 | 155 | def __init__(self): 156 | QtCore.QObject.__init__(self) 157 | self._messages = [] 158 | self._nullMessage = QErrorMessage("", QErrorMessage.LEVEL_INFO) 159 | 160 | @qt_compat.Slot(str) 161 | def push_busy(self, message): 162 | _moduleLogger.debug("Entering state: %s" % message) 163 | self._push_message(message, QErrorMessage.LEVEL_BUSY) 164 | 165 | @qt_compat.Slot(str) 166 | def push_info(self, message): 167 | self._push_message(message, QErrorMessage.LEVEL_INFO) 168 | 169 | @qt_compat.Slot(str) 170 | def push_error(self, message): 171 | self._push_message(message, QErrorMessage.LEVEL_ERROR) 172 | 173 | @qt_compat.Slot(str, int) 174 | def push_message(self, message, level): 175 | self._push_message(message, level) 176 | 177 | def push_exception(self): 178 | userMessage = str(sys.exc_info()[1]) 179 | _moduleLogger.exception(userMessage) 180 | self.push_error(userMessage) 181 | 182 | @qt_compat.Slot() 183 | @qt_compat.Slot(str) 184 | def pop(self, message = None): 185 | if message is None: 186 | del self._messages[0] 187 | else: 188 | _moduleLogger.debug("Exiting state: %s" % message) 189 | messageIndex = [ 190 | i 191 | for (i, error) in enumerate(self._messages) 192 | if error.message == message 193 | ] 194 | # Might be removed out of order 195 | if messageIndex: 196 | del self._messages[messageIndex[0]] 197 | self.messagePopped.emit() 198 | self.currentMessageChanged.emit() 199 | 200 | def peek_message(self): 201 | if self._messages: 202 | return self._messages[0] 203 | else: 204 | return self._nullMessage 205 | 206 | currentMessage = qt_compat.Property(QtCore.QObject, lambda self: self.peek_message(), notify=currentMessageChanged) 207 | hasMessages = qt_compat.Property(bool, lambda self: bool(self._messages), notify=currentMessageChanged) 208 | 209 | def _push_message(self, message, level): 210 | self._messages.append(QErrorMessage(message, level)) 211 | # Sort is defined as stable, so this should be fine 212 | self._messages.sort(key=lambda x: x.level) 213 | self._messages.reverse() 214 | self.messagePushed.emit() 215 | self.currentMessageChanged.emit() 216 | 217 | def __len__(self): 218 | return len(self._messages) 219 | 220 | 221 | def create_single_column_list_model(columnName, **kwargs): 222 | """ 223 | >>> class Single(object): pass 224 | >>> SingleListModel = create_single_column_list_model("s") 225 | >>> slm = SingleListModel([Single(), Single(), Single()]) 226 | """ 227 | 228 | class SingleColumnListModel(QtCore.QAbstractListModel): 229 | 230 | def __init__(self, l = None): 231 | QtCore.QAbstractListModel.__init__(self) 232 | self._list = l if l is not None else [] 233 | self.setRoleNames({0: columnName}) 234 | 235 | def __len__(self): 236 | return len(self._list) 237 | 238 | def __getitem__(self, key): 239 | return self._list[key] 240 | 241 | def __setitem__(self, key, value): 242 | with scoped_model_reset(self): 243 | self._list[key] = value 244 | 245 | def __delitem__(self, key): 246 | with scoped_model_reset(self): 247 | del self._list[key] 248 | 249 | def __iter__(self): 250 | return iter(self._list) 251 | 252 | def __repr__(self): 253 | return '<%s (%s)>' % ( 254 | self.__class__.__name__, 255 | columnName, 256 | ) 257 | 258 | def rowCount(self, parent=QtCore.QModelIndex()): 259 | return len(self._list) 260 | 261 | def data(self, index, role): 262 | if index.isValid() and role == 0: 263 | return self._list[index.row()] 264 | return None 265 | 266 | if "name" in kwargs: 267 | SingleColumnListModel.__name__ = kwargs["name"] 268 | 269 | return SingleColumnListModel 270 | 271 | 272 | def create_tupled_list_model(*columnNames, **kwargs): 273 | """ 274 | >>> class Column0(object): pass 275 | >>> class Column1(object): pass 276 | >>> class Column2(object): pass 277 | >>> MultiColumnedListModel = create_tupled_list_model("c0", "c1", "c2") 278 | >>> mclm = MultiColumnedListModel([(Column0(), Column1(), Column2())]) 279 | """ 280 | 281 | class TupledListModel(QtCore.QAbstractListModel): 282 | 283 | def __init__(self, l = None): 284 | QtCore.QAbstractListModel.__init__(self) 285 | self._list = l if l is not None else [] 286 | self.setRoleNames(dict(enumerate(columnNames))) 287 | 288 | def __len__(self): 289 | return len(self._list) 290 | 291 | def __getitem__(self, key): 292 | return self._list[key] 293 | 294 | def __setitem__(self, key, value): 295 | with scoped_model_reset(self): 296 | self._list[key] = value 297 | 298 | def __delitem__(self, key): 299 | with scoped_model_reset(self): 300 | del self._list[key] 301 | 302 | def __iter__(self): 303 | return iter(self._list) 304 | 305 | def __repr__(self): 306 | return '<%s (%s)>' % ( 307 | self.__class__.__name__, 308 | ', '.join(columnNames), 309 | ) 310 | 311 | def rowCount(self, parent=QtCore.QModelIndex()): 312 | return len(self._list) 313 | 314 | def data(self, index, role): 315 | if index.isValid() and 0 <= role and role < len(columnNames): 316 | return self._list[index.row()][role] 317 | return None 318 | 319 | if "name" in kwargs: 320 | TupledListModel.__name__ = kwargs["name"] 321 | 322 | return TupledListModel 323 | 324 | 325 | class FileSystemModel(QtCore.QAbstractListModel): 326 | """ 327 | Wrapper around QtGui.QFileSystemModel 328 | """ 329 | 330 | FILEINFOS = [ 331 | "fileName", 332 | "isDir", 333 | "filePath", 334 | "completeSuffix", 335 | "baseName", 336 | ] 337 | 338 | EXTINFOS = [ 339 | "type", 340 | ] 341 | 342 | ALLINFOS = FILEINFOS + EXTINFOS 343 | 344 | def __init__(self, model, path): 345 | QtCore.QAbstractListModel.__init__(self) 346 | self._path = path 347 | 348 | self._model = model 349 | self._rootIndex = self._model.index(self._path) 350 | 351 | self._child = None 352 | self.setRoleNames(dict(enumerate(self.ALLINFOS))) 353 | self._model.directoryLoaded.connect(self._on_directory_loaded) 354 | 355 | childChanged = qt_compat.Signal(QtCore.QObject) 356 | 357 | def _child(self): 358 | assert self._child is not None 359 | return self._child 360 | 361 | child = qt_compat.Property(QtCore.QObject, _child, notify=childChanged) 362 | 363 | backendChanged = qt_compat.Signal() 364 | 365 | def _parent(self): 366 | finfo = self._model.fileInfo(self._rootIndex) 367 | return finfo.fileName() 368 | 369 | parent = qt_compat.Property(str, _parent, notify=backendChanged) 370 | 371 | @qt_compat.Slot(str) 372 | @misc.log_exception(_moduleLogger) 373 | def browse_to(self, path): 374 | if self._child is None: 375 | self._child = FileSystemModel(self._model, path) 376 | else: 377 | self._child.switch_to(path) 378 | self.childChanged.emit() 379 | return self._child 380 | 381 | @qt_compat.Slot(str) 382 | @misc.log_exception(_moduleLogger) 383 | def switch_to(self, path): 384 | with scoped_model_reset(self): 385 | self._path = path 386 | self._rootIndex = self._model.index(self._path) 387 | self.backendChanged.emit() 388 | 389 | def __len__(self): 390 | return self._model.rowCount(self._rootIndex) 391 | 392 | def __getitem__(self, key): 393 | return self._model.index(key, 0, self._rootIndex) 394 | 395 | def __iter__(self): 396 | return (self[i] for i in xrange(len(self))) 397 | 398 | def rowCount(self, parent=QtCore.QModelIndex()): 399 | return len(self) 400 | 401 | def data(self, index, role): 402 | if index.isValid() and 0 <= role and role < len(self.ALLINFOS): 403 | internalIndex = self._translate_index(index) 404 | info = self._model.fileInfo(internalIndex) 405 | if role < len(self.FILEINFOS): 406 | field = self.FILEINFOS[role] 407 | value = getattr(info, field)() 408 | else: 409 | role -= len(self.FILEINFOS) 410 | field = self.EXTINFOS[role] 411 | if field == "type": 412 | return self._model.type(internalIndex) 413 | else: 414 | raise NotImplementedError("Out of range that was already checked") 415 | return value 416 | return None 417 | 418 | def _on_directory_loaded(self, path): 419 | if self._path == path: 420 | self.backendChanged.emit() 421 | self.reset() 422 | 423 | def _translate_index(self, externalIndex): 424 | internalIndex = self._model.index(externalIndex.row(), 0, self._rootIndex) 425 | return internalIndex 426 | 427 | 428 | @contextlib.contextmanager 429 | def scoped_model_reset(model): 430 | model.beginResetModel() 431 | try: 432 | yield 433 | finally: 434 | model.endResetModel() 435 | 436 | 437 | def create_qobject(*classDef, **kwargs): 438 | """ 439 | >>> Car = create_qobject( 440 | ... ('model', str), 441 | ... ('brand', str), 442 | ... ('year', int), 443 | ... ('inStock', bool), 444 | ... name='Car' 445 | ... ) 446 | >>> print Car 447 | 448 | >>> 449 | >>> c = Car(model='Fiesta', brand='Ford', year=1337) 450 | >>> print c.model, c.brand, c.year, c.inStock 451 | Fiesta Ford 1337 False 452 | >>> print c 453 | 454 | >>> 455 | >>> c.inStock = True 456 | >>> 457 | >>> print c.model, c.brand, c.year, c.inStock 458 | Fiesta Ford 1337 True 459 | >>> print c 460 | 461 | """ 462 | 463 | class AutoQObject(QtCore.QObject): 464 | 465 | def __init__(self, **initKwargs): 466 | QtCore.QObject.__init__(self) 467 | for key, val in classDef: 468 | setattr(self, '_'+key, initKwargs.get(key, val())) 469 | 470 | def __repr__(self): 471 | qTypeNames = ( 472 | '%s=%r' % (key, getattr(self, '_'+key)) 473 | for key, qTypeName in classDef 474 | ) 475 | return '<%s (%s)>' % ( 476 | kwargs.get('name', self.__class__.__name__), 477 | ', '.join(qTypeNames), 478 | ) 479 | 480 | for key, qTypeName in classDef: 481 | nfy = locals()['_nfy_'+key] = qt_compat.Signal() 482 | 483 | def _get(key): 484 | def f(self): 485 | return self.__dict__['_'+key] 486 | return f 487 | 488 | def _set(key): 489 | def f(self, qTypeName): 490 | setattr(self, '_'+key, qTypeName) 491 | getattr(self, '_nfy_'+key).emit() 492 | return f 493 | 494 | setter = locals()['_set_'+key] = _set(key) 495 | getter = locals()['_get_'+key] = _get(key) 496 | 497 | locals()[key] = qt_compat.Property(qTypeName, getter, setter, notify=nfy) 498 | del nfy, _get, _set, getter, setter 499 | 500 | return AutoQObject 501 | 502 | 503 | def obj_to_qtype(obj): 504 | return type(obj) 505 | 506 | 507 | def create_object_proxy(obj, **kwargs): 508 | """ 509 | >>> class Constants(object): 510 | ... FILE = "a" 511 | ... BIRD = "b" 512 | >>> print Constants 513 | 514 | >>> qConstants = create_object_proxy(Constants, name="constants")() 515 | >>> qConstants._get_FILE() 516 | 'a' 517 | >>> qConstants._get_BIRD() 518 | 'b' 519 | """ 520 | 521 | members = list( 522 | (name, value) 523 | for (name, value) in inspect.getmembers(obj) 524 | if not name.startswith("_") 525 | ) 526 | 527 | class AutoQObject(QtCore.QObject): 528 | 529 | def __init__(self, **initKwargs): 530 | QtCore.QObject.__init__(self) 531 | 532 | def __repr__(self): 533 | return '<%s (wrapping %r)>' % ( 534 | kwargs.get('name', self.__class__.__name__), 535 | obj, 536 | ) 537 | 538 | changed = qt_compat.Signal() 539 | 540 | for key, value in members: 541 | qTypeName = obj_to_qtype(value) 542 | 543 | def _get(key): 544 | def _get(self): 545 | return getattr(obj, key) 546 | return _get 547 | 548 | def _set(key): 549 | if key == key.upper(): 550 | def _set_constant(self, v): 551 | raise NotImplementedError() 552 | return _set_constant 553 | else: 554 | def _set_mutable(self, v): 555 | setattr(obj, key, v) 556 | getattr(self, "changed").emit() 557 | return _set_mutable 558 | 559 | setter = locals()['_set_'+key] = _set(key) 560 | getter = locals()['_get_'+key] = _get(key) 561 | 562 | locals()[key] = qt_compat.Property(qTypeName, getter, setter, notify=changed) 563 | del _get, _set, getter, setter, qTypeName 564 | 565 | return AutoQObject 566 | 567 | 568 | class QObjectProxy(object): 569 | """ 570 | Proxy for accessing properties and slots as attributes 571 | 572 | This class acts as a proxy for the object for which it is 573 | created, and makes property access more Pythonic while 574 | still allowing access to slots (as member functions). 575 | 576 | Attribute names starting with '_' are not proxied. 577 | """ 578 | 579 | def __init__(self, rootQObject): 580 | self._rootQObject = rootQObject 581 | m = self._rootQObject.metaObject() 582 | self._properties = [ 583 | m.property(i).name() 584 | for i in xrange(m.propertyCount()) 585 | ] 586 | 587 | def __getattr__(self, key): 588 | value = self._rootQObject.property(key) 589 | 590 | # No such property, so assume we call a slot 591 | if value is None and key not in self._properties: 592 | return getattr(self._rootQObject, key) 593 | 594 | return value 595 | 596 | def __setattr__(self, key, value): 597 | if key.startswith('_'): 598 | object.__setattr__(self, key, value) 599 | else: 600 | self._rootQObject.setProperty(key, value) 601 | 602 | 603 | if __name__ == "__main__": 604 | import doctest 605 | print doctest.testmod() 606 | -------------------------------------------------------------------------------- /util/qt_compat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import with_statement 4 | from __future__ import division 5 | 6 | _TRY_PYSIDE = True 7 | 8 | try: 9 | if not _TRY_PYSIDE: 10 | raise ImportError() 11 | import PySide.QtCore as _QtCore 12 | QtCore = _QtCore 13 | USES_PYSIDE = True 14 | except ImportError: 15 | import sip 16 | sip.setapi('QString', 2) 17 | sip.setapi('QVariant', 2) 18 | import PyQt4.QtCore as _QtCore 19 | QtCore = _QtCore 20 | USES_PYSIDE = False 21 | 22 | 23 | def _pyside_import_module(moduleName): 24 | pyside = __import__('PySide', globals(), locals(), [moduleName], -1) 25 | return getattr(pyside, moduleName) 26 | 27 | 28 | def _pyqt4_import_module(moduleName): 29 | pyside = __import__('PyQt4', globals(), locals(), [moduleName], -1) 30 | return getattr(pyside, moduleName) 31 | 32 | 33 | if USES_PYSIDE: 34 | import_module = _pyside_import_module 35 | 36 | Signal = QtCore.Signal 37 | Slot = QtCore.Slot 38 | Property = QtCore.Property 39 | else: 40 | import_module = _pyqt4_import_module 41 | 42 | Signal = QtCore.pyqtSignal 43 | Slot = QtCore.pyqtSlot 44 | Property = QtCore.pyqtProperty 45 | 46 | 47 | if __name__ == "__main__": 48 | pass 49 | 50 | -------------------------------------------------------------------------------- /util/qtpieboard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | 4 | from __future__ import division 5 | 6 | import os 7 | import warnings 8 | 9 | import qt_compat 10 | QtGui = qt_compat.import_module("QtGui") 11 | 12 | import qtpie 13 | 14 | 15 | class PieKeyboard(object): 16 | 17 | SLICE_CENTER = -1 18 | SLICE_NORTH = 0 19 | SLICE_NORTH_WEST = 1 20 | SLICE_WEST = 2 21 | SLICE_SOUTH_WEST = 3 22 | SLICE_SOUTH = 4 23 | SLICE_SOUTH_EAST = 5 24 | SLICE_EAST = 6 25 | SLICE_NORTH_EAST = 7 26 | 27 | MAX_ANGULAR_SLICES = 8 28 | 29 | SLICE_DIRECTIONS = [ 30 | SLICE_CENTER, 31 | SLICE_NORTH, 32 | SLICE_NORTH_WEST, 33 | SLICE_WEST, 34 | SLICE_SOUTH_WEST, 35 | SLICE_SOUTH, 36 | SLICE_SOUTH_EAST, 37 | SLICE_EAST, 38 | SLICE_NORTH_EAST, 39 | ] 40 | 41 | SLICE_DIRECTION_NAMES = [ 42 | "CENTER", 43 | "NORTH", 44 | "NORTH_WEST", 45 | "WEST", 46 | "SOUTH_WEST", 47 | "SOUTH", 48 | "SOUTH_EAST", 49 | "EAST", 50 | "NORTH_EAST", 51 | ] 52 | 53 | def __init__(self): 54 | self._layout = QtGui.QGridLayout() 55 | self._widget = QtGui.QWidget() 56 | self._widget.setLayout(self._layout) 57 | 58 | self.__cells = {} 59 | 60 | @property 61 | def toplevel(self): 62 | return self._widget 63 | 64 | def add_pie(self, row, column, pieButton): 65 | assert len(pieButton) == 8 66 | self._layout.addWidget(pieButton, row, column) 67 | self.__cells[(row, column)] = pieButton 68 | 69 | def get_pie(self, row, column): 70 | return self.__cells[(row, column)] 71 | 72 | 73 | class KeyboardModifier(object): 74 | 75 | def __init__(self, name): 76 | self.name = name 77 | self.lock = False 78 | self.once = False 79 | 80 | @property 81 | def isActive(self): 82 | return self.lock or self.once 83 | 84 | def on_toggle_lock(self, *args, **kwds): 85 | self.lock = not self.lock 86 | 87 | def on_toggle_once(self, *args, **kwds): 88 | self.once = not self.once 89 | 90 | def reset_once(self): 91 | self.once = False 92 | 93 | 94 | def parse_keyboard_data(text): 95 | return eval(text) 96 | 97 | 98 | def _enumerate_pie_slices(pieData, iconPaths): 99 | for direction, directionName in zip( 100 | PieKeyboard.SLICE_DIRECTIONS, PieKeyboard.SLICE_DIRECTION_NAMES 101 | ): 102 | if directionName in pieData: 103 | sliceData = pieData[directionName] 104 | 105 | action = QtGui.QAction(None) 106 | try: 107 | action.setText(sliceData["text"]) 108 | except KeyError: 109 | pass 110 | try: 111 | relativeIconPath = sliceData["path"] 112 | except KeyError: 113 | pass 114 | else: 115 | for iconPath in iconPaths: 116 | absIconPath = os.path.join(iconPath, relativeIconPath) 117 | if os.path.exists(absIconPath): 118 | action.setIcon(QtGui.QIcon(absIconPath)) 119 | break 120 | pieItem = qtpie.QActionPieItem(action) 121 | actionToken = sliceData["action"] 122 | else: 123 | pieItem = qtpie.PieFiling.NULL_CENTER 124 | actionToken = "" 125 | yield direction, pieItem, actionToken 126 | 127 | 128 | def load_keyboard(keyboardName, dataTree, keyboard, keyboardHandler, iconPaths): 129 | for (row, column), pieData in dataTree.iteritems(): 130 | pieItems = list(_enumerate_pie_slices(pieData, iconPaths)) 131 | assert pieItems[0][0] == PieKeyboard.SLICE_CENTER, pieItems[0] 132 | _, center, centerAction = pieItems.pop(0) 133 | 134 | pieButton = qtpie.QPieButton(center) 135 | pieButton.set_center(center) 136 | keyboardHandler.map_slice_action(center, centerAction) 137 | for direction, pieItem, action in pieItems: 138 | pieButton.insertItem(pieItem) 139 | keyboardHandler.map_slice_action(pieItem, action) 140 | keyboard.add_pie(row, column, pieButton) 141 | 142 | 143 | class KeyboardHandler(object): 144 | 145 | def __init__(self, keyhandler): 146 | self.__keyhandler = keyhandler 147 | self.__commandHandlers = {} 148 | self.__modifiers = {} 149 | self.__sliceActions = {} 150 | 151 | self.register_modifier("Shift") 152 | self.register_modifier("Super") 153 | self.register_modifier("Control") 154 | self.register_modifier("Alt") 155 | 156 | def register_command_handler(self, command, handler): 157 | # @todo Look into hooking these up directly to the pie actions 158 | self.__commandHandlers["[%s]" % command] = handler 159 | 160 | def unregister_command_handler(self, command): 161 | # @todo Look into hooking these up directly to the pie actions 162 | del self.__commandHandlers["[%s]" % command] 163 | 164 | def register_modifier(self, modifierName): 165 | mod = KeyboardModifier(modifierName) 166 | self.register_command_handler(modifierName, mod.on_toggle_lock) 167 | self.__modifiers["<%s>" % modifierName] = mod 168 | 169 | def unregister_modifier(self, modifierName): 170 | self.unregister_command_handler(modifierName) 171 | del self.__modifiers["<%s>" % modifierName] 172 | 173 | def map_slice_action(self, slice, action): 174 | callback = lambda: self(action) 175 | slice.action().triggered.connect(callback) 176 | self.__sliceActions[slice] = (action, callback) 177 | 178 | def __call__(self, action): 179 | activeModifiers = [ 180 | mod.name 181 | for mod in self.__modifiers.itervalues() 182 | if mod.isActive 183 | ] 184 | 185 | needResetOnce = False 186 | if action.startswith("[") and action.endswith("]"): 187 | commandName = action[1:-1] 188 | if action in self.__commandHandlers: 189 | self.__commandHandlers[action](commandName, activeModifiers) 190 | needResetOnce = True 191 | else: 192 | warnings.warn("Unknown command: [%s]" % commandName) 193 | elif action.startswith("<") and action.endswith(">"): 194 | modName = action[1:-1] 195 | for mod in self.__modifiers.itervalues(): 196 | if mod.name == modName: 197 | mod.on_toggle_once() 198 | break 199 | else: 200 | warnings.warn("Unknown modifier: <%s>" % modName) 201 | else: 202 | self.__keyhandler(action, activeModifiers) 203 | needResetOnce = True 204 | 205 | if needResetOnce: 206 | for mod in self.__modifiers.itervalues(): 207 | mod.reset_once() 208 | -------------------------------------------------------------------------------- /util/qui_utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import contextlib 3 | import datetime 4 | import logging 5 | 6 | import qt_compat 7 | QtCore = qt_compat.QtCore 8 | QtGui = qt_compat.import_module("QtGui") 9 | import qore_utils 10 | 11 | import misc 12 | 13 | 14 | _moduleLogger = logging.getLogger(__name__) 15 | 16 | 17 | class ErrorDisplay(object): 18 | 19 | _SENTINEL_ICON = QtGui.QIcon() 20 | 21 | def __init__(self, errorLog): 22 | self._errorLog = errorLog 23 | self._errorLog.messagePushed.connect(self._on_message_pushed) 24 | self._errorLog.messagePopped.connect(self._on_message_popped) 25 | 26 | self._icons = None 27 | self._severityLabel = QtGui.QLabel() 28 | self._severityLabel.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) 29 | 30 | self._message = QtGui.QLabel() 31 | self._message.setText("Boo") 32 | self._message.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) 33 | self._message.setWordWrap(True) 34 | 35 | self._closeLabel = None 36 | 37 | self._controlLayout = QtGui.QHBoxLayout() 38 | self._controlLayout.addWidget(self._severityLabel, 1, QtCore.Qt.AlignCenter) 39 | self._controlLayout.addWidget(self._message, 1000) 40 | 41 | self._widget = QtGui.QWidget() 42 | self._widget.setLayout(self._controlLayout) 43 | self._widget.hide() 44 | 45 | @property 46 | def toplevel(self): 47 | return self._widget 48 | 49 | def _show_error(self): 50 | if self._icons is None: 51 | self._icons = { 52 | qore_utils.QErrorMessage.LEVEL_BUSY: 53 | get_theme_icon( 54 | #("process-working", "view-refresh", "general_refresh", "gtk-refresh") 55 | ("view-refresh", "general_refresh", "gtk-refresh", ) 56 | ).pixmap(32, 32), 57 | qore_utils.QErrorMessage.LEVEL_WARNING: 58 | get_theme_icon( 59 | ("dialog-warning", ) 60 | ).pixmap(32, 32), 61 | qore_utils.QErrorMessage.LEVEL_INFO: 62 | get_theme_icon( 63 | ("dialog-information", "general_notes", "gtk-info") 64 | ).pixmap(32, 32), 65 | qore_utils.QErrorMessage.LEVEL_ERROR: 66 | get_theme_icon( 67 | ("dialog-error", "app_install_error", "gtk-dialog-error") 68 | ).pixmap(32, 32), 69 | } 70 | if self._closeLabel is None: 71 | closeIcon = get_theme_icon(("window-close", "general_close", "gtk-close"), self._SENTINEL_ICON) 72 | if closeIcon is not self._SENTINEL_ICON: 73 | self._closeLabel = QtGui.QPushButton(closeIcon, "") 74 | else: 75 | self._closeLabel = QtGui.QPushButton("X") 76 | self._closeLabel.clicked.connect(self._on_close) 77 | self._controlLayout.addWidget(self._closeLabel, 1, QtCore.Qt.AlignCenter) 78 | error = self._errorLog.peek_message() 79 | self._message.setText(error.message) 80 | self._severityLabel.setPixmap(self._icons[error.level]) 81 | self._widget.show() 82 | 83 | @qt_compat.Slot() 84 | @qt_compat.Slot(bool) 85 | @misc.log_exception(_moduleLogger) 86 | def _on_close(self, checked = False): 87 | self._errorLog.pop() 88 | 89 | @qt_compat.Slot() 90 | @misc.log_exception(_moduleLogger) 91 | def _on_message_pushed(self): 92 | self._show_error() 93 | 94 | @qt_compat.Slot() 95 | @misc.log_exception(_moduleLogger) 96 | def _on_message_popped(self): 97 | if len(self._errorLog) == 0: 98 | self._message.setText("") 99 | self._widget.hide() 100 | else: 101 | self._show_error() 102 | 103 | 104 | class QHtmlDelegate(QtGui.QStyledItemDelegate): 105 | 106 | UNDEFINED_SIZE = -1 107 | 108 | def __init__(self, *args, **kwd): 109 | QtGui.QStyledItemDelegate.__init__(*((self, ) + args), **kwd) 110 | self._width = self.UNDEFINED_SIZE 111 | 112 | def paint(self, painter, option, index): 113 | newOption = QtGui.QStyleOptionViewItemV4(option) 114 | self.initStyleOption(newOption, index) 115 | if newOption.widget is not None: 116 | style = newOption.widget.style() 117 | else: 118 | style = QtGui.QApplication.style() 119 | 120 | doc = QtGui.QTextDocument() 121 | doc.setHtml(newOption.text) 122 | doc.setTextWidth(newOption.rect.width()) 123 | 124 | newOption.text = "" 125 | style.drawControl(QtGui.QStyle.CE_ItemViewItem, newOption, painter) 126 | 127 | ctx = QtGui.QAbstractTextDocumentLayout.PaintContext() 128 | if newOption.state & QtGui.QStyle.State_Selected: 129 | ctx.palette.setColor( 130 | QtGui.QPalette.Text, 131 | newOption.palette.color( 132 | QtGui.QPalette.Active, 133 | QtGui.QPalette.HighlightedText 134 | ) 135 | ) 136 | else: 137 | ctx.palette.setColor( 138 | QtGui.QPalette.Text, 139 | newOption.palette.color( 140 | QtGui.QPalette.Active, 141 | QtGui.QPalette.Text 142 | ) 143 | ) 144 | 145 | textRect = style.subElementRect(QtGui.QStyle.SE_ItemViewItemText, newOption) 146 | painter.save() 147 | painter.translate(textRect.topLeft()) 148 | painter.setClipRect(textRect.translated(-textRect.topLeft())) 149 | doc.documentLayout().draw(painter, ctx) 150 | painter.restore() 151 | 152 | def setWidth(self, width, model): 153 | if self._width == width: 154 | return 155 | self._width = width 156 | for c in xrange(model.rowCount()): 157 | cItem = model.item(c, 0) 158 | for r in xrange(model.rowCount()): 159 | rItem = cItem.child(r, 0) 160 | rIndex = model.indexFromItem(rItem) 161 | self.sizeHintChanged.emit(rIndex) 162 | return 163 | 164 | def sizeHint(self, option, index): 165 | newOption = QtGui.QStyleOptionViewItemV4(option) 166 | self.initStyleOption(newOption, index) 167 | 168 | doc = QtGui.QTextDocument() 169 | doc.setHtml(newOption.text) 170 | if self._width != self.UNDEFINED_SIZE: 171 | width = self._width 172 | else: 173 | width = newOption.rect.width() 174 | doc.setTextWidth(width) 175 | size = QtCore.QSize(doc.idealWidth(), doc.size().height()) 176 | return size 177 | 178 | 179 | class QSignalingMainWindow(QtGui.QMainWindow): 180 | 181 | closed = qt_compat.Signal() 182 | hidden = qt_compat.Signal() 183 | shown = qt_compat.Signal() 184 | resized = qt_compat.Signal() 185 | 186 | def __init__(self, *args, **kwd): 187 | QtGui.QMainWindow.__init__(*((self, )+args), **kwd) 188 | 189 | def closeEvent(self, event): 190 | val = QtGui.QMainWindow.closeEvent(self, event) 191 | self.closed.emit() 192 | return val 193 | 194 | def hideEvent(self, event): 195 | val = QtGui.QMainWindow.hideEvent(self, event) 196 | self.hidden.emit() 197 | return val 198 | 199 | def showEvent(self, event): 200 | val = QtGui.QMainWindow.showEvent(self, event) 201 | self.shown.emit() 202 | return val 203 | 204 | def resizeEvent(self, event): 205 | val = QtGui.QMainWindow.resizeEvent(self, event) 206 | self.resized.emit() 207 | return val 208 | 209 | def set_current_index(selector, itemText, default = 0): 210 | for i in xrange(selector.count()): 211 | if selector.itemText(i) == itemText: 212 | selector.setCurrentIndex(i) 213 | break 214 | else: 215 | itemText.setCurrentIndex(default) 216 | 217 | 218 | def _null_set_stackable(window, isStackable): 219 | pass 220 | 221 | 222 | def _maemo_set_stackable(window, isStackable): 223 | window.setAttribute(QtCore.Qt.WA_Maemo5StackedWindow, isStackable) 224 | 225 | 226 | try: 227 | QtCore.Qt.WA_Maemo5StackedWindow 228 | set_stackable = _maemo_set_stackable 229 | except AttributeError: 230 | set_stackable = _null_set_stackable 231 | 232 | 233 | def _null_set_autorient(window, doAutoOrient): 234 | pass 235 | 236 | 237 | def _maemo_set_autorient(window, doAutoOrient): 238 | window.setAttribute(QtCore.Qt.WA_Maemo5AutoOrientation, doAutoOrient) 239 | 240 | 241 | try: 242 | QtCore.Qt.WA_Maemo5AutoOrientation 243 | set_autorient = _maemo_set_autorient 244 | except AttributeError: 245 | set_autorient = _null_set_autorient 246 | 247 | 248 | def screen_orientation(): 249 | geom = QtGui.QApplication.desktop().screenGeometry() 250 | if geom.width() <= geom.height(): 251 | return QtCore.Qt.Vertical 252 | else: 253 | return QtCore.Qt.Horizontal 254 | 255 | 256 | def _null_set_window_orientation(window, orientation): 257 | pass 258 | 259 | 260 | def _maemo_set_window_orientation(window, orientation): 261 | if orientation == QtCore.Qt.Vertical: 262 | window.setAttribute(QtCore.Qt.WA_Maemo5LandscapeOrientation, False) 263 | window.setAttribute(QtCore.Qt.WA_Maemo5PortraitOrientation, True) 264 | elif orientation == QtCore.Qt.Horizontal: 265 | window.setAttribute(QtCore.Qt.WA_Maemo5LandscapeOrientation, True) 266 | window.setAttribute(QtCore.Qt.WA_Maemo5PortraitOrientation, False) 267 | elif orientation is None: 268 | window.setAttribute(QtCore.Qt.WA_Maemo5LandscapeOrientation, False) 269 | window.setAttribute(QtCore.Qt.WA_Maemo5PortraitOrientation, False) 270 | else: 271 | raise RuntimeError("Unknown orientation: %r" % orientation) 272 | 273 | 274 | try: 275 | QtCore.Qt.WA_Maemo5LandscapeOrientation 276 | QtCore.Qt.WA_Maemo5PortraitOrientation 277 | set_window_orientation = _maemo_set_window_orientation 278 | except AttributeError: 279 | set_window_orientation = _null_set_window_orientation 280 | 281 | 282 | def _null_show_progress_indicator(window, isStackable): 283 | pass 284 | 285 | 286 | def _maemo_show_progress_indicator(window, isStackable): 287 | window.setAttribute(QtCore.Qt.WA_Maemo5ShowProgressIndicator, isStackable) 288 | 289 | 290 | try: 291 | QtCore.Qt.WA_Maemo5ShowProgressIndicator 292 | show_progress_indicator = _maemo_show_progress_indicator 293 | except AttributeError: 294 | show_progress_indicator = _null_show_progress_indicator 295 | 296 | 297 | def _null_mark_numbers_preferred(widget): 298 | pass 299 | 300 | 301 | def _newqt_mark_numbers_preferred(widget): 302 | widget.setInputMethodHints(QtCore.Qt.ImhPreferNumbers) 303 | 304 | 305 | try: 306 | QtCore.Qt.ImhPreferNumbers 307 | mark_numbers_preferred = _newqt_mark_numbers_preferred 308 | except AttributeError: 309 | mark_numbers_preferred = _null_mark_numbers_preferred 310 | 311 | 312 | def _null_get_theme_icon(iconNames, fallback = None): 313 | icon = fallback if fallback is not None else QtGui.QIcon() 314 | return icon 315 | 316 | 317 | def _newqt_get_theme_icon(iconNames, fallback = None): 318 | for iconName in iconNames: 319 | if QtGui.QIcon.hasThemeIcon(iconName): 320 | icon = QtGui.QIcon.fromTheme(iconName) 321 | break 322 | else: 323 | icon = fallback if fallback is not None else QtGui.QIcon() 324 | return icon 325 | 326 | 327 | try: 328 | QtGui.QIcon.fromTheme 329 | get_theme_icon = _newqt_get_theme_icon 330 | except AttributeError: 331 | get_theme_icon = _null_get_theme_icon 332 | 333 | -------------------------------------------------------------------------------- /util/qwrappers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from __future__ import with_statement 4 | from __future__ import division 5 | 6 | import logging 7 | 8 | import qt_compat 9 | QtCore = qt_compat.QtCore 10 | QtGui = qt_compat.import_module("QtGui") 11 | 12 | import qore_utils 13 | import qui_utils 14 | import misc as misc_utils 15 | import linux as linux_utils 16 | 17 | 18 | _moduleLogger = logging.getLogger(__name__) 19 | 20 | 21 | class ApplicationWrapper(object): 22 | 23 | DEFAULT_ORIENTATION = "Default" 24 | AUTO_ORIENTATION = "Auto" 25 | LANDSCAPE_ORIENTATION = "Landscape" 26 | PORTRAIT_ORIENTATION = "Portrait" 27 | 28 | def __init__(self, qapp, constants): 29 | self._constants = constants 30 | self._qapp = qapp 31 | self._clipboard = QtGui.QApplication.clipboard() 32 | 33 | self._errorLog = qore_utils.QErrorLog() 34 | self._mainWindow = None 35 | 36 | self._fullscreenAction = QtGui.QAction(None) 37 | self._fullscreenAction.setText("Fullscreen") 38 | self._fullscreenAction.setCheckable(True) 39 | self._fullscreenAction.setShortcut(QtGui.QKeySequence("CTRL+Enter")) 40 | self._fullscreenAction.toggled.connect(self._on_toggle_fullscreen) 41 | 42 | self._orientation = self.DEFAULT_ORIENTATION 43 | self._orientationAction = QtGui.QAction(None) 44 | self._orientationAction.setText("Next Orientation") 45 | self._orientationAction.setCheckable(True) 46 | self._orientationAction.setShortcut(QtGui.QKeySequence("CTRL+o")) 47 | self._orientationAction.triggered.connect(self._on_next_orientation) 48 | 49 | self._logAction = QtGui.QAction(None) 50 | self._logAction.setText("Log") 51 | self._logAction.setShortcut(QtGui.QKeySequence("CTRL+l")) 52 | self._logAction.triggered.connect(self._on_log) 53 | 54 | self._quitAction = QtGui.QAction(None) 55 | self._quitAction.setText("Quit") 56 | self._quitAction.setShortcut(QtGui.QKeySequence("CTRL+q")) 57 | self._quitAction.triggered.connect(self._on_quit) 58 | 59 | self._aboutAction = QtGui.QAction(None) 60 | self._aboutAction.setText("About") 61 | self._aboutAction.triggered.connect(self._on_about) 62 | 63 | self._qapp.lastWindowClosed.connect(self._on_app_quit) 64 | self._mainWindow = self._new_main_window() 65 | self._mainWindow.window.destroyed.connect(self._on_child_close) 66 | 67 | self.load_settings() 68 | 69 | self._mainWindow.show() 70 | self._idleDelay = QtCore.QTimer() 71 | self._idleDelay.setSingleShot(True) 72 | self._idleDelay.setInterval(0) 73 | self._idleDelay.timeout.connect(self._on_delayed_start) 74 | self._idleDelay.start() 75 | 76 | def load_settings(self): 77 | raise NotImplementedError("Booh") 78 | 79 | def save_settings(self): 80 | raise NotImplementedError("Booh") 81 | 82 | def _new_main_window(self): 83 | raise NotImplementedError("Booh") 84 | 85 | @property 86 | def qapp(self): 87 | return self._qapp 88 | 89 | @property 90 | def constants(self): 91 | return self._constants 92 | 93 | @property 94 | def errorLog(self): 95 | return self._errorLog 96 | 97 | @property 98 | def fullscreenAction(self): 99 | return self._fullscreenAction 100 | 101 | @property 102 | def orientationAction(self): 103 | return self._orientationAction 104 | 105 | @property 106 | def orientation(self): 107 | return self._orientation 108 | 109 | @property 110 | def logAction(self): 111 | return self._logAction 112 | 113 | @property 114 | def aboutAction(self): 115 | return self._aboutAction 116 | 117 | @property 118 | def quitAction(self): 119 | return self._quitAction 120 | 121 | def set_orientation(self, orientation): 122 | self._orientation = orientation 123 | self._mainWindow.update_orientation(self._orientation) 124 | 125 | @classmethod 126 | def _next_orientation(cls, current): 127 | return { 128 | cls.DEFAULT_ORIENTATION: cls.AUTO_ORIENTATION, 129 | cls.AUTO_ORIENTATION: cls.LANDSCAPE_ORIENTATION, 130 | cls.LANDSCAPE_ORIENTATION: cls.PORTRAIT_ORIENTATION, 131 | cls.PORTRAIT_ORIENTATION: cls.DEFAULT_ORIENTATION, 132 | }[current] 133 | 134 | def _close_windows(self): 135 | if self._mainWindow is not None: 136 | self.save_settings() 137 | self._mainWindow.window.destroyed.disconnect(self._on_child_close) 138 | self._mainWindow.close() 139 | self._mainWindow = None 140 | 141 | @misc_utils.log_exception(_moduleLogger) 142 | def _on_delayed_start(self): 143 | self._mainWindow.start() 144 | 145 | @misc_utils.log_exception(_moduleLogger) 146 | def _on_app_quit(self, checked = False): 147 | if self._mainWindow is not None: 148 | self.save_settings() 149 | self._mainWindow.destroy() 150 | 151 | @misc_utils.log_exception(_moduleLogger) 152 | def _on_child_close(self, obj = None): 153 | if self._mainWindow is not None: 154 | self.save_settings() 155 | self._mainWindow = None 156 | 157 | @misc_utils.log_exception(_moduleLogger) 158 | def _on_toggle_fullscreen(self, checked = False): 159 | with qui_utils.notify_error(self._errorLog): 160 | self._mainWindow.set_fullscreen(checked) 161 | 162 | @misc_utils.log_exception(_moduleLogger) 163 | def _on_next_orientation(self, checked = False): 164 | with qui_utils.notify_error(self._errorLog): 165 | self.set_orientation(self._next_orientation(self._orientation)) 166 | 167 | @misc_utils.log_exception(_moduleLogger) 168 | def _on_about(self, checked = True): 169 | raise NotImplementedError("Booh") 170 | 171 | @misc_utils.log_exception(_moduleLogger) 172 | def _on_log(self, checked = False): 173 | with qui_utils.notify_error(self._errorLog): 174 | logPath = linux_utils.get_resource_path( 175 | "cache", self._constants.__app_name__, "%s.log" % self._constants.__app_name__ 176 | ) 177 | with open(logPath, "r") as f: 178 | logLines = f.xreadlines() 179 | log = "".join(logLines) 180 | self._clipboard.setText(log) 181 | 182 | @misc_utils.log_exception(_moduleLogger) 183 | def _on_quit(self, checked = False): 184 | with qui_utils.notify_error(self._errorLog): 185 | self._close_windows() 186 | 187 | 188 | class WindowWrapper(object): 189 | 190 | def __init__(self, parent, app): 191 | self._app = app 192 | 193 | self._errorDisplay = qui_utils.ErrorDisplay(self._app.errorLog) 194 | 195 | self._layout = QtGui.QBoxLayout(QtGui.QBoxLayout.LeftToRight) 196 | self._layout.setContentsMargins(0, 0, 0, 0) 197 | 198 | self._superLayout = QtGui.QVBoxLayout() 199 | self._superLayout.addWidget(self._errorDisplay.toplevel) 200 | self._superLayout.setContentsMargins(0, 0, 0, 0) 201 | self._superLayout.addLayout(self._layout) 202 | 203 | centralWidget = QtGui.QWidget() 204 | centralWidget.setLayout(self._superLayout) 205 | centralWidget.setContentsMargins(0, 0, 0, 0) 206 | 207 | self._window = qui_utils.QSignalingMainWindow(parent) 208 | self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) 209 | qui_utils.set_stackable(self._window, True) 210 | self._window.setCentralWidget(centralWidget) 211 | 212 | self._closeWindowAction = QtGui.QAction(None) 213 | self._closeWindowAction.setText("Close") 214 | self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w")) 215 | self._closeWindowAction.triggered.connect(self._on_close_window) 216 | 217 | self._window.addAction(self._closeWindowAction) 218 | self._window.addAction(self._app.quitAction) 219 | self._window.addAction(self._app.fullscreenAction) 220 | self._window.addAction(self._app.orientationAction) 221 | self._window.addAction(self._app.logAction) 222 | 223 | @property 224 | def window(self): 225 | return self._window 226 | 227 | @property 228 | def windowOrientation(self): 229 | geom = self._window.size() 230 | if geom.width() <= geom.height(): 231 | return QtCore.Qt.Vertical 232 | else: 233 | return QtCore.Qt.Horizontal 234 | 235 | @property 236 | def idealWindowOrientation(self): 237 | if self._app.orientation == self._app.AUTO_ORIENTATION: 238 | windowOrientation = self.windowOrientation 239 | elif self._app.orientation == self._app.DEFAULT_ORIENTATION: 240 | windowOrientation = qui_utils.screen_orientation() 241 | elif self._app.orientation == self._app.LANDSCAPE_ORIENTATION: 242 | windowOrientation = QtCore.Qt.Horizontal 243 | elif self._app.orientation == self._app.PORTRAIT_ORIENTATION: 244 | windowOrientation = QtCore.Qt.Vertical 245 | else: 246 | raise RuntimeError("Bad! No %r for you" % self._app.orientation) 247 | return windowOrientation 248 | 249 | def walk_children(self): 250 | return () 251 | 252 | def start(self): 253 | pass 254 | 255 | def close(self): 256 | for child in self.walk_children(): 257 | child.window.destroyed.disconnect(self._on_child_close) 258 | child.close() 259 | self._window.close() 260 | 261 | def destroy(self): 262 | pass 263 | 264 | def show(self): 265 | self._window.show() 266 | for child in self.walk_children(): 267 | child.show() 268 | self.set_fullscreen(self._app.fullscreenAction.isChecked()) 269 | 270 | def hide(self): 271 | for child in self.walk_children(): 272 | child.hide() 273 | self._window.hide() 274 | 275 | def set_fullscreen(self, isFullscreen): 276 | if self._window.isVisible(): 277 | if isFullscreen: 278 | self._window.showFullScreen() 279 | else: 280 | self._window.showNormal() 281 | for child in self.walk_children(): 282 | child.set_fullscreen(isFullscreen) 283 | 284 | def update_orientation(self, orientation): 285 | if orientation == self._app.DEFAULT_ORIENTATION: 286 | qui_utils.set_autorient(self.window, False) 287 | qui_utils.set_window_orientation(self.window, None) 288 | elif orientation == self._app.AUTO_ORIENTATION: 289 | qui_utils.set_autorient(self.window, True) 290 | qui_utils.set_window_orientation(self.window, None) 291 | elif orientation == self._app.LANDSCAPE_ORIENTATION: 292 | qui_utils.set_autorient(self.window, False) 293 | qui_utils.set_window_orientation(self.window, QtCore.Qt.Horizontal) 294 | elif orientation == self._app.PORTRAIT_ORIENTATION: 295 | qui_utils.set_autorient(self.window, False) 296 | qui_utils.set_window_orientation(self.window, QtCore.Qt.Vertical) 297 | else: 298 | raise RuntimeError("Unknown orientation: %r" % orientation) 299 | for child in self.walk_children(): 300 | child.update_orientation(orientation) 301 | 302 | @misc_utils.log_exception(_moduleLogger) 303 | def _on_child_close(self, obj = None): 304 | raise NotImplementedError("Booh") 305 | 306 | @misc_utils.log_exception(_moduleLogger) 307 | def _on_close_window(self, checked = True): 308 | with qui_utils.notify_error(self._errorLog): 309 | self.close() 310 | 311 | 312 | class AutoFreezeWindowFeature(object): 313 | 314 | def __init__(self, app, window): 315 | self._app = app 316 | self._window = window 317 | self._app.qapp.focusChanged.connect(self._on_focus_changed) 318 | if self._app.qapp.focusWidget() is not None: 319 | self._window.setUpdatesEnabled(True) 320 | else: 321 | self._window.setUpdatesEnabled(False) 322 | 323 | def close(self): 324 | self._app.qapp.focusChanged.disconnect(self._on_focus_changed) 325 | self._window.setUpdatesEnabled(True) 326 | 327 | @misc_utils.log_exception(_moduleLogger) 328 | def _on_focus_changed(self, oldWindow, newWindow): 329 | with qui_utils.notify_error(self._app.errorLog): 330 | if oldWindow is None and newWindow is not None: 331 | self._window.setUpdatesEnabled(True) 332 | elif oldWindow is not None and newWindow is None: 333 | self._window.setUpdatesEnabled(False) 334 | -------------------------------------------------------------------------------- /util/time_utils.py: -------------------------------------------------------------------------------- 1 | from datetime import tzinfo, timedelta, datetime 2 | 3 | ZERO = timedelta(0) 4 | HOUR = timedelta(hours=1) 5 | 6 | 7 | def first_sunday_on_or_after(dt): 8 | days_to_go = 6 - dt.weekday() 9 | if days_to_go: 10 | dt += timedelta(days_to_go) 11 | return dt 12 | 13 | 14 | # US DST Rules 15 | # 16 | # This is a simplified (i.e., wrong for a few cases) set of rules for US 17 | # DST start and end times. For a complete and up-to-date set of DST rules 18 | # and timezone definitions, visit the Olson Database (or try pytz): 19 | # http://www.twinsun.com/tz/tz-link.htm 20 | # http://sourceforge.net/projects/pytz/ (might not be up-to-date) 21 | # 22 | # In the US, since 2007, DST starts at 2am (standard time) on the second 23 | # Sunday in March, which is the first Sunday on or after Mar 8. 24 | DSTSTART_2007 = datetime(1, 3, 8, 2) 25 | # and ends at 2am (DST time; 1am standard time) on the first Sunday of Nov. 26 | DSTEND_2007 = datetime(1, 11, 1, 1) 27 | # From 1987 to 2006, DST used to start at 2am (standard time) on the first 28 | # Sunday in April and to end at 2am (DST time; 1am standard time) on the last 29 | # Sunday of October, which is the first Sunday on or after Oct 25. 30 | DSTSTART_1987_2006 = datetime(1, 4, 1, 2) 31 | DSTEND_1987_2006 = datetime(1, 10, 25, 1) 32 | # From 1967 to 1986, DST used to start at 2am (standard time) on the last 33 | # Sunday in April (the one on or after April 24) and to end at 2am (DST time; 34 | # 1am standard time) on the last Sunday of October, which is the first Sunday 35 | # on or after Oct 25. 36 | DSTSTART_1967_1986 = datetime(1, 4, 24, 2) 37 | DSTEND_1967_1986 = DSTEND_1987_2006 38 | 39 | 40 | class USTimeZone(tzinfo): 41 | 42 | def __init__(self, hours, reprname, stdname, dstname): 43 | self.stdoffset = timedelta(hours=hours) 44 | self.reprname = reprname 45 | self.stdname = stdname 46 | self.dstname = dstname 47 | 48 | def __repr__(self): 49 | return self.reprname 50 | 51 | def tzname(self, dt): 52 | if self.dst(dt): 53 | return self.dstname 54 | else: 55 | return self.stdname 56 | 57 | def utcoffset(self, dt): 58 | return self.stdoffset + self.dst(dt) 59 | 60 | def dst(self, dt): 61 | if dt is None or dt.tzinfo is None: 62 | # An exception may be sensible here, in one or both cases. 63 | # It depends on how you want to treat them. The default 64 | # fromutc() implementation (called by the default astimezone() 65 | # implementation) passes a datetime with dt.tzinfo is self. 66 | return ZERO 67 | assert dt.tzinfo is self 68 | 69 | # Find start and end times for US DST. For years before 1967, return 70 | # ZERO for no DST. 71 | if 2006 < dt.year: 72 | dststart, dstend = DSTSTART_2007, DSTEND_2007 73 | elif 1986 < dt.year < 2007: 74 | dststart, dstend = DSTSTART_1987_2006, DSTEND_1987_2006 75 | elif 1966 < dt.year < 1987: 76 | dststart, dstend = DSTSTART_1967_1986, DSTEND_1967_1986 77 | else: 78 | return ZERO 79 | 80 | start = first_sunday_on_or_after(dststart.replace(year=dt.year)) 81 | end = first_sunday_on_or_after(dstend.replace(year=dt.year)) 82 | 83 | # Can't compare naive to aware objects, so strip the timezone from 84 | # dt first. 85 | if start <= dt.replace(tzinfo=None) < end: 86 | return HOUR 87 | else: 88 | return ZERO 89 | 90 | 91 | Eastern = USTimeZone(-5, "Eastern", "EST", "EDT") 92 | Central = USTimeZone(-6, "Central", "CST", "CDT") 93 | Mountain = USTimeZone(-7, "Mountain", "MST", "MDT") 94 | Pacific = USTimeZone(-8, "Pacific", "PST", "PDT") 95 | -------------------------------------------------------------------------------- /util/tp_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import logging 4 | 5 | import dbus 6 | import telepathy 7 | 8 | import util.go_utils as gobject_utils 9 | import misc 10 | 11 | 12 | _moduleLogger = logging.getLogger(__name__) 13 | DBUS_PROPERTIES = 'org.freedesktop.DBus.Properties' 14 | 15 | 16 | class WasMissedCall(object): 17 | 18 | def __init__(self, bus, conn, chan, on_success, on_error): 19 | self.__on_success = on_success 20 | self.__on_error = on_error 21 | 22 | self._requested = None 23 | self._didMembersChange = False 24 | self._didClose = False 25 | self._didReport = False 26 | 27 | self._onTimeout = gobject_utils.Timeout(self._on_timeout) 28 | self._onTimeout.start(seconds=60) 29 | 30 | chan[telepathy.interfaces.CHANNEL_INTERFACE_GROUP].connect_to_signal( 31 | "MembersChanged", 32 | self._on_members_changed, 33 | ) 34 | 35 | chan[telepathy.interfaces.CHANNEL].connect_to_signal( 36 | "Closed", 37 | self._on_closed, 38 | ) 39 | 40 | chan[DBUS_PROPERTIES].GetAll( 41 | telepathy.interfaces.CHANNEL_INTERFACE, 42 | reply_handler = self._on_got_all, 43 | error_handler = self._on_error, 44 | ) 45 | 46 | def cancel(self): 47 | self._report_error("by request") 48 | 49 | def _report_missed_if_ready(self): 50 | if self._didReport: 51 | pass 52 | elif self._requested is not None and (self._didMembersChange or self._didClose): 53 | if self._requested: 54 | self._report_error("wrong direction") 55 | elif self._didClose: 56 | self._report_success() 57 | else: 58 | self._report_error("members added") 59 | else: 60 | if self._didClose: 61 | self._report_error("closed too early") 62 | 63 | def _report_success(self): 64 | assert not self._didReport, "Double reporting a missed call" 65 | self._didReport = True 66 | self._onTimeout.cancel() 67 | self.__on_success(self) 68 | 69 | def _report_error(self, reason): 70 | assert not self._didReport, "Double reporting a missed call" 71 | self._didReport = True 72 | self._onTimeout.cancel() 73 | self.__on_error(self, reason) 74 | 75 | @misc.log_exception(_moduleLogger) 76 | def _on_got_all(self, properties): 77 | self._requested = properties["Requested"] 78 | self._report_missed_if_ready() 79 | 80 | @misc.log_exception(_moduleLogger) 81 | def _on_members_changed(self, message, added, removed, lp, rp, actor, reason): 82 | if added: 83 | self._didMembersChange = True 84 | self._report_missed_if_ready() 85 | 86 | @misc.log_exception(_moduleLogger) 87 | def _on_closed(self): 88 | self._didClose = True 89 | self._report_missed_if_ready() 90 | 91 | @misc.log_exception(_moduleLogger) 92 | def _on_error(self, *args): 93 | self._report_error(args) 94 | 95 | @misc.log_exception(_moduleLogger) 96 | def _on_timeout(self): 97 | self._report_error("timeout") 98 | return False 99 | 100 | 101 | class NewChannelSignaller(object): 102 | 103 | def __init__(self, on_new_channel): 104 | self._sessionBus = dbus.SessionBus() 105 | self._on_user_new_channel = on_new_channel 106 | 107 | def start(self): 108 | self._sessionBus.add_signal_receiver( 109 | self._on_new_channel, 110 | "NewChannel", 111 | "org.freedesktop.Telepathy.Connection", 112 | None, 113 | None 114 | ) 115 | 116 | def stop(self): 117 | self._sessionBus.remove_signal_receiver( 118 | self._on_new_channel, 119 | "NewChannel", 120 | "org.freedesktop.Telepathy.Connection", 121 | None, 122 | None 123 | ) 124 | 125 | @misc.log_exception(_moduleLogger) 126 | def _on_new_channel( 127 | self, channelObjectPath, channelType, handleType, handle, supressHandler 128 | ): 129 | connObjectPath = channel_path_to_conn_path(channelObjectPath) 130 | serviceName = path_to_service_name(channelObjectPath) 131 | try: 132 | self._on_user_new_channel( 133 | self._sessionBus, serviceName, connObjectPath, channelObjectPath, channelType 134 | ) 135 | except Exception: 136 | _moduleLogger.exception("Blocking exception from being passed up") 137 | 138 | 139 | class EnableSystemContactIntegration(object): 140 | 141 | ACCOUNT_MGR_NAME = "org.freedesktop.Telepathy.AccountManager" 142 | ACCOUNT_MGR_PATH = "/org/freedesktop/Telepathy/AccountManager" 143 | ACCOUNT_MGR_IFACE_QUERY = "com.nokia.AccountManager.Interface.Query" 144 | ACCOUNT_IFACE_COMPAT = "com.nokia.Account.Interface.Compat" 145 | ACCOUNT_IFACE_COMPAT_PROFILE = "com.nokia.Account.Interface.Compat.Profile" 146 | DBUS_PROPERTIES = 'org.freedesktop.DBus.Properties' 147 | 148 | def __init__(self, profileName): 149 | self._bus = dbus.SessionBus() 150 | self._profileName = profileName 151 | 152 | def start(self): 153 | self._accountManager = self._bus.get_object( 154 | self.ACCOUNT_MGR_NAME, 155 | self.ACCOUNT_MGR_PATH, 156 | ) 157 | self._accountManagerQuery = dbus.Interface( 158 | self._accountManager, 159 | dbus_interface=self.ACCOUNT_MGR_IFACE_QUERY, 160 | ) 161 | 162 | self._accountManagerQuery.FindAccounts( 163 | { 164 | self.ACCOUNT_IFACE_COMPAT_PROFILE: self._profileName, 165 | }, 166 | reply_handler = self._on_found_accounts_reply, 167 | error_handler = self._on_error, 168 | ) 169 | 170 | @misc.log_exception(_moduleLogger) 171 | def _on_found_accounts_reply(self, accountObjectPaths): 172 | for accountObjectPath in accountObjectPaths: 173 | print accountObjectPath 174 | account = self._bus.get_object( 175 | self.ACCOUNT_MGR_NAME, 176 | accountObjectPath, 177 | ) 178 | accountProperties = dbus.Interface( 179 | account, 180 | self.DBUS_PROPERTIES, 181 | ) 182 | accountProperties.Set( 183 | self.ACCOUNT_IFACE_COMPAT, 184 | "SecondaryVCardFields", 185 | ["TEL"], 186 | reply_handler = self._on_field_set, 187 | error_handler = self._on_error, 188 | ) 189 | 190 | @misc.log_exception(_moduleLogger) 191 | def _on_field_set(self): 192 | _moduleLogger.debug("SecondaryVCardFields Set") 193 | 194 | @misc.log_exception(_moduleLogger) 195 | def _on_error(self, error): 196 | _moduleLogger.error("%r" % (error, )) 197 | 198 | 199 | def channel_path_to_conn_path(channelObjectPath): 200 | """ 201 | >>> channel_path_to_conn_path("/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME/Channel1") 202 | '/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME' 203 | """ 204 | return channelObjectPath.rsplit("/", 1)[0] 205 | 206 | 207 | def path_to_service_name(path): 208 | """ 209 | >>> path_to_service_name("/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME/Channel1") 210 | 'org.freedesktop.Telepathy.ConnectionManager.theonering.gv.USERNAME' 211 | """ 212 | return ".".join(path[1:].split("/")[0:7]) 213 | 214 | 215 | def cm_from_path(path): 216 | """ 217 | >>> cm_from_path("/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME/Channel1") 218 | 'theonering' 219 | """ 220 | return path[1:].split("/")[4] 221 | --------------------------------------------------------------------------------