├── .gitignore ├── Bag1.py ├── Bag2.py ├── Bag3.py ├── Command.py ├── Feed.py ├── Hyphenate1.py ├── Hyphenate2 ├── Hyphenate.pyx ├── __init__.py ├── chyphenate.pxd └── setup.py ├── Image ├── Png.py ├── Xbm.py ├── Xpm.py └── __init__.py ├── Meter.py ├── MeterLogin.py ├── MeterMT.py ├── Qtrac.py ├── README.txt ├── Scale ├── Fast.pyx ├── Slow.py ├── __init__.py └── setup.py ├── Session.py ├── TkUtil ├── About.py ├── Dialog.py ├── Dock.py ├── DockManager.py ├── ListBox.py ├── Scrollbar.py ├── Settings.py ├── TextEdit.py ├── Tooltip.py ├── TreeView.py ├── __init__.py └── images │ ├── DockLeft_16x16.gif │ ├── DockRight_16x16.gif │ ├── Hide_16x16.gif │ └── Undock_16x16.gif ├── Unpack.py ├── barchart1.py ├── barchart2.py ├── barchart3.py ├── benchmark_Scale.py ├── currency ├── Main.py ├── Rates.py ├── currency.pyw └── images │ ├── icon_16x16.gif │ ├── icon_16x16.ico │ ├── icon_16x16.png │ ├── icon_32x32.gif │ └── icon_32x32.png ├── cyImage ├── Globals.py ├── Image.pyx ├── Xbm.pyx ├── Xpm.pyx ├── _Scale.pyx ├── __init__.py └── setup.py ├── cylinder1.pyw ├── cylinder2.pyw ├── cylinder_16x16.png ├── cylinder_32x32.png ├── diagram1.py ├── diagram2.py ├── eventhandler1.py ├── eventhandler2.py ├── formbuilder.py ├── gameboard1.py ├── gameboard2.py ├── gameboard3.py ├── gameboard4.py ├── genome1.py ├── genome2.py ├── genome3.py ├── gpl-3.0.txt ├── gravitate ├── About.py ├── Board.py ├── Globals.py ├── Help.py ├── Main.py ├── Preferences.py ├── gravitate.pyw └── images │ ├── About_16x16.gif │ ├── Close_16x16.gif │ ├── Help_16x16.gif │ ├── New_16x16.gif │ ├── Preferences_16x16.gif │ ├── icon_16x16.gif │ ├── icon_16x16.ico │ ├── icon_32x32.gif │ └── icon_32x32.png ├── gravitate2 ├── About.py ├── Board.py ├── GameOver.py ├── Globals.py ├── Help.py ├── Main.py ├── Options.py ├── Preferences.py ├── Shapes.py ├── gravitate.pyw └── images │ ├── About_16x16.gif │ ├── Circle_16x16.gif │ ├── Close_16x16.gif │ ├── Help_16x16.gif │ ├── Hexagon_16x16.gif │ ├── New_16x16.gif │ ├── Octagon_16x16.gif │ ├── Preferences_16x16.gif │ ├── Shape_16x16.gif │ ├── Spiral_16x16.gif │ ├── Square_16x16.gif │ ├── icon_16x16.gif │ ├── icon_16x16.ico │ ├── icon_32x32.gif │ └── icon_32x32.png ├── gravitate3d.pyw ├── grid.py ├── hello.pyw ├── hyph_de_DE.dic ├── hyph_en_US.dic ├── hyph_fr_FR.dic ├── imageproxy1.py ├── imageproxy2.py ├── imagescale-c.py ├── imagescale-m.py ├── imagescale-q-m.py ├── imagescale-s.py ├── imagescale-t.py ├── imagescale ├── About.py ├── Globals.py ├── ImageScale.py ├── Main.py ├── images │ ├── icon_16x16.gif │ ├── icon_32x32.gif │ ├── icon_32x32.ico │ └── icon_32x32.png └── imagescale.pyw ├── mediator1.py ├── mediator1d.py ├── mediator2.py ├── mediator2d.py ├── meter-rpc.pyw ├── meter-rpyc.pyw ├── meter_16x16.gif ├── meter_32x32.gif ├── meter_32x32.ico ├── meter_32x32.png ├── meterclient-rpc.py ├── meterclient-rpyc.py ├── meterserver-rpc.py ├── meterserver-rpyc.py ├── multiplexer1.py ├── multiplexer2.py ├── observer.py ├── pointstore1.py ├── pointstore2.py ├── render1.py ├── render2.py ├── stationery1.py ├── stationery2.py ├── tabulator1.py ├── tabulator2.py ├── tabulator3.py ├── tabulator4.py ├── texteditor ├── About.py ├── Editor.py ├── Find.py ├── Globals.py ├── Main.py ├── Options.py ├── Preferences.py ├── images │ ├── About_16x16.gif │ ├── AlignCenter_16x16.gif │ ├── AlignCenter_24x24.gif │ ├── AlignLeft_16x16.gif │ ├── AlignLeft_24x24.gif │ ├── AlignRight_16x16.gif │ ├── AlignRight_24x24.gif │ ├── Bold_16x16.gif │ ├── Bold_24x24.gif │ ├── Copy_16x16.gif │ ├── Copy_24x24.gif │ ├── Cut_16x16.gif │ ├── Cut_24x24.gif │ ├── Extend_16x16.gif │ ├── Find_16x16.gif │ ├── Find_24x24.gif │ ├── Help_16x16.gif │ ├── Italic_16x16.gif │ ├── Italic_24x24.gif │ ├── New_16x16.gif │ ├── New_24x24.gif │ ├── Open_16x16.gif │ ├── Open_24x24.gif │ ├── Paste_16x16.gif │ ├── Paste_24x24.gif │ ├── Preferences_16x16.gif │ ├── Preferences_24x24.gif │ ├── Quit_16x16.gif │ ├── Redo_16x16.gif │ ├── Redo_24x24.gif │ ├── SaveAs_16x16.gif │ ├── Save_16x16.gif │ ├── Save_24x24.gif │ ├── ToolbarMenu_3x24.gif │ ├── Undo_16x16.gif │ ├── Undo_24x24.gif │ ├── Unextend_16x16.gif │ ├── icon.png │ ├── icon_16x16.gif │ ├── icon_16x16.ico │ └── icon_32x32.gif └── texteditor.pyw ├── texteditor2 ├── About.py ├── Colors.py ├── Display.py ├── Editor.py ├── Find.py ├── Globals.py ├── Main.py ├── Options.py ├── Preferences.py ├── images │ ├── About_16x16.gif │ ├── AlignCenter_16x16.gif │ ├── AlignCenter_24x24.gif │ ├── AlignLeft_16x16.gif │ ├── AlignLeft_24x24.gif │ ├── AlignRight_16x16.gif │ ├── AlignRight_24x24.gif │ ├── Bold_16x16.gif │ ├── Bold_24x24.gif │ ├── Copy_16x16.gif │ ├── Copy_24x24.gif │ ├── Cut_16x16.gif │ ├── Cut_24x24.gif │ ├── Extend_16x16.gif │ ├── Find_16x16.gif │ ├── Find_24x24.gif │ ├── Help_16x16.gif │ ├── Italic_16x16.gif │ ├── Italic_24x24.gif │ ├── New_16x16.gif │ ├── New_24x24.gif │ ├── Open_16x16.gif │ ├── Open_24x24.gif │ ├── Paste_16x16.gif │ ├── Paste_24x24.gif │ ├── Preferences_16x16.gif │ ├── Preferences_24x24.gif │ ├── Quit_16x16.gif │ ├── Redo_16x16.gif │ ├── Redo_24x24.gif │ ├── SaveAs_16x16.gif │ ├── Save_16x16.gif │ ├── Save_24x24.gif │ ├── ToolbarMenu_3x24.gif │ ├── Undo_16x16.gif │ ├── Undo_24x24.gif │ ├── Unextend_16x16.gif │ ├── drag.cur │ ├── drag.gif │ ├── drag.png │ ├── drag.xbm │ ├── drag_mask.xbm │ ├── icon.png │ ├── icon_16x16.gif │ ├── icon_16x16.ico │ └── icon_32x32.gif └── texteditor.pyw ├── tilefall_16x16.png ├── tilefall_32x32.png ├── validate1.py ├── validate2.py ├── whatsnew-c.py ├── whatsnew-m.py ├── whatsnew-q-m.py ├── whatsnew-q.py ├── whatsnew-t.py ├── whatsnew.dat ├── whatsnew.py ├── wordcount1.py └── wordcount2.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | -------------------------------------------------------------------------------- /Command.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import sys 13 | 14 | 15 | if sys.version_info[:2] < (3, 3): 16 | import collections 17 | def callable(function): 18 | return isinstance(function, collections.Callable) 19 | 20 | 21 | class Command: 22 | 23 | def __init__(self, do, undo, description=""): 24 | assert callable(do) and callable(undo) 25 | self.do = do 26 | self.undo = undo 27 | self.description = description 28 | 29 | 30 | def __call__(self): 31 | self.do() 32 | 33 | 34 | class Macro: 35 | 36 | def __init__(self, description=""): 37 | self.description = description 38 | self.__commands = [] 39 | 40 | 41 | def add(self, command): 42 | if not isinstance(command, Command): 43 | raise TypeError("Expected object of type Command, got {}". 44 | format(type(command).__name__)) 45 | self.__commands.append(command) 46 | 47 | 48 | def __call__(self): 49 | for command in self.__commands: 50 | command() 51 | 52 | do = __call__ 53 | 54 | 55 | def undo(self): 56 | for command in reversed(self.__commands): 57 | command.undo() 58 | -------------------------------------------------------------------------------- /Hyphenate2/Hyphenate.pyx: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # cython: language_level=3 3 | # Copyright © 2012 Qtrac Ltd. All rights reserved. 4 | # This program or module is free software: you can redistribute it 5 | # and/or modify it under the terms of the GNU General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. It is provided for 8 | # educational purposes and is distributed in the hope that it will be 9 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | 13 | """ 14 | Typical usage: 15 | 16 | import os 17 | import Hyphenate 18 | 19 | # Locate your hyph*.dic files 20 | path = "/usr/share/hyph_dic" 21 | usHyphDic = os.path.join(path, "hyph_en_US.dic") 22 | deHyphDic = os.path.join(path, "hyph_de_DE.dic") 23 | 24 | # Create wrappers so you don't have to keep specifying the dictionary 25 | hyphenate = lambda word: Hyphenate.hyphenate(word, usHyphDic) 26 | hyphenate_de = lambda word: Hyphenate.hyphenate(word, deHyphDic) 27 | 28 | # Use your wrappers 29 | print(hyphenate("extraordinary")) # prints: ex-traor-di-nary 30 | print(hyphenate_de("außergewöhnlich")) # prints: außerge-wöhn-lich 31 | """ 32 | 33 | import atexit 34 | cimport chyphenate 35 | cimport cpython.pycapsule as pycapsule 36 | #from libc.stdio cimport printf # Helpful for debugging pointers 37 | 38 | 39 | class Error(Exception): pass 40 | 41 | 42 | _hdictForFilename = {} 43 | 44 | 45 | def hyphenate(str word, str filename, str hyphen="-"): 46 | """Pass a word to hyphenate and a hyphenation file to use, e.g., 47 | hyph_en_US.dic (including its full path), and optionally the 48 | hyphenation character to use. 49 | 50 | Each hyphenation dictionary file is only loaded the first time it is 51 | needed; after that it is reused. 52 | """ 53 | cdef chyphenate.HyphenDict *hdict = _get_hdict(filename) 54 | cdef bytes bword = word.encode("utf-8") 55 | cdef int word_size = len(bword) 56 | cdef bytes hyphens = b"\x00" * (word_size + 5) 57 | cdef bytes hyphenated_word = b"\x00" * (word_size * 2) 58 | cdef char **rep = NULL 59 | cdef int *pos = NULL 60 | cdef int *cut = NULL 61 | cdef int failed = chyphenate.hnj_hyphen_hyphenate2(hdict, bword, 62 | word_size, hyphens, hyphenated_word, &rep, &pos, &cut) 63 | if failed: 64 | raise Error("hyphenation failed for '{}'".format(word)) 65 | end = hyphenated_word.find(b"\x00") 66 | return hyphenated_word[:end].decode("utf-8").replace("=", hyphen) 67 | 68 | 69 | cdef chyphenate.HyphenDict *_get_hdict( 70 | str filename) except NULL: 71 | cdef bytes bfilename = filename.encode("utf-8") 72 | cdef chyphenate.HyphenDict *hdict = NULL 73 | if bfilename not in _hdictForFilename: 74 | hdict = chyphenate.hnj_hyphen_load(bfilename) 75 | #printf("%p\n", hdict) 76 | if hdict == NULL: 77 | raise Error("failed to load '{}'".format(filename)) 78 | _hdictForFilename[bfilename] = pycapsule.PyCapsule_New( 79 | hdict, NULL, NULL) 80 | # Setting a destructor didn't work 81 | capsule = _hdictForFilename.get(bfilename) 82 | if not pycapsule.PyCapsule_IsValid(capsule, NULL): 83 | raise Error("failed to load '{}'".format(filename)) 84 | return pycapsule.PyCapsule_GetPointer(capsule, 85 | NULL) 86 | 87 | 88 | def _cleanup(): 89 | cdef chyphenate.HyphenDict *hdict = NULL 90 | for capsule in _hdictForFilename.values(): 91 | if pycapsule.PyCapsule_IsValid(capsule, NULL): 92 | hdict = ( 93 | pycapsule.PyCapsule_GetPointer(capsule, NULL)) 94 | if hdict != NULL: 95 | chyphenate.hnj_hyphen_free(hdict) 96 | 97 | 98 | atexit.register(_cleanup) 99 | -------------------------------------------------------------------------------- /Hyphenate2/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | # Build with: python3 setup.py build_ext --inplace 13 | 14 | from Hyphenate2.Hyphenate import hyphenate, Error 15 | -------------------------------------------------------------------------------- /Hyphenate2/chyphenate.pxd: -------------------------------------------------------------------------------- 1 | # Copyright © 2012 Qtrac Ltd. All rights reserved. 2 | # This program or module is free software: you can redistribute it 3 | # and/or modify it under the terms of the GNU General Public License as 4 | # published by the Free Software Foundation, either version 3 of the 5 | # License, or (at your option) any later version. It is provided for 6 | # educational purposes and is distributed in the hope that it will be 7 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | 11 | 12 | cdef extern from "hyphen.h": 13 | ctypedef struct HyphenDict: 14 | pass 15 | 16 | HyphenDict *hnj_hyphen_load(char *filename) 17 | void hnj_hyphen_free(HyphenDict *hdict) 18 | int hnj_hyphen_hyphenate2(HyphenDict *hdict, char *word, 19 | int word_size, char *hyphens, char *hyphenated_word, 20 | char ***rep, int **pos, int **cut) 21 | -------------------------------------------------------------------------------- /Hyphenate2/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | # Build with: python3 setup.py build_ext --inplace 13 | 14 | import distutils.core 15 | import distutils.extension 16 | import Cython.Distutils 17 | 18 | 19 | distutils.core.setup(name="Hyphenate2", 20 | cmdclass={"build_ext": Cython.Distutils.build_ext}, 21 | ext_modules=[distutils.extension.Extension("Hyphenate", 22 | ["Hyphenate.pyx"], libraries=["hyphen"])]) 23 | -------------------------------------------------------------------------------- /Image/Png.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | """ 13 | import Image 14 | Use the above rather than importing this module explicitly. This works 15 | because Image imports any modules it finds (to allow for new image 16 | processing modules to be added post-facto). 17 | 18 | This Image plugin module can read and write .png files if PyPNG is 19 | installed. See http://pypi.python.org/pypi/pypng 20 | """ 21 | 22 | import os 23 | import sys 24 | import warnings 25 | import Image 26 | # These are due to issues with the png module 27 | if sys.version_info[:2] >= (3, 2): 28 | warnings.simplefilter("ignore", ResourceWarning) 29 | warnings.simplefilter("ignore", DeprecationWarning) 30 | 31 | try: 32 | import png 33 | except ImportError: 34 | png = None 35 | 36 | 37 | def can_load(filename): 38 | """Returns 100 if this module can do a lossless load, 0 if it can't 39 | load the file, and something inbetween if it can do a lossy load.""" 40 | return (80 if png is not None and 41 | os.path.splitext(filename)[1].lower() == ".png" else 0) 42 | 43 | 44 | def can_save(filename): 45 | """Returns 100 if this module can do a lossless save, 0 if it can't 46 | save the file, and something inbetween if it can do a lossy save.""" 47 | return can_load(filename) 48 | 49 | 50 | if png is not None: 51 | def load(image, filename): 52 | """load a PNG file""" 53 | reader = png.Reader(filename=filename) 54 | image.width, image.height, pixels, _ = reader.asRGBA8() 55 | image.pixels = Image.create_array(image.width, image.height) 56 | index = 0 57 | for row in pixels: 58 | for r, g, b, α in zip(row[::4], row[1::4], row[2::4], row[3::4]): 59 | image.pixels[index] = Image.color_for_argb(α, r, g, b) 60 | index += 1 61 | 62 | 63 | def save(image, filename): 64 | """save a PNG file""" 65 | with open(filename, "wb") as file: 66 | writer = png.Writer(width=image.width, height=image.height, 67 | alpha=True) 68 | writer.write_array(file, list(_rgba_for_pixels(image.pixels))) 69 | 70 | 71 | def _rgba_for_pixels(pixels): 72 | for color in pixels: 73 | α, r, g, b = Image.argb_for_color(color) 74 | for component in (r, g, b, α): 75 | yield component 76 | -------------------------------------------------------------------------------- /Qtrac.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import abc 13 | import collections 14 | import errno 15 | import functools 16 | import os 17 | import sys 18 | 19 | 20 | def coroutine(function): 21 | @functools.wraps(function) 22 | def wrapper(*args, **kwargs): 23 | generator = function(*args, **kwargs) 24 | next(generator) 25 | return generator 26 | return wrapper 27 | 28 | 29 | if sys.version_info[:2] < (3, 3): 30 | def remove_if_exists(filename): 31 | try: 32 | os.remove(filename) 33 | except OSError as err: 34 | if err.errno != errno.ENOENT: 35 | raise 36 | else: 37 | def remove_if_exists(filename): 38 | try: 39 | os.remove(filename) 40 | except FileNotFoundError: 41 | pass # All other exceptions are passed to the caller 42 | 43 | 44 | # Thanks to Nick Coghlan for these! 45 | if sys.version_info[:2] >= (3, 3): 46 | def has_methods(*methods): 47 | def decorator(Base): 48 | def __subclasshook__(Class, Subclass): 49 | if Class is Base: 50 | attributes = collections.ChainMap(*(Superclass.__dict__ 51 | for Superclass in Subclass.__mro__)) 52 | if all(method in attributes for method in methods): 53 | return True 54 | return NotImplemented 55 | Base.__subclasshook__ = classmethod(__subclasshook__) 56 | return Base 57 | return decorator 58 | else: 59 | def has_methods(*methods): 60 | def decorator(Base): 61 | def __subclasshook__(Class, Subclass): 62 | if Class is Base: 63 | needed = set(methods) 64 | for Superclass in Subclass.__mro__: 65 | for meth in needed.copy(): 66 | if meth in Superclass.__dict__: 67 | needed.discard(meth) 68 | if not needed: 69 | return True 70 | return NotImplemented 71 | Base.__subclasshook__ = classmethod(__subclasshook__) 72 | return Base 73 | return decorator 74 | 75 | 76 | # Thanks to Nick Coghlan for this! 77 | class Requirer(metaclass=abc.ABCMeta): 78 | 79 | # Since we have rules for adding new expected attributes, we *do* 80 | # perform the check for subclasses 81 | @classmethod 82 | def __subclasshook__(Class, Subclass): 83 | methods = set() 84 | for Superclass in Subclass.__mro__: 85 | if hasattr(Superclass, "required_methods"): 86 | methods |= set(Superclass.required_methods) 87 | attributes = collections.ChainMap(*(Superclass.__dict__ 88 | for Superclass in Class.__mro__)) 89 | if all(method in attributes for method in methods): 90 | return True 91 | return NotImplemented 92 | 93 | 94 | def report(message="", error=False): 95 | if len(message) >= 70 and not error: 96 | message = message[:67] + "..." 97 | sys.stdout.write("\r{:70}{}".format(message, "\n" if error else "")) 98 | sys.stdout.flush() 99 | -------------------------------------------------------------------------------- /Scale/Fast.pyx: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # cython: language_level=3 3 | # Copyright © 2012 Qtrac Ltd. All rights reserved. 4 | # This program or module is free software: you can redistribute it 5 | # and/or modify it under the terms of the GNU General Public License as 6 | # published by the Free Software Foundation, either version 3 of the 7 | # License, or (at your option) any later version. It is provided for 8 | # educational purposes and is distributed in the hope that it will be 9 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | 13 | # Throughout, pixels and newPixels are really of type 14 | # numpy.ndarray[_DTYPE_t] but using a memory view is almost 4x faster 15 | 16 | from libc.math cimport round 17 | import numpy 18 | cimport numpy 19 | cimport cython 20 | 21 | 22 | # See: http://docs.cython.org/src/tutorial/numpy.html 23 | _DTYPE = numpy.uint32 24 | ctypedef numpy.uint32_t _DTYPE_t 25 | 26 | cdef struct Argb: 27 | int alpha 28 | int red 29 | int green 30 | int blue 31 | 32 | DEF MAX_COMPONENT = 0xFF 33 | 34 | 35 | @cython.boundscheck(False) 36 | def scale(_DTYPE_t[:] pixels, int width, int height, double ratio): 37 | """returns a smoothly scaled copy of this image 38 | 39 | ratio is how much to scale by, e.g., 0.75 means reduce width and 40 | height to ¾ their original size, 0.5 to half (making the image ¼ 41 | of the original size), and so on. 42 | """ 43 | assert 0 < ratio < 1 44 | cdef int rows = round(height * ratio) 45 | cdef int columns = round(width * ratio) 46 | cdef _DTYPE_t[:] newPixels = numpy.zeros(rows * columns, dtype=_DTYPE) 47 | cdef double yStep = height / rows 48 | cdef double xStep = width / columns 49 | cdef int index = 0 50 | cdef int row, column, y0, y1, x0, x1 51 | for row in range(rows): 52 | y0 = round(row * yStep) 53 | y1 = round(y0 + yStep) 54 | for column in range(columns): 55 | x0 = round(column * xStep) 56 | x1 = round(x0 + xStep) 57 | newPixels[index] = _mean(pixels, width, height, x0, y0, x1, y1) 58 | index += 1 59 | return columns, newPixels 60 | 61 | 62 | @cython.cdivision(True) 63 | @cython.boundscheck(False) 64 | cdef _DTYPE_t _mean(_DTYPE_t[:] pixels, int width, int height, int x0, 65 | int y0, int x1, int y1): 66 | cdef int alphaTotal = 0 67 | cdef int redTotal = 0 68 | cdef int greenTotal = 0 69 | cdef int blueTotal = 0 70 | cdef int count = 0 71 | cdef int y, x, offset 72 | cdef Argb argb 73 | for y in range(y0, y1): 74 | if y >= height: 75 | break 76 | offset = y * width 77 | for x in range(x0, x1): 78 | if x >= width: 79 | break 80 | argb = _argb_for_color(pixels[offset + x]) 81 | alphaTotal += argb.alpha 82 | redTotal += argb.red 83 | greenTotal += argb.green 84 | blueTotal += argb.blue 85 | count += 1 86 | cdef int a = round(alphaTotal / count) 87 | cdef int r = round(redTotal / count) 88 | cdef int g = round(greenTotal / count) 89 | cdef int b = round(blueTotal / count) 90 | return _color_for_argb(a, r, g, b) 91 | 92 | 93 | cdef inline Argb _argb_for_color(_DTYPE_t color): 94 | """returns an ARGB quadruple for a color specified as a numpy.uint32""" 95 | return Argb((color >> 24) & MAX_COMPONENT, 96 | (color >> 16) & MAX_COMPONENT, (color >> 8) & MAX_COMPONENT, 97 | (color & MAX_COMPONENT)) 98 | 99 | 100 | cdef inline _DTYPE_t _color_for_argb(int a, int r, int g, int b): 101 | """returns a numpy.uint32 representing the given ARGB values""" 102 | return (((a & MAX_COMPONENT) << 24) | ((r & MAX_COMPONENT) << 16) | 103 | ((g & MAX_COMPONENT) << 8) | (b & MAX_COMPONENT)) 104 | -------------------------------------------------------------------------------- /Scale/Slow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import numpy 13 | 14 | 15 | MAX_COMPONENT = 0xFF 16 | 17 | 18 | def scale(pixels, width, height, ratio): 19 | """returns a smoothly scaled copy of this image 20 | 21 | ratio is how much to scale by, e.g., 0.75 means reduce width and 22 | height to ¾ their original size, 0.5 to half (making the image ¼ 23 | of the original size), and so on. 24 | 25 | Scaling is slow but produces good results even for text; 26 | subsample() is faster. 27 | """ 28 | assert 0 < ratio < 1 29 | rows = round(height * ratio) 30 | columns = round(width * ratio) 31 | newPixels = numpy.zeros(rows * columns, dtype=numpy.uint32) 32 | yStep = height / rows 33 | xStep = width / columns 34 | index = 0 35 | for row in range(rows): 36 | y0 = round(row * yStep) 37 | y1 = round(y0 + yStep) 38 | for column in range(columns): 39 | x0 = round(column * xStep) 40 | x1 = round(x0 + xStep) 41 | newPixels[index] = _mean(pixels, width, height, x0, y0, x1, y1) 42 | index += 1 43 | return columns, newPixels 44 | 45 | 46 | def _mean(pixels, width, height, x0, y0, x1, y1): 47 | alphaTotal, redTotal, greenTotal, blueTotal, count = 0, 0, 0, 0, 0 48 | for y in range(y0, y1): 49 | if y >= height: 50 | break 51 | offset = y * width # Compute this per row rather than per pixel 52 | for x in range(x0, x1): 53 | if x >= width: 54 | break 55 | a, r, g, b = _argb_for_color(pixels[offset + x]) 56 | alphaTotal += a 57 | redTotal += r 58 | greenTotal += g 59 | blueTotal += b 60 | count += 1 61 | a = int(round(alphaTotal / count)) 62 | r = int(round(redTotal / count)) 63 | g = int(round(greenTotal / count)) 64 | b = int(round(blueTotal / count)) 65 | return _color_for_argb(a, r, g, b) 66 | 67 | 68 | # color is always a numpy.uint32 69 | def _argb_for_color(color): 70 | color = int(color) 71 | return ((color >> 24) & MAX_COMPONENT, (color >> 16) & MAX_COMPONENT, 72 | (color >> 8) & MAX_COMPONENT, (color & MAX_COMPONENT)) 73 | 74 | 75 | # We don't need to check that the values are in range because they come 76 | # from a numpy.uint32 77 | def _color_for_argb(a, r, g, b): 78 | return (((a & MAX_COMPONENT) << 24) | ((r & MAX_COMPONENT) << 16) | 79 | ((g & MAX_COMPONENT) << 8) | (b & MAX_COMPONENT)) 80 | -------------------------------------------------------------------------------- /Scale/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | from Scale.Slow import scale as scale_slow 13 | from Scale.Scale.Fast import scale as scale_fast 14 | -------------------------------------------------------------------------------- /Scale/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | # Build with: python3 setup.py build_ext --inplace 13 | 14 | import distutils.core 15 | import numpy 16 | import Cython.Build 17 | 18 | 19 | distutils.core.setup(name="Scale.Fast", 20 | include_dirs=[numpy.get_include()], 21 | ext_modules=Cython.Build.cythonize("Fast.pyx")) 22 | -------------------------------------------------------------------------------- /Session.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import os 13 | import random 14 | import sys 15 | try: 16 | import ssl 17 | except ImportError: 18 | ssl = None 19 | 20 | 21 | _private = {} 22 | _sessionIDkey = 0 23 | 24 | 25 | def id(): 26 | return _private[_sessionIDkey] 27 | 28 | 29 | def reset(size=11): 30 | if ssl is not None and sys.version_info[:2] >= (3, 3): 31 | sessionId = ssl.RAND_bytes(size) 32 | else: 33 | try: 34 | sessionId = os.urandom(size) 35 | except NotImplementedError: 36 | sessionId = "" 37 | while len(sessionId) < size: 38 | sessionId += str(random.random())[2:] # Skip leading "0." 39 | sessionId = sessionId[:size].encode("utf-8") 40 | _private[_sessionIDkey] = sessionId 41 | return sessionId 42 | 43 | 44 | reset() 45 | 46 | 47 | if __name__ == "__main__": 48 | sessionId = id() 49 | print(len(sessionId), type(sessionId)) 50 | print(sessionId) 51 | -------------------------------------------------------------------------------- /TkUtil/About.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import sys 13 | import webbrowser 14 | import tkinter as tk 15 | if __name__ == "__main__": # For stand-alone testing with parallel TkUtil 16 | import os 17 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 18 | ".."))) 19 | import TkUtil.Dialog 20 | import TkUtil.TextEdit 21 | 22 | 23 | class Window(TkUtil.Dialog.Dialog): 24 | 25 | def __init__(self, master, appname, width=60, height=20): 26 | self.appname = appname 27 | self.width = width 28 | self.height = height 29 | super().__init__(master, "About — {}".format(appname)) 30 | 31 | 32 | def body(self, master): 33 | self.editor = TkUtil.TextEdit.TextEdit(master, takefocus=False, 34 | exportselection=False, width=self.width, 35 | height=self.height, undo=False, wrap=tk.WORD, relief=None, 36 | borderwidth=0, setgrid=True) 37 | self.text = self.editor.text 38 | self.create_tags() 39 | self.populate_text() 40 | self.text.config(state=tk.DISABLED) 41 | self.editor.pack(fill=tk.BOTH, expand=True) 42 | return self.editor 43 | 44 | 45 | def create_tags(self): 46 | self.text.tag_config("center", justify=tk.CENTER) 47 | self.text.tag_config("url", underline=True) 48 | self.text.tag_bind("url", "", self.handle_url) 49 | 50 | 51 | def add_lines(self, lines): 52 | self.text.insert(tk.END, "\n") 53 | self.text.insert(tk.END, lines.replace("\n", " ").strip()) 54 | self.text.insert(tk.END, "\n") 55 | 56 | 57 | def populate_text(self): 58 | "Override" 59 | self.text.insert(tk.END, "[Override populate_text()]") 60 | 61 | 62 | def handle_url(self, event): 63 | index = self.text.index("@{0.x},{0.y}".format(event)) 64 | indexes = self.text.tag_prevrange("url", index) 65 | url = self.text.get(*indexes).strip() 66 | if url: 67 | HTTP = "http://" 68 | if not url.startswith(HTTP): 69 | url = HTTP + url 70 | webbrowser.open_new_tab(url) 71 | 72 | 73 | if __name__ == "__main__": 74 | if sys.stdout.isatty(): 75 | application = tk.Tk() 76 | Window(application, "About") 77 | application.bind("", lambda *args: application.quit()) 78 | application.mainloop() 79 | else: 80 | print("Loaded OK") 81 | -------------------------------------------------------------------------------- /TkUtil/DockManager.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | """ 13 | A class that keeps track of dock windows to ensure that they are 14 | shown/hidden as appropriate. 15 | 16 | Assumes that areas are gridded (and then immediately removed) and that 17 | dock windows are packed within them. 18 | 19 | Dock windows *must* provide an on_close method, _should_ provide a 20 | title property, and _may_ provide a minsize 2-tuple property. 21 | """ 22 | 23 | import os 24 | import sys 25 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 26 | ".."))) 27 | import tkinter as tk 28 | import TkUtil 29 | 30 | 31 | class DockManager: 32 | 33 | def __init__(self, left=None, top=None, bottom=None, right=None): 34 | self.left = left 35 | self.top = top 36 | self.bottom = bottom 37 | self.right = right 38 | self.docked = {left: [], top: [], bottom: [], right: []} 39 | self.xy_for_dock = {} # Use to undock to last undock position 40 | 41 | 42 | def dock(self, dock, area): 43 | if not self.__remove_area(dock): # Wasn't already docked 44 | self.xy_for_dock[dock] = (dock.winfo_rootx(), 45 | dock.winfo_rooty()) 46 | if not len(self.docked[area]): 47 | area.grid() 48 | self.docked[area].append(dock) 49 | dock.pack_forget() 50 | dock.tk.call("wm", "forget", dock) 51 | options = {"in": area, "padx": 2, "pady": 2, "fill": tk.X} 52 | dock.config(relief=tk.GROOVE, borderwidth=2) 53 | dock.pack(**options) 54 | 55 | 56 | def undock(self, dock, x=None, y=None): 57 | """Warning: On Mac OS X 10.5 undocking works imperfectly. 58 | Left and right docking work fine though. 59 | """ 60 | dock.pack_forget() 61 | dock.config(relief=tk.FLAT, borderwidth=0) 62 | dock.tk.call("wm", "manage", dock) 63 | on_close = dock.register(dock.on_close) 64 | dock.tk.call("wm", "protocol", dock, "WM_DELETE_WINDOW", on_close) 65 | title = dock.title if hasattr(dock, "title") else "Dock" 66 | dock.tk.call("wm", "title", dock, title) 67 | minsize = dock.minsize if hasattr(dock, "minsize") else (60, 30) 68 | dock.tk.call("wm", "minsize", dock, *minsize) 69 | dock.tk.call("wm", "resizable", dock, False, False) 70 | if TkUtil.windows(): 71 | dock.tk.call("wm", "attributes", dock, "-toolwindow", True) 72 | if x is not None and y is not None: 73 | self.xy_for_dock[dock] = (x, y) 74 | x, y = self.xy_for_dock.get(dock, (None, None)) 75 | if x is not None and y is not None: 76 | dock.tk.call("wm", "geometry", dock, "{:+}{:+}".format(x, y)) 77 | self.__remove_area(dock) 78 | 79 | 80 | def hide(self, dock): 81 | dock.pack_forget() 82 | dock.tk.call("wm", "forget", dock) 83 | self.__remove_area(dock) 84 | 85 | 86 | def __remove_area(self, dock): 87 | for area in self.docked: 88 | if dock in self.docked[area]: 89 | self.docked[area].remove(dock) 90 | if not len(self.docked[area]): 91 | area.grid_remove() 92 | return True 93 | return False 94 | 95 | 96 | if __name__ == "__main__": 97 | if not sys.stdout.isatty(): 98 | print("Loaded OK") 99 | -------------------------------------------------------------------------------- /TkUtil/ListBox.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import os 13 | import sys 14 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 15 | ".."))) 16 | import tkinter as tk 17 | import tkinter.ttk as ttk 18 | import TkUtil.Scrollbar 19 | 20 | 21 | class ListBox(ttk.Frame): 22 | """A scrollable tk.ListBox widget 23 | 24 | Note that the kwargs are given to the tk.ListBox not the outer 25 | ttk.Frame. (You can always configure the frame afterwards.) 26 | 27 | In general: 28 | listBox.method() or listBox.listbox.method() -> listBox.listbox.method() 29 | listBox.yscrollbar.method() -> listBox.yscrollbar.method() 30 | listBox.frame.method() -> listBox.method() 31 | Exceptions: private methods always go to the frame; methods that are 32 | in the frame (e.g., bind(), cget(), config() etc.), go to the frame, 33 | so for those use, say, listBox.listbox.config() etc. 34 | """ 35 | 36 | def __init__(self, master=None, **kwargs): 37 | super().__init__(master) 38 | self.frame = self 39 | self.listbox = tk.Listbox(self, **kwargs) 40 | self.xscrollbar = TkUtil.Scrollbar.Scrollbar(self, 41 | command=self.listbox.xview, orient=tk.HORIZONTAL) 42 | self.yscrollbar = TkUtil.Scrollbar.Scrollbar(self, 43 | command=self.listbox.yview, orient=tk.VERTICAL) 44 | self.listbox.configure(yscrollcommand=self.yscrollbar.set, 45 | xscrollcommand=self.xscrollbar.set) 46 | self.xscrollbar.grid(row=1, column=0, sticky=(tk.W, tk.E)) 47 | self.yscrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S)) 48 | self.listbox.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.W, tk.E)) 49 | self.grid_rowconfigure(0, weight=1) 50 | self.grid_columnconfigure(0, weight=1) 51 | 52 | 53 | def __getattr__(self, name): 54 | # This is only used if attribute lookup fails, so, e.g., 55 | # listBox.cget() will succeed (on the frame) without coming 56 | # here, but listBox.index() will fail (there is no 57 | # ttk.Frame.index method) and will come here. 58 | return getattr(self.listbox, name) 59 | 60 | 61 | if __name__ == "__main__": 62 | if sys.stdout.isatty(): 63 | application = tk.Tk() 64 | application.title("ListBox") 65 | listBox = ListBox(application) 66 | for i, x in enumerate(("One", "Two", "Three", "Four", "Five", 67 | "Six", "Seven", "Eight", "Nine", "Ten")): 68 | listBox.insert(i, x) 69 | listBox.pack(fill=tk.BOTH, expand=True) 70 | application.mainloop() 71 | else: 72 | print("Loaded OK") 73 | -------------------------------------------------------------------------------- /TkUtil/Scrollbar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | # 12 | # This module is based on Fredrik Lundh's code: 13 | # http://effbot.org/zone/tkinter-autoscrollbar.htm 14 | 15 | import tkinter.ttk as ttk 16 | 17 | 18 | class Scrollbar(ttk.Scrollbar): 19 | 20 | def set(self, first, last): 21 | if float(first) <= 0.0 and float(last) >= 1.0: 22 | self.grid_remove() 23 | else: 24 | self.grid() 25 | super().set(first, last) 26 | 27 | 28 | def pack(self, *args, **kwargs): 29 | raise NotImplementedError() 30 | 31 | 32 | def place(self, *args, **kwargs): 33 | raise NotImplementedError() 34 | 35 | 36 | if __name__ == "__main__": 37 | import sys 38 | if not sys.stdout.isatty(): 39 | print("Loaded OK") 40 | -------------------------------------------------------------------------------- /TkUtil/Settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import configparser 13 | import os 14 | import sys 15 | 16 | 17 | Data = None # This is set after load() has been called 18 | # DOMAIN and APPNAME must be populated before calling any functions 19 | DOMAIN = None 20 | APPNAME = None 21 | _SUFFIX = ".ini" 22 | 23 | 24 | class ConfigParser(configparser.ConfigParser): 25 | 26 | 27 | def get_str(self, section, option, default=None): 28 | if self.has_option(section, option): 29 | return self.get(section, option) 30 | return default 31 | 32 | 33 | def get_int(self, section, option, default=None): 34 | if self.has_option(section, option): 35 | return self.getint(section, option) 36 | return default 37 | 38 | 39 | def get_float(self, section, option, default=None): 40 | if self.has_option(section, option): 41 | return self.getfloat(section, option) 42 | return default 43 | 44 | 45 | def get_bool(self, section, option, default=None): 46 | if self.has_option(section, option): 47 | return self.getboolean(section, option) 48 | return default 49 | 50 | 51 | def put(self, section, option, value): 52 | if not self.has_section(section): 53 | self.add_section(section) 54 | self.set(section, option, str(value)) 55 | 56 | 57 | def load(): 58 | global Data 59 | Data = ConfigParser() 60 | filename = _load_filename() 61 | if filename is not None: 62 | Data.read(filename) 63 | return Data 64 | 65 | 66 | def save(): 67 | with open(_save_filename(), "w") as file: 68 | Data.write(file) 69 | 70 | 71 | def _load_filename(): 72 | assert APPNAME 73 | filename = APPNAME + _SUFFIX 74 | paths = _path_list() 75 | for path in paths: 76 | file = os.path.join(path, filename) 77 | if os.path.exists(file): 78 | return file 79 | # return None # No settings file exists 80 | 81 | 82 | def _save_filename(): 83 | assert APPNAME 84 | filename = APPNAME + _SUFFIX 85 | paths = _path_list() 86 | for path in paths: 87 | if os.path.exists(path): 88 | return os.path.join(path, filename) 89 | else: 90 | try: 91 | os.makedirs(path) 92 | return os.path.join(path, filename) 93 | except os.error: 94 | pass 95 | raise OSError("no valid path to save settings to") 96 | 97 | 98 | def _path_list(): 99 | assert DOMAIN 100 | paths = [] 101 | home = os.path.expanduser("~") 102 | if sys.platform.startswith("win"): 103 | path = os.path.expandvars("%APPDATA%") 104 | if "%" not in path: 105 | paths.append(path) 106 | path = os.path.expandvars("%COMMON_APPDATA%") 107 | if "%" not in path: 108 | paths.append(path) 109 | paths.append(r"\Users\Default\AppData\Local") 110 | paths.append(home) 111 | else: 112 | paths.append(home + "/.") 113 | path = os.path.join(home, ".config") 114 | paths.append(path) 115 | path = os.path.join(path, DOMAIN) 116 | paths.append(path) 117 | paths.reverse() 118 | return paths 119 | 120 | 121 | if __name__ == "__main__": 122 | DOMAIN = "Qtrac" 123 | APPNAME = "MyApp" 124 | print(_path_list()) 125 | print(_save_filename()) 126 | print(_load_filename()) 127 | -------------------------------------------------------------------------------- /TkUtil/TextEdit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | # 12 | # This module is a simplification and adaptation of the standard 13 | # library's ScrolledText module. 14 | 15 | import os 16 | import sys 17 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 18 | ".."))) 19 | import tkinter as tk 20 | import tkinter.ttk as ttk 21 | import TkUtil.Scrollbar 22 | 23 | 24 | class TextEdit(ttk.Frame): 25 | """A scrollable tk.Text widget 26 | 27 | Note that the kwargs are given to the tk.Text not the outer 28 | ttk.Frame. (You can always configure the frame afterwards.) 29 | 30 | In general: 31 | textEdit.method() or textEdit.text.method() -> textEdit.text.method() 32 | textEdit.yscrollbar.method() -> textEdit.yscrollbar.method() 33 | textEdit.frame.method() -> textEdit.method() 34 | Exceptions: private methods always go to the frame; methods that are 35 | in the frame (e.g., bind(), cget(), config() etc.), go to the frame, 36 | so for those use, say, textEdit.text.config() etc. 37 | """ 38 | 39 | def __init__(self, master=None, **kwargs): 40 | super().__init__(master) 41 | self.frame = self 42 | self.text = tk.Text(self, **kwargs) 43 | self.xscrollbar = TkUtil.Scrollbar.Scrollbar(self, 44 | command=self.text.xview, orient=tk.HORIZONTAL) 45 | self.yscrollbar = TkUtil.Scrollbar.Scrollbar(self, 46 | command=self.text.yview, orient=tk.VERTICAL) 47 | self.text.configure(yscrollcommand=self.yscrollbar.set, 48 | xscrollcommand=self.xscrollbar.set) 49 | self.xscrollbar.grid(row=1, column=0, sticky=(tk.W, tk.E)) 50 | self.yscrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S)) 51 | self.text.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.W, tk.E)) 52 | self.grid_rowconfigure(0, weight=1) 53 | self.grid_columnconfigure(0, weight=1) 54 | 55 | 56 | def __getattr__(self, name): 57 | # This is only used if attribute lookup fails, so, e.g., 58 | # textEdit.cget() will succeed (on the frame) without coming 59 | # here, but textEdit.index() will fail (there is no 60 | # ttk.Frame.index method) and will come here. 61 | return getattr(self.text, name) 62 | 63 | 64 | if __name__ == "__main__": 65 | if sys.stdout.isatty(): 66 | application = tk.Tk() 67 | application.title("TextEdit") 68 | textEdit = TextEdit(application, wrap=tk.NONE) 69 | textEdit.pack(fill=tk.BOTH, expand=True) 70 | def check(): 71 | textEdit.frame.config(borderwidth=2) 72 | print("frame", textEdit.frame.cget("borderwidth")) 73 | print("yscrollbar", textEdit.yscrollbar.fraction(5, 5)) 74 | textEdit.insert("end", 75 | "This is a test of the method delegation.\n" * 20) 76 | print("text", textEdit.text.index(tk.INSERT)) 77 | print("text", textEdit.index(tk.INSERT)) 78 | textEdit.text.focus() 79 | application.after(50, check) 80 | application.mainloop() 81 | else: 82 | print("Loaded OK") 83 | -------------------------------------------------------------------------------- /TkUtil/TreeView.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import os 13 | import sys 14 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 15 | ".."))) 16 | import tkinter as tk 17 | import tkinter.ttk as ttk 18 | import TkUtil.Scrollbar 19 | 20 | 21 | class TreeView(ttk.Frame): 22 | """A scrollable tk.TreeView widget 23 | 24 | Note that the kwargs are given to the tk.TreeView not the outer 25 | ttk.Frame. (You can always configure the frame afterwards.) 26 | 27 | To access methods use treeView.xscrollbar, treeView.yscrollbar, or 28 | treeView.treeview. 29 | """ 30 | 31 | def __init__(self, master=None, **kwargs): 32 | super().__init__(master) 33 | self.frame = self 34 | self.treeview = ttk.Treeview(self, **kwargs) 35 | self.xscrollbar = TkUtil.Scrollbar.Scrollbar(self, 36 | command=self.treeview.xview, orient=tk.HORIZONTAL) 37 | self.yscrollbar = TkUtil.Scrollbar.Scrollbar(self, 38 | command=self.treeview.yview, orient=tk.VERTICAL) 39 | self.treeview.configure(yscrollcommand=self.yscrollbar.set, 40 | xscrollcommand=self.xscrollbar.set) 41 | self.xscrollbar.grid(row=1, column=0, sticky=(tk.W, tk.E)) 42 | self.yscrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S)) 43 | self.treeview.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.W, tk.E)) 44 | self.grid_rowconfigure(0, weight=1) 45 | self.grid_columnconfigure(0, weight=1) 46 | 47 | 48 | if __name__ == "__main__": 49 | if sys.stdout.isatty(): 50 | application = tk.Tk() 51 | application.title("TreeView") 52 | treeView = TreeView(application) 53 | for i, x in enumerate(("One", "Two", "Three", "Four", "Five", 54 | "Six", "Seven", "Eight", "Nine", "Ten")): 55 | treeView.treeview.insert("", tk.END, str(i), text=x) 56 | treeView.treeview.insert(str(i), tk.END, text="child of {}" 57 | .format(x)) 58 | treeView.pack(fill=tk.BOTH, expand=True) 59 | application.mainloop() 60 | else: 61 | print("Loaded OK") 62 | -------------------------------------------------------------------------------- /TkUtil/images/DockLeft_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/TkUtil/images/DockLeft_16x16.gif -------------------------------------------------------------------------------- /TkUtil/images/DockRight_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/TkUtil/images/DockRight_16x16.gif -------------------------------------------------------------------------------- /TkUtil/images/Hide_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/TkUtil/images/Hide_16x16.gif -------------------------------------------------------------------------------- /TkUtil/images/Undock_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/TkUtil/images/Undock_16x16.gif -------------------------------------------------------------------------------- /currency/Rates.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import re 13 | import urllib.request 14 | 15 | 16 | _URL = "http://www.bankofcanada.ca/stats/assets/csv/fx-seven-day.csv" 17 | 18 | 19 | def get(refresh=False): 20 | if refresh: 21 | get.rates = {} 22 | if get.rates: 23 | return get.rates 24 | with urllib.request.urlopen(_URL) as file: 25 | for line in file: 26 | line = line.rstrip().decode("utf-8") 27 | if not line or line.startswith(("#", "Date")): 28 | continue 29 | name, currency, *rest = re.split(r"\s*,\s*", line) 30 | key = "{} ({})".format(name, currency) 31 | try: 32 | get.rates[key] = float(rest[-1]) 33 | except ValueError as err: 34 | print("error {}: {}".format(err, line)) 35 | return get.rates 36 | get.rates = {} 37 | 38 | 39 | if __name__ == "__main__": 40 | import sys 41 | if sys.stdout.isatty(): 42 | print(get()) 43 | else: 44 | print("Loaded OK") 45 | -------------------------------------------------------------------------------- /currency/currency.pyw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import os 13 | import sys 14 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 15 | ".."))) 16 | import tkinter as tk 17 | import Main 18 | import TkUtil 19 | 20 | 21 | def main(): 22 | application = tk.Tk() 23 | application.title("Currency") 24 | TkUtil.set_application_icons(application, os.path.join( 25 | os.path.dirname(os.path.realpath(__file__)), "images")) 26 | Main.Window(application) 27 | application.mainloop() 28 | 29 | 30 | main() 31 | -------------------------------------------------------------------------------- /currency/images/icon_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/currency/images/icon_16x16.gif -------------------------------------------------------------------------------- /currency/images/icon_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/currency/images/icon_16x16.ico -------------------------------------------------------------------------------- /currency/images/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/currency/images/icon_16x16.png -------------------------------------------------------------------------------- /currency/images/icon_32x32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/currency/images/icon_32x32.gif -------------------------------------------------------------------------------- /currency/images/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/currency/images/icon_32x32.png -------------------------------------------------------------------------------- /cyImage/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | from cyImage.cyImage.Image import (Error, Image, argb_for_color, 13 | rgb_for_color, color_for_argb, color_for_rgb, color_for_name) 14 | -------------------------------------------------------------------------------- /cyImage/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | # Build with: python3 setup.py build_ext --inplace 13 | 14 | import distutils.core 15 | import numpy 16 | import Cython.Build 17 | 18 | 19 | distutils.core.setup(name="cyImage", 20 | include_dirs=[numpy.get_include()], 21 | ext_modules=Cython.Build.cythonize("*.pyx")) 22 | -------------------------------------------------------------------------------- /cylinder_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/cylinder_16x16.png -------------------------------------------------------------------------------- /cylinder_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/cylinder_32x32.png -------------------------------------------------------------------------------- /eventhandler1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import sys 13 | import Event 14 | 15 | 16 | def main(): 17 | print("Handler Chain #1") 18 | handler1 = TimerHandler(KeyHandler(MouseHandler(NullHandler()))) 19 | # Could pass None or nothing instead of the NullHandler 20 | while True: 21 | event = Event.next() 22 | if event.kind == Event.TERMINATE: 23 | break 24 | handler1.handle(event) 25 | 26 | print("\nHandler Chain #2 (debugging)") 27 | handler2 = DebugHandler(handler1) 28 | while True: 29 | event = Event.next() 30 | if event.kind == Event.TERMINATE: 31 | break 32 | handler2.handle(event) 33 | 34 | 35 | class NullHandler: 36 | 37 | def __init__(self, successor=None): 38 | self.__successor = successor 39 | 40 | 41 | def handle(self, event): 42 | if self.__successor is not None: 43 | self.__successor.handle(event) 44 | 45 | 46 | class DebugHandler(NullHandler): 47 | 48 | def __init__(self, successor=None, file=sys.stdout): 49 | super().__init__(successor) 50 | self.__file = file 51 | 52 | 53 | def handle(self, event): 54 | self.__file.write("*DEBUG*: {}\n".format(event)) 55 | super().handle(event) 56 | 57 | 58 | class MouseHandler(NullHandler): 59 | 60 | def handle(self, event): 61 | if event.kind == Event.MOUSE: 62 | print("Click: {}".format(event)) 63 | else: 64 | super().handle(event) 65 | 66 | 67 | class KeyHandler(NullHandler): 68 | 69 | def handle(self, event): 70 | if event.kind == Event.KEYPRESS: 71 | print("Press: {}".format(event)) 72 | else: 73 | super().handle(event) 74 | 75 | 76 | class TimerHandler(NullHandler): 77 | 78 | def handle(self, event): 79 | if event.kind == Event.TIMER: 80 | print("Timeout: {}".format(event)) 81 | else: 82 | super().handle(event) 83 | 84 | 85 | if __name__ == "__main__": 86 | main() 87 | -------------------------------------------------------------------------------- /eventhandler2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import sys 13 | import Event 14 | from Qtrac import coroutine 15 | 16 | 17 | def main(): 18 | print("Handler Chain #1") 19 | pipeline = key_handler(mouse_handler(timer_handler())) 20 | while True: 21 | event = Event.next() 22 | if event.kind == Event.TERMINATE: 23 | break 24 | pipeline.send(event) 25 | 26 | print("\nHandler Chain #2 (debugging)") 27 | pipeline = debug_handler(pipeline) 28 | while True: 29 | event = Event.next() 30 | if event.kind == Event.TERMINATE: 31 | break 32 | pipeline.send(event) 33 | 34 | 35 | @coroutine 36 | def debug_handler(successor, file=sys.stdout): 37 | while True: 38 | event = (yield) 39 | file.write("*DEBUG*: {}\n".format(event)) 40 | successor.send(event) 41 | 42 | 43 | @coroutine 44 | def mouse_handler(successor=None): 45 | while True: 46 | event = (yield) 47 | if event.kind == Event.MOUSE: 48 | print("Click: {}".format(event)) 49 | elif successor is not None: 50 | successor.send(event) 51 | 52 | 53 | @coroutine 54 | def key_handler(successor=None): 55 | while True: 56 | event = (yield) 57 | if event.kind == Event.KEYPRESS: 58 | print("Press: {}".format(event)) 59 | elif successor is not None: 60 | successor.send(event) 61 | 62 | 63 | @coroutine 64 | def timer_handler(successor=None): 65 | while True: 66 | event = (yield) 67 | if event.kind == Event.TIMER: 68 | print("Timeout: {}".format(event)) 69 | elif successor is not None: 70 | successor.send(event) 71 | 72 | 73 | if __name__ == "__main__": 74 | main() 75 | -------------------------------------------------------------------------------- /gravitate/About.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import sys 13 | import tkinter as tk 14 | import tkinter.font as tkfont 15 | if __name__ == "__main__": # For stand-alone testing with parallel TkUtil 16 | import os 17 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 18 | ".."))) 19 | import TkUtil 20 | import TkUtil.About 21 | from Globals import * 22 | 23 | 24 | class Window(TkUtil.About.Window): 25 | 26 | def __init__(self, master): 27 | super().__init__(master, APPNAME, height=20) 28 | 29 | 30 | def create_tags(self): 31 | super().create_tags() # for url tag 32 | # Don't modify predefined fonts! 33 | baseFont = tkfont.nametofont("TkDefaultFont") 34 | size = baseFont.cget("size") # -ve is pixels +ve is points 35 | bodyFont = tkfont.Font(family=baseFont.cget("family"), size=size) 36 | titleFont = tkfont.Font(family=baseFont.cget("family"), 37 | size=((size - 8) if size < 0 else (size + 3)), 38 | weight=tkfont.BOLD) 39 | 40 | self.text.config(font=bodyFont) 41 | self.text.tag_config("title", font=titleFont, 42 | foreground="navyblue", spacing1=3, spacing3=5) 43 | self.text.tag_config("versions", foreground="darkgreen") 44 | self.text.tag_config("above5", spacing1=5) 45 | self.text.tag_config("above3", spacing1=3) 46 | 47 | 48 | def populate_text(self): 49 | self.text.insert(tk.END, "{}\n".format(APPNAME), ("title", 50 | "center")) 51 | self.text.insert(tk.END, "Copyright © 2012-13 Qtrac Ltd. " 52 | "All rights reserved.\n", ("center",)) 53 | self.text.insert(tk.END, "www.qtrac.eu/pipbook.html\n", ("center", 54 | "url", "above5")) 55 | self.add_lines(""" 56 | This program or module is free software: you can redistribute it 57 | and/or modify it under the terms of the GNU General Public License as 58 | published by the Free Software Foundation, either version 3 of the 59 | License, or (at your option) any later version. It is provided for 60 | educational purposes and is distributed in the hope that it will be 61 | useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 62 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 63 | General Public License for more details.""") 64 | self.add_lines(""" 65 | {} was inspired by tile fall/same game which was originally 66 | written for the Amiga and Psion by Adam Dawes.""".format(APPNAME)) 67 | self.text.insert(tk.END, "\n" + TkUtil.about(self.master, APPNAME, 68 | VERSION), ("versions", "center", "above3")) 69 | 70 | 71 | if __name__ == "__main__": 72 | if sys.stdout.isatty(): 73 | application = tk.Tk() 74 | Window(application) 75 | application.mainloop() 76 | else: 77 | print("Loaded OK") 78 | -------------------------------------------------------------------------------- /gravitate/Globals.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | ABOUT = "About" 13 | APPNAME = "Gravitate" 14 | BOARD = "Board" 15 | CLOSE = "Close" 16 | COLUMNS = "columns" 17 | ELLIPSIS = "..." 18 | GENERAL = "General" 19 | HELP = "Help" 20 | HIGHSCORE = "highscore" 21 | MAXCOLORS = "maxcolors" 22 | NEW = "New" 23 | OK = "OK" 24 | PAD = "0.75m" 25 | PREFERENCES = "Preferences" 26 | ROWS = "rows" 27 | SHOW_TIME = 5000 # milliseconds 28 | VERSION = "1.0.0" 29 | -------------------------------------------------------------------------------- /gravitate/Help.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import os 13 | import re 14 | import tkinter as tk 15 | import tkinter.ttk as ttk 16 | if __name__ == "__main__": # For stand-alone testing with parallel TkUtil 17 | import sys 18 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 19 | ".."))) 20 | import TkUtil 21 | from Globals import * 22 | 23 | 24 | _TEXT = """\ 25 | The aim of the game is to remove all the tiles from the board. 26 | 27 | When a tile is clicked, that tile, and any vertically or 28 | horizontally adjoining tiles of the same color, are removed. 29 | (If there are no adjoining tiles the click has no effect.) 30 | 31 | The more tiles removed in one go, the more points you score!""" 32 | 33 | 34 | class Window(tk.Toplevel): 35 | 36 | def __init__(self, master): 37 | super().__init__(master) 38 | self.withdraw() 39 | self.title("Help \u2014 {}".format(APPNAME)) 40 | self.create_ui() 41 | self.reposition() 42 | self.resizable(False, False) 43 | self.deiconify() 44 | if self.winfo_viewable(): 45 | self.transient(master) 46 | self.wait_visibility() 47 | 48 | 49 | def create_ui(self): 50 | self.helpLabel = ttk.Label(self, text=_TEXT, background="white") 51 | self.closeButton = TkUtil.Button(self, text="Close", underline=0) 52 | self.helpLabel.pack(anchor=tk.N, expand=True, fill=tk.BOTH, 53 | padx=PAD, pady=PAD) 54 | self.closeButton.pack(anchor=tk.S) 55 | self.protocol("WM_DELETE_WINDOW", self.close) 56 | if not TkUtil.mac(): 57 | self.bind("", self.close) 58 | self.bind("", self.close) 59 | self.bind("", self.reposition) 60 | 61 | 62 | def reposition(self, event=None): 63 | if self.master is not None: 64 | self.geometry("+{}+{}".format(self.master.winfo_rootx() + 50, 65 | self.master.winfo_rooty() + 50)) 66 | 67 | 68 | def close(self, event=None): 69 | self.withdraw() 70 | 71 | 72 | if __name__ == "__main__": 73 | if sys.stdout.isatty(): 74 | application = tk.Tk() 75 | window = Window(application) 76 | application.bind("", lambda *args: application.quit()) 77 | window.bind("", lambda *args: application.quit()) 78 | application.mainloop() 79 | else: 80 | print("Loaded OK") 81 | -------------------------------------------------------------------------------- /gravitate/gravitate.pyw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import os 13 | import sys 14 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 15 | ".."))) 16 | import tkinter as tk 17 | import Main 18 | import TkUtil 19 | from Globals import * 20 | 21 | 22 | def main(): 23 | application = tk.Tk() 24 | application.withdraw() 25 | application.title(APPNAME) 26 | application.option_add("*tearOff", False) 27 | TkUtil.set_application_icons(application, os.path.join( 28 | os.path.dirname(os.path.realpath(__file__)), "images")) 29 | window = Main.Window(application) 30 | application.protocol("WM_DELETE_WINDOW", window.close) 31 | application.deiconify() 32 | application.mainloop() 33 | 34 | 35 | main() 36 | -------------------------------------------------------------------------------- /gravitate/images/About_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate/images/About_16x16.gif -------------------------------------------------------------------------------- /gravitate/images/Close_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate/images/Close_16x16.gif -------------------------------------------------------------------------------- /gravitate/images/Help_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate/images/Help_16x16.gif -------------------------------------------------------------------------------- /gravitate/images/New_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate/images/New_16x16.gif -------------------------------------------------------------------------------- /gravitate/images/Preferences_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate/images/Preferences_16x16.gif -------------------------------------------------------------------------------- /gravitate/images/icon_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate/images/icon_16x16.gif -------------------------------------------------------------------------------- /gravitate/images/icon_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate/images/icon_16x16.ico -------------------------------------------------------------------------------- /gravitate/images/icon_32x32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate/images/icon_32x32.gif -------------------------------------------------------------------------------- /gravitate/images/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate/images/icon_32x32.png -------------------------------------------------------------------------------- /gravitate2/About.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import sys 13 | import tkinter as tk 14 | import tkinter.font as tkfont 15 | if __name__ == "__main__": # For stand-alone testing with parallel TkUtil 16 | import os 17 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 18 | ".."))) 19 | import TkUtil 20 | import TkUtil.About 21 | from Globals import * 22 | 23 | 24 | class Window(TkUtil.About.Window): 25 | 26 | def __init__(self, master): 27 | super().__init__(master, APPNAME, height=20) 28 | 29 | 30 | def create_tags(self): 31 | super().create_tags() # for url tag 32 | # Don't modify predefined fonts! 33 | baseFont = tkfont.nametofont("TkDefaultFont") 34 | size = baseFont.cget("size") # -ve is pixels +ve is points 35 | bodyFont = tkfont.Font(family=baseFont.cget("family"), size=size) 36 | titleFont = tkfont.Font(family=baseFont.cget("family"), 37 | size=((size - 8) if size < 0 else (size + 3)), 38 | weight=tkfont.BOLD) 39 | 40 | self.text.config(font=bodyFont) 41 | self.text.tag_config("title", font=titleFont, 42 | foreground="navyblue", spacing1=3, spacing3=5) 43 | self.text.tag_config("versions", foreground="darkgreen") 44 | self.text.tag_config("above5", spacing1=5) 45 | self.text.tag_config("above3", spacing1=3) 46 | 47 | 48 | def populate_text(self): 49 | self.text.insert(tk.END, "{}\n".format(APPNAME), ("title", 50 | "center")) 51 | self.text.insert(tk.END, "Copyright © 2012-13 Qtrac Ltd. " 52 | "All rights reserved.\n", ("center",)) 53 | self.text.insert(tk.END, "www.qtrac.eu/pipbook.html\n", ("center", 54 | "url", "above5")) 55 | self.add_lines(""" 56 | This program or module is free software: you can redistribute it 57 | and/or modify it under the terms of the GNU General Public License as 58 | published by the Free Software Foundation, either version 3 of the 59 | License, or (at your option) any later version. It is provided for 60 | educational purposes and is distributed in the hope that it will be 61 | useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 62 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 63 | General Public License for more details.""") 64 | self.add_lines(""" 65 | {} was inspired by tile fall/same game which was originally 66 | written for the Amiga and Psion by Adam Dawes.""".format(APPNAME)) 67 | self.text.insert(tk.END, "\n" + TkUtil.about(self.master, APPNAME, 68 | VERSION), ("versions", "center", "above3")) 69 | 70 | 71 | if __name__ == "__main__": 72 | if sys.stdout.isatty(): 73 | application = tk.Tk() 74 | window = Window(application) 75 | application.mainloop() 76 | else: 77 | print("Loaded OK") 78 | -------------------------------------------------------------------------------- /gravitate2/GameOver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import os 13 | import sys 14 | import tkinter as tk 15 | import tkinter.ttk as ttk 16 | Spinbox = ttk.Spinbox if hasattr(ttk, "Spinbox") else tk.Spinbox 17 | if __name__ == "__main__": # For stand-alone testing with parallel TkUtil 18 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 19 | ".."))) 20 | import TkUtil.Dialog 21 | from Globals import * 22 | 23 | 24 | class Window(TkUtil.Dialog.Dialog): 25 | 26 | def __init__(self, master, score, won, newHighScore): 27 | self.score = score 28 | self.won = won 29 | self.newHighScore = newHighScore 30 | title = "Winner!" if won else "Game Over" 31 | super().__init__(master, "{} — {}".format(title, APPNAME), 32 | TkUtil.Dialog.OK_BUTTON) 33 | 34 | 35 | def initialize(self): 36 | if self.master is not None: 37 | self.geometry("+{}+{}".format(self.master.winfo_rootx() + 50, 38 | self.master.winfo_rooty())) 39 | self.acceptButton.focus() 40 | 41 | 42 | def body(self, master): 43 | frame = ttk.Frame(master) 44 | if self.won: 45 | message = "You won with a score of {:,}!" 46 | if self.newHighScore: 47 | message += "\nThat's a new high score!" 48 | else: 49 | message = "Game over with a score of {:,}." 50 | label = ttk.Label(frame, text=message.format(self.score), 51 | justify=tk.CENTER) 52 | label.pack(fill=tk.BOTH, padx="7m", pady="4m") 53 | frame.pack(fill=tk.BOTH) 54 | return frame 55 | 56 | 57 | if __name__ == "__main__": 58 | if sys.stdout.isatty(): 59 | import random 60 | def close(event): 61 | window.destroy() 62 | application.quit() 63 | application = tk.Tk() 64 | window = Window(application, random.randint(400, 1600), 65 | random.choice((True, False)), random.choice((True, False))) 66 | application.bind("", close) 67 | application.mainloop() 68 | else: 69 | print("Loaded OK") 70 | -------------------------------------------------------------------------------- /gravitate2/Globals.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | ABOUT = "About" 13 | ADVANCED = "Advanced" 14 | APPNAME = "Gravitate" 15 | BOARD = "Board" 16 | CLOSE = "Close" 17 | COLUMNS = "columns" 18 | DELAY = "delay" 19 | ELLIPSIS = "..." 20 | GENERAL = "General" 21 | GENERAL = "General" 22 | HELP = "Help" 23 | HIGHSCORE = "highscore" 24 | MAXCOLORS = "maxcolors" 25 | MAXCOLORCHOICES = "maxcolorchoices" 26 | NEW = "New" 27 | OK = "OK" 28 | PAD = "0.75m" 29 | POSITION = "position" 30 | PREFERENCES = "Preferences" 31 | RESTORE = "restore" 32 | ROWS = "rows" 33 | SHAPENAME = "shapename" 34 | SHAPE = "Shape" 35 | SHOW_TIME = 5000 # milliseconds 36 | SHOWTOOLBAR = "showtoolbar" 37 | VERSION = "2.0.0" 38 | ZOOM = "zoom" 39 | -------------------------------------------------------------------------------- /gravitate2/Help.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import os 13 | import re 14 | import tkinter as tk 15 | import tkinter.ttk as ttk 16 | if __name__ == "__main__": # For stand-alone testing with parallel TkUtil 17 | import sys 18 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 19 | ".."))) 20 | import TkUtil 21 | from Globals import * 22 | 23 | 24 | _TEXT = """\ 25 | The aim of the game is to remove all the tiles from the board. 26 | 27 | When a tile is clicked, that tile, and any vertically or 28 | horizontally adjoining tiles of the same color, are removed. 29 | (If there are no adjoining tiles the click has no effect.) 30 | 31 | The more tiles removed in one go, the more points you score! 32 | 33 | Keyboard users can navigate using the arrow keys and 34 | delete by pressing the spacebar.""" 35 | 36 | 37 | class Window(tk.Toplevel): 38 | 39 | def __init__(self, master): 40 | super().__init__(master) 41 | self.withdraw() 42 | self.title("Help \u2014 {}".format(APPNAME)) 43 | self.create_ui() 44 | self.reposition() 45 | self.resizable(False, False) 46 | self.deiconify() 47 | if self.winfo_viewable(): 48 | self.transient(master) 49 | self.wait_visibility() 50 | 51 | 52 | def create_ui(self): 53 | self.helpLabel = ttk.Label(self, text=_TEXT, background="white", 54 | justify=tk.CENTER) 55 | self.closeButton = TkUtil.Button(self, text="Close", underline=0) 56 | self.helpLabel.pack(anchor=tk.N, expand=True, fill=tk.BOTH, 57 | padx=PAD, pady=PAD, ipadx="2m", ipady="2m") 58 | self.closeButton.pack(anchor=tk.S) 59 | self.protocol("WM_DELETE_WINDOW", self.close) 60 | if not TkUtil.mac(): 61 | self.bind("", self.close) 62 | self.bind("", self.close) 63 | self.bind("", self.reposition) 64 | 65 | 66 | def reposition(self, event=None): 67 | if self.master is not None: 68 | self.geometry("+{}+{}".format(self.master.winfo_rootx() + 50, 69 | self.master.winfo_rooty() + 50)) 70 | 71 | 72 | def close(self, event=None): 73 | self.withdraw() 74 | 75 | 76 | if __name__ == "__main__": 77 | if sys.stdout.isatty(): 78 | application = tk.Tk() 79 | window = Window(application) 80 | application.bind("", lambda *args: application.quit()) 81 | window.bind("", lambda *args: application.quit()) 82 | application.mainloop() 83 | else: 84 | print("Loaded OK") 85 | -------------------------------------------------------------------------------- /gravitate2/Options.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | 13 | class Options: 14 | 15 | def __init__(self, ok, showToolbar, restore, shapeName, zoom, board): 16 | self.ok = ok 17 | self.showToolbar = showToolbar 18 | self.restore = restore 19 | self.shapeName = shapeName 20 | self.zoom = zoom 21 | self.board = board 22 | 23 | 24 | def __str__(self): 25 | return ("ok={} showToolbar={} restore={} shapeName={} zoom={} " 26 | "board={}".format(self.ok, self.showToolbar, self.restore, 27 | self.shapeName.get(), self.zoom.get(), self.board)) 28 | -------------------------------------------------------------------------------- /gravitate2/gravitate.pyw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import os 13 | import sys 14 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 15 | ".."))) 16 | import tkinter as tk 17 | import Main 18 | import TkUtil 19 | import TkUtil.Settings 20 | from Globals import * 21 | 22 | 23 | def main(): 24 | application = tk.Tk() 25 | application.withdraw() # hide until ready to show 26 | application.title(APPNAME) 27 | application.option_add("*tearOff", False) # Avoid ugly tear menu line 28 | TkUtil.Settings.DOMAIN = "Qtrac" 29 | TkUtil.Settings.APPNAME = APPNAME 30 | settings = TkUtil.Settings.load() 31 | TkUtil.set_application_icons(application, os.path.join( 32 | os.path.dirname(os.path.realpath(__file__)), "images")) 33 | window = Main.Window(application) 34 | application.protocol("WM_DELETE_WINDOW", window.close) 35 | application.deiconify() # show 36 | application.mainloop() 37 | 38 | 39 | main() 40 | -------------------------------------------------------------------------------- /gravitate2/images/About_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate2/images/About_16x16.gif -------------------------------------------------------------------------------- /gravitate2/images/Circle_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate2/images/Circle_16x16.gif -------------------------------------------------------------------------------- /gravitate2/images/Close_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate2/images/Close_16x16.gif -------------------------------------------------------------------------------- /gravitate2/images/Help_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate2/images/Help_16x16.gif -------------------------------------------------------------------------------- /gravitate2/images/Hexagon_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate2/images/Hexagon_16x16.gif -------------------------------------------------------------------------------- /gravitate2/images/New_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate2/images/New_16x16.gif -------------------------------------------------------------------------------- /gravitate2/images/Octagon_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate2/images/Octagon_16x16.gif -------------------------------------------------------------------------------- /gravitate2/images/Preferences_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate2/images/Preferences_16x16.gif -------------------------------------------------------------------------------- /gravitate2/images/Shape_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate2/images/Shape_16x16.gif -------------------------------------------------------------------------------- /gravitate2/images/Spiral_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate2/images/Spiral_16x16.gif -------------------------------------------------------------------------------- /gravitate2/images/Square_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate2/images/Square_16x16.gif -------------------------------------------------------------------------------- /gravitate2/images/icon_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate2/images/icon_16x16.gif -------------------------------------------------------------------------------- /gravitate2/images/icon_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate2/images/icon_16x16.ico -------------------------------------------------------------------------------- /gravitate2/images/icon_32x32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate2/images/icon_32x32.gif -------------------------------------------------------------------------------- /gravitate2/images/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/gravitate2/images/icon_32x32.png -------------------------------------------------------------------------------- /hello.pyw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import tkinter as tk 13 | import tkinter.ttk as ttk 14 | 15 | class Window(ttk.Frame): 16 | 17 | def __init__(self, master=None): 18 | super().__init__(master) # Creates self.master 19 | helloLabel = ttk.Label(self, text="Hello Tkinter!") 20 | quitButton = ttk.Button(self, text="Quit", command=self.quit) 21 | helloLabel.pack() 22 | quitButton.pack() 23 | self.pack() 24 | 25 | window = Window() # Implicitly creates tk.Tk object 26 | window.master.title("Hello") 27 | window.master.mainloop() 28 | -------------------------------------------------------------------------------- /hyph_de_DE.dic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/hyph_de_DE.dic -------------------------------------------------------------------------------- /hyph_fr_FR.dic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/hyph_fr_FR.dic -------------------------------------------------------------------------------- /imageproxy1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import os 13 | import tempfile 14 | try: 15 | import cyImage as Image 16 | except ImportError: 17 | import Image 18 | 19 | 20 | YELLOW, CYAN, BLUE, RED, BLACK = (Image.color_for_name(color) 21 | for color in ("yellow", "cyan", "blue", "red", "black")) 22 | 23 | 24 | def main(): 25 | filename = os.path.join(tempfile.gettempdir(), "image.xpm") 26 | image = Image.Image(300, 60) 27 | draw_and_save_image(image, filename) 28 | 29 | filename = os.path.join(tempfile.gettempdir(), "proxy.xpm") 30 | image = ImageProxy(Image.Image, 300, 60) 31 | draw_and_save_image(image, filename) 32 | 33 | 34 | def draw_and_save_image(image, filename): 35 | image.rectangle(0, 0, 299, 59, fill=YELLOW) 36 | image.ellipse(0, 0, 299, 59, fill=CYAN) 37 | image.ellipse(60, 20, 120, 40, BLUE, RED) 38 | image.ellipse(180, 20, 240, 40, BLUE, RED) 39 | image.rectangle(180, 32, 240, 41, fill=CYAN) 40 | image.line(181, 32, 239, 32, BLUE) 41 | image.line(140, 50, 160, 50, BLACK) 42 | image.save(filename) 43 | print("saved", filename) 44 | 45 | 46 | class ImageProxy: 47 | 48 | def __init__(self, ImageClass, width=None, height=None, filename=None): 49 | assert (width is not None and height is not None) or \ 50 | filename is not None 51 | self.Image = ImageClass 52 | self.commands = [] 53 | if filename is not None: 54 | self.load(filename) 55 | else: 56 | self.commands = [(self.Image, width, height)] 57 | 58 | 59 | def load(self, filename): 60 | self.commands = [(self.Image, None, None, filename)] 61 | 62 | 63 | def save(self, filename=None): 64 | command = self.commands.pop(0) 65 | function, *args = command 66 | image = function(*args) 67 | for command in self.commands: 68 | function, *args = command 69 | function(image, *args) 70 | image.save(filename) 71 | return image 72 | 73 | 74 | def set_pixel(self, x, y, color): 75 | self.commands.append((self.Image.set_pixel, x, y, color)) 76 | 77 | 78 | def line(self, x0, y0, x1, y1, color): 79 | self.commands.append((self.Image.line, x0, y0, x1, y1, color)) 80 | 81 | 82 | def rectangle(self, x0, y0, x1, y1, outline=None, fill=None): 83 | self.commands.append((self.Image.rectangle, x0, y0, x1, y1, 84 | outline, fill)) 85 | 86 | 87 | def ellipse(self, x0, y0, x1, y1, outline=None, fill=None): 88 | self.commands.append((self.Image.ellipse, x0, y0, x1, y1, 89 | outline, fill)) 90 | 91 | # Incomplete API. Unsupported are: 92 | # pixel(), subsample(), scale(), and size() 93 | 94 | if __name__ == "__main__": 95 | main() 96 | -------------------------------------------------------------------------------- /imagescale/About.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import sys 13 | import tkinter as tk 14 | import tkinter.font as tkfont 15 | if __name__ == "__main__": # For stand-alone testing with parallel TkUtil 16 | import os 17 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 18 | ".."))) 19 | import TkUtil 20 | import TkUtil.About 21 | from Globals import * 22 | 23 | 24 | class Window(TkUtil.About.Window): 25 | 26 | def __init__(self, master): 27 | super().__init__(master, APPNAME, height=20) 28 | 29 | 30 | def create_tags(self): 31 | super().create_tags() # for url tag 32 | # Don't modify predefined fonts! 33 | baseFont = tkfont.nametofont("TkDefaultFont") 34 | size = baseFont.cget("size") # -ve is pixels +ve is points 35 | bodyFont = tkfont.Font(family=baseFont.cget("family"), size=size) 36 | titleFont = tkfont.Font(family=baseFont.cget("family"), 37 | size=((size - 8) if size < 0 else (size + 3)), 38 | weight=tkfont.BOLD) 39 | 40 | self.text.config(font=bodyFont) 41 | self.text.tag_config("title", font=titleFont, 42 | foreground="navyblue", spacing1=3, spacing3=5) 43 | self.text.tag_config("versions", foreground="darkgreen") 44 | self.text.tag_config("above5", spacing1=5) 45 | self.text.tag_config("above3", spacing1=3) 46 | 47 | 48 | def populate_text(self): 49 | self.text.insert(tk.END, "{}\n".format(APPNAME), ("title", 50 | "center")) 51 | self.text.insert(tk.END, "Copyright © 2012-13 Qtrac Ltd. " 52 | "All rights reserved.\n", ("center",)) 53 | self.text.insert(tk.END, "www.qtrac.eu/pipbook.html\n", ("center", 54 | "url", "above5")) 55 | self.add_lines(""" 56 | This program or module is free software: you can redistribute it 57 | and/or modify it under the terms of the GNU General Public License as 58 | published by the Free Software Foundation, either version 3 of the 59 | License, or (at your option) any later version. It is provided for 60 | educational purposes and is distributed in the hope that it will be 61 | useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 62 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 63 | General Public License for more details.""") 64 | self.text.insert(tk.END, "\n" + TkUtil.about(self.master, APPNAME, 65 | VERSION), ("versions", "center", "above3")) 66 | 67 | 68 | if __name__ == "__main__": 69 | if sys.stdout.isatty(): 70 | application = tk.Tk() 71 | Window(application) 72 | application.mainloop() 73 | else: 74 | print("Loaded OK") 75 | -------------------------------------------------------------------------------- /imagescale/Globals.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | ABOUT = "About" 13 | APPNAME = "ImageScale" 14 | GENERAL = "General" 15 | PAD = "0.75m" 16 | POSITION = "position" 17 | RESTORE = "Restore" 18 | SOURCE, TARGET = ("SOURCE", "TARGET") 19 | VERSION = "1.0.0" 20 | WORKING, CANCELED, TERMINATING, IDLE = ("WORKING", "CANCELED", 21 | "TERMINATING", "IDLE") 22 | 23 | class Canceled(Exception): pass 24 | -------------------------------------------------------------------------------- /imagescale/ImageScale.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import collections 13 | import concurrent.futures 14 | import multiprocessing 15 | import os 16 | import sys 17 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 18 | ".."))) # For access to parallel Image 19 | try: 20 | import cyImage as Image 21 | except ImportError: 22 | import Image 23 | from Globals import * 24 | 25 | 26 | Result = collections.namedtuple("Result", "name copied scaled") 27 | 28 | 29 | def scale(size, source, target, report_progress, state, when_finished): 30 | futures = set() 31 | with concurrent.futures.ProcessPoolExecutor( 32 | max_workers=multiprocessing.cpu_count()) as executor: 33 | for sourceImage, targetImage in get_jobs(source, target): 34 | future = executor.submit(scale_one, size, sourceImage, 35 | targetImage, state) 36 | future.add_done_callback(report_progress) 37 | futures.add(future) 38 | if state.value in {CANCELED, TERMINATING}: 39 | for future in futures: 40 | future.cancel() 41 | executor.shutdown() 42 | break 43 | concurrent.futures.wait(futures) # Keep working until finished 44 | if state.value != TERMINATING: 45 | when_finished() 46 | 47 | 48 | def get_jobs(source, target): 49 | for name in os.listdir(source): 50 | yield os.path.join(source, name), os.path.join(target, name) 51 | 52 | 53 | def scale_one(size, sourceImage, targetImage, state): 54 | if state.value in {CANCELED, TERMINATING}: 55 | raise Canceled() 56 | oldImage = Image.Image.from_file(sourceImage) 57 | if state.value in {CANCELED, TERMINATING}: 58 | raise Canceled() 59 | if oldImage.width <= size and oldImage.height <= size: 60 | oldImage.save(targetImage) 61 | return Result(targetImage, 1, 0) 62 | else: 63 | scale = min(size / oldImage.width, size / oldImage.height) 64 | newImage = oldImage.scale(scale) 65 | if state.value in {CANCELED, TERMINATING}: 66 | raise Canceled() 67 | newImage.save(targetImage) 68 | return Result(targetImage, 0, 1) 69 | 70 | 71 | if __name__ == "__main__": 72 | print("Loaded OK") 73 | -------------------------------------------------------------------------------- /imagescale/images/icon_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/imagescale/images/icon_16x16.gif -------------------------------------------------------------------------------- /imagescale/images/icon_32x32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/imagescale/images/icon_32x32.gif -------------------------------------------------------------------------------- /imagescale/images/icon_32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/imagescale/images/icon_32x32.ico -------------------------------------------------------------------------------- /imagescale/images/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/imagescale/images/icon_32x32.png -------------------------------------------------------------------------------- /imagescale/imagescale.pyw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import sys 13 | if sys.version_info < (3, 2): 14 | print("requires Python 3.2+ for concurrent.futures") 15 | sys.exit(1) 16 | import os 17 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 18 | ".."))) 19 | import tkinter as tk 20 | import Main 21 | import TkUtil 22 | import TkUtil.Settings 23 | from Globals import * 24 | 25 | 26 | def main(): 27 | application = tk.Tk() 28 | application.withdraw() # hide until ready to show 29 | application.title(APPNAME) 30 | application.option_add("*tearOff", False) # Avoid ugly tear menu line 31 | TkUtil.Settings.DOMAIN = "Qtrac" 32 | TkUtil.Settings.APPNAME = APPNAME 33 | settings = TkUtil.Settings.load() 34 | TkUtil.set_application_icons(application, os.path.join( 35 | os.path.dirname(os.path.realpath(__file__)), "images")) 36 | window = Main.Window(application) 37 | application.protocol("WM_DELETE_WINDOW", window.close) 38 | application.deiconify() # show 39 | application.mainloop() 40 | 41 | 42 | if __name__ == "__main__": 43 | main() 44 | -------------------------------------------------------------------------------- /mediator2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | from Qtrac import coroutine 13 | 14 | 15 | def main(): 16 | form = Form() 17 | test_user_interaction_with(form) 18 | 19 | 20 | class Form: 21 | 22 | def __init__(self): 23 | self.create_widgets() 24 | self.create_mediator() 25 | 26 | 27 | def create_widgets(self): 28 | self.nameText = Text() 29 | self.emailText = Text() 30 | self.okButton = Button("OK") 31 | self.cancelButton = Button("Cancel") 32 | 33 | 34 | def create_mediator(self): 35 | self.mediator = self._update_ui_mediator(self._clicked_mediator()) 36 | for widget in (self.nameText, self.emailText, self.okButton, 37 | self.cancelButton): 38 | widget.mediator = self.mediator 39 | self.mediator.send(None) 40 | 41 | 42 | @coroutine 43 | def _update_ui_mediator(self, successor=None): 44 | while True: 45 | widget = (yield) 46 | self.okButton.enabled = (bool(self.nameText.text) and 47 | bool(self.emailText.text)) 48 | if successor is not None: 49 | successor.send(widget) 50 | 51 | 52 | @coroutine 53 | def _clicked_mediator(self, successor=None): 54 | while True: 55 | widget = (yield) 56 | if widget == self.okButton: 57 | print("OK") 58 | elif widget == self.cancelButton: 59 | print("Cancel") 60 | elif successor is not None: 61 | successor.send(widget) 62 | 63 | 64 | class Mediated: 65 | 66 | def __init__(self): 67 | self.mediator = None 68 | 69 | 70 | def on_change(self): 71 | if self.mediator is not None: 72 | self.mediator.send(self) 73 | 74 | 75 | class Button(Mediated): 76 | 77 | def __init__(self, text=""): 78 | super().__init__() 79 | self.enabled = True 80 | self.text = text 81 | 82 | 83 | def click(self): 84 | if self.enabled: 85 | self.on_change() 86 | 87 | 88 | def __str__(self): 89 | return "Button({!r}) {}".format(self.text, 90 | "enabled" if self.enabled else "disabled") 91 | 92 | 93 | class Text(Mediated): 94 | 95 | def __init__(self, text=""): 96 | super().__init__() 97 | self.__text = text 98 | 99 | 100 | @property 101 | def text(self): 102 | return self.__text 103 | 104 | 105 | @text.setter 106 | def text(self, text): 107 | if self.text != text: 108 | self.__text = text 109 | self.on_change() 110 | 111 | 112 | def __str__(self): 113 | return "Text({!r})".format(self.text) 114 | 115 | 116 | def test_user_interaction_with(form): 117 | form.okButton.click() # Ignored because it is disabled 118 | print(form.okButton.enabled) # False 119 | form.nameText.text = "Fred" 120 | print(form.okButton.enabled) # False 121 | form.emailText.text = "fred@bloggers.com" 122 | print(form.okButton.enabled) # True 123 | form.okButton.click() # OK 124 | form.emailText.text = "" 125 | print(form.okButton.enabled) # False 126 | form.cancelButton.click() # Cancel 127 | 128 | 129 | if __name__ == "__main__": 130 | main() 131 | -------------------------------------------------------------------------------- /mediator2d.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | from Qtrac import coroutine 13 | 14 | 15 | def main(): 16 | form = Form() 17 | test_user_interaction_with(form) 18 | 19 | 20 | class Form: 21 | 22 | def __init__(self): 23 | self.create_widgets() 24 | self.create_mediator() 25 | 26 | 27 | def create_widgets(self): 28 | self.nameText = Text() 29 | self.emailText = Text() 30 | self.okButton = Button("OK") 31 | self.cancelButton = Button("Cancel") 32 | 33 | 34 | def create_mediator(self): 35 | self.mediator = self._update_ui_mediator(self._clicked_mediator()) 36 | for widget in (self.nameText, self.emailText, self.okButton, 37 | self.cancelButton): 38 | widget.mediator = self.mediator 39 | self.mediator.send(None) 40 | 41 | 42 | @coroutine 43 | def _update_ui_mediator(self, successor=None): 44 | while True: 45 | widget = (yield) 46 | self.okButton.enabled = (bool(self.nameText.text) and 47 | bool(self.emailText.text)) 48 | if successor is not None: 49 | successor.send(widget) 50 | 51 | 52 | @coroutine 53 | def _clicked_mediator(self, successor=None): 54 | while True: 55 | widget = (yield) 56 | if widget == self.okButton: 57 | print("OK") 58 | elif widget == self.cancelButton: 59 | print("Cancel") 60 | elif successor is not None: 61 | successor.send(widget) 62 | 63 | def mediated(Class): 64 | setattr(Class, "mediator", None) 65 | def on_change(self): 66 | if self.mediator is not None: 67 | self.mediator.send(self) 68 | setattr(Class, "on_change", on_change) 69 | return Class 70 | 71 | 72 | @mediated 73 | class Button: 74 | 75 | def __init__(self, text=""): 76 | super().__init__() 77 | self.enabled = True 78 | self.text = text 79 | 80 | 81 | def click(self): 82 | if self.enabled: 83 | self.on_change() 84 | 85 | 86 | def __str__(self): 87 | return "Button({!r}) {}".format(self.text, 88 | "enabled" if self.enabled else "disabled") 89 | 90 | 91 | @mediated 92 | class Text: 93 | 94 | def __init__(self, text=""): 95 | super().__init__() 96 | self.__text = text 97 | 98 | 99 | @property 100 | def text(self): 101 | return self.__text 102 | 103 | 104 | @text.setter 105 | def text(self, text): 106 | if self.text != text: 107 | self.__text = text 108 | self.on_change() 109 | 110 | 111 | def __str__(self): 112 | return "Text({!r})".format(self.text) 113 | 114 | 115 | def test_user_interaction_with(form): 116 | form.okButton.click() # Ignored because it is disabled 117 | print(form.okButton.enabled) # False 118 | form.nameText.text = "Fred" 119 | print(form.okButton.enabled) # False 120 | form.emailText.text = "fred@bloggers.com" 121 | print(form.okButton.enabled) # True 122 | form.okButton.click() # OK 123 | form.emailText.text = "" 124 | print(form.okButton.enabled) # False 125 | form.cancelButton.click() # Cancel 126 | 127 | 128 | if __name__ == "__main__": 129 | main() 130 | -------------------------------------------------------------------------------- /meter_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/meter_16x16.gif -------------------------------------------------------------------------------- /meter_32x32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/meter_32x32.gif -------------------------------------------------------------------------------- /meter_32x32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/meter_32x32.ico -------------------------------------------------------------------------------- /meter_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/meter_32x32.png -------------------------------------------------------------------------------- /meterclient-rpyc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import datetime 13 | import getpass 14 | import socket 15 | import sys 16 | import warnings 17 | import rpyc 18 | if sys.version_info[:2] > (3, 1): 19 | warnings.simplefilter("ignore", ResourceWarning) # For stdlib socket.py 20 | if not sys.stdout.isatty(): # For regression testing 21 | warnings.simplefilter("ignore", getpass.GetPassWarning) 22 | if sys.version_info[:2] < (3, 3): 23 | ConnectionError = socket.error 24 | 25 | 26 | HOST = "localhost" 27 | PORT = 11003 28 | 29 | 30 | def main(): 31 | username, password = login() 32 | if username is not None: 33 | try: 34 | service = rpyc.connect(HOST, PORT) 35 | manager = service.root 36 | sessionId, name = manager.login(username, password) 37 | print("Welcome, {}, to Meter RPYC".format(name)) 38 | interact(manager, sessionId) 39 | except ConnectionError as err: 40 | print("Error: Is the meter server running? {}".format(err)) 41 | 42 | 43 | def login(): 44 | loginName = getpass.getuser() 45 | username = input("Username [{}]: ".format(loginName)) 46 | if not username: 47 | username = loginName 48 | password = getpass.getpass() 49 | if not password: 50 | return None, None 51 | return username, password 52 | 53 | 54 | def interact(manager, sessionId): 55 | accepted = True 56 | while True: 57 | if accepted: 58 | meter = manager.get_job(sessionId) 59 | if not meter: 60 | print("All jobs done") 61 | break 62 | accepted, reading, reason = get_reading(meter) 63 | if not accepted: 64 | continue 65 | if (not reading or reading == -1) and not reason: 66 | break 67 | accepted = submit(manager, sessionId, meter, reading, reason) 68 | 69 | 70 | def get_reading(meter): 71 | reading = input("Reading for meter {}: ".format(meter)) 72 | if reading: 73 | try: 74 | return True, int(reading), "" 75 | except ValueError: 76 | print("Invalid reading") 77 | return False, 0, "" 78 | else: 79 | return True, -1, input("Reason for meter {}: ".format(meter)) 80 | 81 | 82 | def submit(manager, sessionId, meter, reading, reason): 83 | try: 84 | now = datetime.datetime.now() 85 | manager.submit_reading(sessionId, meter, now, reading, reason) 86 | count, total = manager.get_status(sessionId) 87 | print("Accepted: you have read {} out of {} readings".format( 88 | count, total)) 89 | return True 90 | except (EOFError, rpyc.core.vinegar.GenericException) as err: 91 | print("Error: {}".format(err)) 92 | return False 93 | 94 | 95 | if __name__ == "__main__": 96 | main() 97 | -------------------------------------------------------------------------------- /meterserver-rpc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import argparse 13 | import datetime 14 | import os 15 | import sys 16 | import xmlrpc.server 17 | if sys.version_info[:2] > (3, 1): 18 | import warnings 19 | warnings.simplefilter("ignore", ResourceWarning) # For stdlib socket.py 20 | import Meter 21 | 22 | 23 | HOST = "localhost" 24 | PORT = 11002 25 | PATH = "/meter" 26 | 27 | 28 | class RequestHandler(xmlrpc.server.SimpleXMLRPCRequestHandler): 29 | rpc_paths = (PATH,) 30 | 31 | 32 | def main(): 33 | host, port, notify = handle_commandline() 34 | manager, server = setup(host, port) 35 | print("Meter server startup at {} on {}:{}{}".format( 36 | datetime.datetime.now().isoformat()[:19], host, port, PATH)) 37 | try: 38 | if notify: 39 | with open(notify, "wb") as file: 40 | file.write(b"\n") 41 | server.serve_forever() 42 | except KeyboardInterrupt: 43 | print("\rMeter server shutdown at {}".format( 44 | datetime.datetime.now().isoformat()[:19])) 45 | manager._dump() 46 | 47 | 48 | def handle_commandline(): 49 | parser = argparse.ArgumentParser(conflict_handler="resolve") 50 | parser.add_argument("-h", "--host", default=HOST, 51 | help="hostname [default %(default)s]") 52 | parser.add_argument("-p", "--port", default=PORT, type=int, 53 | help="port number [default %(default)d]") 54 | parser.add_argument("--notify", help="specify a notification file") 55 | args = parser.parse_args() 56 | return args.host, args.port, args.notify 57 | 58 | 59 | def setup(host, port): 60 | manager = Meter.Manager() 61 | server = xmlrpc.server.SimpleXMLRPCServer((host, port), 62 | requestHandler=RequestHandler, logRequests=False) 63 | server.register_introspection_functions() 64 | for method in (manager.login, manager.get_job, manager.submit_reading, 65 | manager.get_status): 66 | server.register_function(method) 67 | return manager, server 68 | 69 | 70 | if __name__ == "__main__": 71 | main() 72 | -------------------------------------------------------------------------------- /meterserver-rpyc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import datetime 13 | import threading 14 | import rpyc 15 | import sys 16 | import MeterMT 17 | 18 | PORT = 11003 19 | 20 | Manager = MeterMT.Manager() 21 | 22 | 23 | class MeterService(rpyc.Service): 24 | 25 | def on_connect(self): 26 | pass 27 | 28 | 29 | def on_disconnect(self): 30 | pass 31 | 32 | 33 | exposed_login = Manager.login 34 | exposed_get_status = Manager.get_status 35 | exposed_get_job = Manager.get_job 36 | 37 | 38 | def exposed_submit_reading(self, sessionId, meter, when, reading, 39 | reason=""): 40 | when = datetime.datetime.strptime(str(when)[:19], 41 | "%Y-%m-%d %H:%M:%S") 42 | Manager.submit_reading(sessionId, meter, when, reading, reason) 43 | 44 | 45 | if __name__ == "__main__": 46 | import rpyc.utils.server 47 | print("Meter server startup at {}".format( 48 | datetime.datetime.now().isoformat()[:19])) 49 | server = rpyc.utils.server.ThreadedServer(MeterService, port=PORT) 50 | thread = threading.Thread(target=server.start) 51 | thread.start() 52 | try: 53 | if len(sys.argv) > 1: # Notify if called by a GUI client 54 | with open(sys.argv[1], "wb") as file: 55 | file.write(b"\n") 56 | thread.join() 57 | except KeyboardInterrupt: 58 | pass 59 | server.close() 60 | print("\rMeter server shutdown at {}".format( 61 | datetime.datetime.now().isoformat()[:19])) 62 | MeterMT.Manager._dump() 63 | -------------------------------------------------------------------------------- /observer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import datetime 13 | import itertools 14 | import sys 15 | import time 16 | 17 | 18 | def main(): 19 | historyView = HistoryView() 20 | liveView = LiveView() 21 | model = SliderModel(0, 0, 40) # minimum, value, maximum 22 | model.observers_add(historyView, liveView) # liveView produces output 23 | for value in (7, 23, 37): 24 | model.value = value # liveView produces output 25 | for value, timestamp in historyView.data: 26 | print("{:3} {}".format(value, datetime.datetime.fromtimestamp( 27 | timestamp)), file=sys.stderr) 28 | 29 | 30 | class Observed: 31 | 32 | def __init__(self): 33 | self.__observers = set() 34 | 35 | 36 | def observers_add(self, observer, *observers): 37 | for observer in itertools.chain((observer,), observers): 38 | self.__observers.add(observer) 39 | observer.update(self) 40 | 41 | 42 | def observer_discard(self, observer): 43 | self.__observers.discard(observer) 44 | 45 | 46 | def observers_notify(self): 47 | for observer in self.__observers: 48 | observer.update(self) 49 | 50 | 51 | class SliderModel(Observed): 52 | 53 | def __init__(self, minimum, value, maximum): 54 | super().__init__() 55 | # These must exist before using their property setters 56 | self.__minimum = self.__value = self.__maximum = None 57 | self.minimum = minimum 58 | self.value = value 59 | self.maximum = maximum 60 | 61 | 62 | @property 63 | def value(self): 64 | return self.__value 65 | 66 | 67 | @value.setter 68 | def value(self, value): 69 | if self.__value != value: 70 | self.__value = value 71 | self.observers_notify() 72 | 73 | 74 | @property 75 | def minimum(self): 76 | return self.__minimum 77 | 78 | 79 | @minimum.setter 80 | def minimum(self, value): 81 | if self.__minimum != value: 82 | self.__minimum = value 83 | self.observers_notify() 84 | 85 | 86 | @property 87 | def maximum(self): 88 | return self.__maximum 89 | 90 | 91 | @maximum.setter 92 | def maximum(self, value): 93 | if self.__maximum != value: 94 | self.__maximum = value 95 | self.observers_notify() 96 | 97 | 98 | class HistoryView: 99 | 100 | def __init__(self): 101 | self.data = [] 102 | 103 | 104 | def update(self, model): 105 | self.data.append((model.value, time.time())) 106 | 107 | 108 | class LiveView: 109 | 110 | def __init__(self, length=40): 111 | self.length = length 112 | 113 | 114 | def update(self, model): 115 | tippingPoint = round(model.value * self.length / 116 | (model.maximum - model.minimum)) 117 | td = ' ' 118 | html = [''] 119 | html.extend(td.format("darkblue") * tippingPoint) 120 | html.extend(td.format("cyan") * (self.length - tippingPoint)) 121 | html.append("
{}
".format(model.value)) 122 | print("".join(html)) 123 | 124 | 125 | if __name__ == "__main__": 126 | main() 127 | -------------------------------------------------------------------------------- /pointstore1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import sys 13 | import time 14 | 15 | 16 | def main(): 17 | regression = False 18 | size = int(1e6) 19 | if len(sys.argv) > 1 and sys.argv[1] == "-P": 20 | regression = True 21 | size = 20 22 | start = time.clock() 23 | points = [] 24 | for i in range(size): 25 | points.append(Point(i, i ** 2, i // 2)) 26 | end = time.clock() - start 27 | assert points[size - 1].x == size - 1 28 | assert points[size - 1].color is None 29 | print(len(points)) 30 | if not regression: # wait until we can see how much memory is used 31 | print("took {} secs to create {:,} points".format(end, size)) 32 | input("press Enter to finish") 33 | 34 | 35 | class Point: 36 | 37 | __slots__ = ("x", "y", "z", "color") 38 | 39 | def __init__(self, x=0, y=0, z=0, color=None): 40 | self.x = x 41 | self.y = y 42 | self.z = z 43 | self.color = color 44 | 45 | 46 | def __repr__(self): 47 | return "Point({0.x!r}, {0.y!r}, {0.z!r}, {0.color!r})".format(self) 48 | 49 | 50 | if __name__ == "__main__": 51 | main() 52 | -------------------------------------------------------------------------------- /pointstore2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import atexit 13 | import os 14 | import shelve 15 | import sys 16 | import tempfile 17 | import time 18 | import Qtrac 19 | 20 | 21 | def main(): 22 | regression = False 23 | size = int(1e6) 24 | if len(sys.argv) > 1 and sys.argv[1] == "-P": 25 | regression = True 26 | size = 20 27 | Qtrac.remove_if_exists(os.path.join(tempfile.gettempdir(), "point.db")) 28 | start = time.clock() 29 | points = [] 30 | for i in range(size): 31 | points.append(Point(i, i ** 2, i // 2)) 32 | end = time.clock() - start 33 | assert points[size - 1].x == size - 1 34 | print(len(points)) 35 | if not regression: # wait until we can see how much memory is used 36 | print("took {} secs to create {:,} points".format(end, size)) 37 | input("press Enter to finish") 38 | 39 | 40 | class Point: 41 | 42 | __slots__ = () 43 | __dbm = shelve.open(os.path.join(tempfile.gettempdir(), "point.db")) 44 | 45 | def __init__(self, x=0, y=0, z=0, color=None): 46 | self.x = x 47 | self.y = y 48 | self.z = z 49 | self.color = color 50 | 51 | 52 | def __key(self, name): 53 | return "{:X}:{}".format(id(self), name) 54 | 55 | 56 | def __getattr__(self, name): 57 | return Point.__dbm[self.__key(name)] 58 | 59 | 60 | def __setattr__(self, name, value): 61 | Point.__dbm[self.__key(name)] = value 62 | 63 | 64 | def __repr__(self): 65 | return "Point({0.x!r}, {0.y!r}, {0.z!r}, {0.color!r})".format(self) 66 | 67 | 68 | atexit.register(__dbm.close) 69 | 70 | 71 | if __name__ == "__main__": 72 | main() 73 | -------------------------------------------------------------------------------- /stationery1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import abc 13 | import sys 14 | 15 | 16 | def main(): 17 | pencil = SimpleItem("Pencil", 0.40) 18 | ruler = SimpleItem("Ruler", 1.60) 19 | eraser = SimpleItem("Eraser", 0.20) 20 | pencilSet = CompositeItem("Pencil Set", pencil, ruler, eraser) 21 | box = SimpleItem("Box", 1.00) 22 | boxedPencilSet = CompositeItem("Boxed Pencil Set", box, pencilSet) 23 | boxedPencilSet.add(pencil) 24 | for item in (pencil, ruler, eraser, pencilSet, boxedPencilSet): 25 | item.print() 26 | 27 | 28 | class AbstractItem(metaclass=abc.ABCMeta): 29 | 30 | @abc.abstractproperty 31 | def composite(self): 32 | pass 33 | 34 | 35 | def __iter__(self): 36 | return iter([]) 37 | 38 | 39 | class SimpleItem(AbstractItem): 40 | 41 | def __init__(self, name, price=0.00): 42 | self.name = name 43 | self.price = price 44 | 45 | 46 | @property 47 | def composite(self): 48 | return False 49 | 50 | 51 | def print(self, indent="", file=sys.stdout): 52 | print("{}${:.2f} {}".format(indent, self.price, self.name), 53 | file=file) 54 | 55 | 56 | class AbstractCompositeItem(AbstractItem): 57 | 58 | def __init__(self, *items): 59 | self.children = [] 60 | if items: 61 | self.add(*items) 62 | 63 | 64 | def add(self, first, *items): 65 | self.children.append(first) 66 | if items: 67 | self.children.extend(items) 68 | 69 | 70 | def remove(self, item): 71 | self.children.remove(item) 72 | 73 | 74 | def __iter__(self): 75 | return iter(self.children) 76 | 77 | 78 | class CompositeItem(AbstractCompositeItem): 79 | 80 | def __init__(self, name, *items): 81 | super().__init__(*items) 82 | self.name = name 83 | 84 | 85 | @property 86 | def composite(self): 87 | return True 88 | 89 | 90 | @property 91 | def price(self): 92 | return sum(item.price for item in self) 93 | 94 | 95 | def print(self, indent="", file=sys.stdout): 96 | print("{}${:.2f} {}".format(indent, self.price, self.name), 97 | file=file) 98 | for child in self: 99 | child.print(indent + " ") 100 | 101 | 102 | if __name__ == "__main__": 103 | main() 104 | -------------------------------------------------------------------------------- /stationery2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import itertools 13 | import sys 14 | 15 | 16 | def main(): 17 | pencil = Item.create("Pencil", 0.40) 18 | ruler = Item.create("Ruler", 1.60) 19 | eraser = make_item("Eraser", 0.20) 20 | pencilSet = Item.compose("Pencil Set", pencil, ruler, eraser) 21 | box = Item.create("Box", 1.00) 22 | boxedPencilSet = make_composite("Boxed Pencil Set", box, pencilSet) 23 | boxedPencilSet.add(pencil) 24 | for item in (pencil, ruler, eraser, pencilSet, boxedPencilSet): 25 | item.print() 26 | assert not pencil.composite 27 | pencil.add(eraser, box) 28 | assert pencil.composite 29 | pencil.print() 30 | pencil.remove(eraser) 31 | assert pencil.composite 32 | pencil.remove(box) 33 | assert not pencil.composite 34 | pencil.print() 35 | 36 | 37 | class Item: 38 | 39 | def __init__(self, name, *items, price=0.00): 40 | self.name = name 41 | self.price = price 42 | self.children = [] 43 | if items: 44 | self.add(*items) 45 | 46 | 47 | @classmethod 48 | def create(Class, name, price): 49 | return Class(name, price=price) 50 | 51 | 52 | @classmethod 53 | def compose(Class, name, *items): 54 | return Class(name, *items) 55 | 56 | 57 | @property 58 | def composite(self): 59 | return bool(self.children) 60 | 61 | 62 | def add(self, first, *items): 63 | self.children.extend(itertools.chain((first,), items)) 64 | 65 | 66 | def remove(self, item): 67 | self.children.remove(item) 68 | 69 | 70 | def __iter__(self): 71 | return iter(self.children) 72 | 73 | 74 | @property 75 | def price(self): 76 | return (sum(item.price for item in self) if self.children else 77 | self.__price) 78 | 79 | 80 | @price.setter 81 | def price(self, price): 82 | self.__price = price 83 | 84 | 85 | def print(self, indent="", file=sys.stdout): 86 | print("{}${:.2f} {}".format(indent, self.price, self.name), 87 | file=file) 88 | for child in self: 89 | child.print(indent + " ") 90 | 91 | 92 | def make_item(name, price): 93 | return Item(name, price=price) 94 | 95 | 96 | def make_composite(name, *items): 97 | return Item(name, *items) 98 | 99 | 100 | if __name__ == "__main__": 101 | main() 102 | -------------------------------------------------------------------------------- /tabulator1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import sys 13 | if sys.version_info[:2] < (3, 2): 14 | from xml.sax.saxutils import escape 15 | else: 16 | from html import escape 17 | 18 | 19 | WINNERS = ("Nikolai Andrianov", "Matt Biondi", "Bjørn Dæhlie", 20 | "Birgit Fischer", "Sawao Kato", "Larisa Latynina", "Carl Lewis", 21 | "Michael Phelps", "Mark Spitz", "Jenny Thompson") 22 | 23 | 24 | def main(): 25 | htmlLayout = Layout(HtmlTabulator()) 26 | for rows in range(2, 6): 27 | print(htmlLayout.tabulate(rows, WINNERS)) 28 | textLayout = Layout(TextTabulator()) 29 | for rows in range(2, 6): 30 | print(textLayout.tabulate(rows, WINNERS)) 31 | 32 | 33 | class Layout: 34 | 35 | def __init__(self, tabulator): 36 | self.tabulator = tabulator 37 | 38 | 39 | def tabulate(self, rows, items): 40 | return self.tabulator.tabulate(rows, items) 41 | 42 | 43 | class Tabulator: 44 | 45 | def tabulate(self, rows, items): 46 | raise NotImplementedError() 47 | 48 | 49 | class HtmlTabulator(Tabulator): 50 | 51 | def tabulate(self, rows, items): 52 | columns, remainder = divmod(len(items), rows) 53 | if remainder: 54 | columns += 1 55 | column = 0 56 | table = ['\n'] 57 | for item in items: 58 | if column == 0: 59 | table.append("") 60 | table.append("".format(escape(str(item)))) 61 | column += 1 62 | if column == columns: 63 | table.append("\n") 64 | column %= columns 65 | if table[-1][-1] != "\n": 66 | table.append("\n") 67 | table.append("
{}
\n") 68 | return "".join(table) 69 | 70 | 71 | class TextTabulator(Tabulator): 72 | 73 | def tabulate(self, rows, items): 74 | columns, remainder = divmod(len(items), rows) 75 | if remainder: 76 | columns += 1 77 | remainder = (rows * columns) - len(items) 78 | if remainder == columns: 79 | remainder = 0 80 | column = columnWidth = 0 81 | for item in items: 82 | columnWidth = max(columnWidth, len(item)) 83 | columnDivider = ("-" * (columnWidth + 2)) + "+" 84 | divider = "+" + (columnDivider * columns) + "\n" 85 | table = [divider] 86 | for item in items + (("",) * remainder): 87 | if column == 0: 88 | table.append("|") 89 | table.append(" {:<{}} |".format(item, columnWidth)) 90 | column += 1 91 | if column == columns: 92 | table.append("\n") 93 | column %= columns 94 | table.append(divider) 95 | return "".join(table) 96 | 97 | 98 | if __name__ == "__main__": 99 | main() 100 | -------------------------------------------------------------------------------- /tabulator2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import sys 13 | if sys.version_info[:2] < (3, 2): 14 | from xml.sax.saxutils import escape 15 | else: 16 | from html import escape 17 | 18 | 19 | WINNERS = ("Nikolai Andrianov", "Matt Biondi", "Bjørn Dæhlie", 20 | "Birgit Fischer", "Sawao Kato", "Larisa Latynina", "Carl Lewis", 21 | "Michael Phelps", "Mark Spitz", "Jenny Thompson") 22 | 23 | 24 | def main(): 25 | htmlLayout = Layout(HtmlTabulator) 26 | for rows in range(2, 6): 27 | print(htmlLayout.tabulate(rows, WINNERS)) 28 | textLayout = Layout(TextTabulator) 29 | for rows in range(2, 6): 30 | print(textLayout.tabulate(rows, WINNERS)) 31 | 32 | 33 | class Layout: 34 | 35 | def __init__(self, tabulator): 36 | self.tabulator = tabulator 37 | 38 | 39 | def tabulate(self, rows, items): 40 | return self.tabulator.tabulate(rows, items) 41 | 42 | 43 | class HtmlTabulator: 44 | 45 | @staticmethod 46 | def tabulate(rows, items): 47 | columns, remainder = divmod(len(items), rows) 48 | if remainder: 49 | columns += 1 50 | column = 0 51 | table = ['\n'] 52 | for item in items: 53 | if column == 0: 54 | table.append("") 55 | table.append("".format(escape(str(item)))) 56 | column += 1 57 | if column == columns: 58 | table.append("\n") 59 | column %= columns 60 | if table[-1][-1] != "\n": 61 | table.append("\n") 62 | table.append("
{}
\n") 63 | return "".join(table) 64 | 65 | 66 | class TextTabulator: 67 | 68 | @staticmethod 69 | def tabulate(rows, items): 70 | columns, remainder = divmod(len(items), rows) 71 | if remainder: 72 | columns += 1 73 | remainder = (rows * columns) - len(items) 74 | if remainder == columns: 75 | remainder = 0 76 | column = columnWidth = 0 77 | for item in items: 78 | columnWidth = max(columnWidth, len(item)) 79 | columnDivider = ("-" * (columnWidth + 2)) + "+" 80 | divider = "+" + (columnDivider * columns) + "\n" 81 | table = [divider] 82 | for item in items + (("",) * remainder): 83 | if column == 0: 84 | table.append("|") 85 | table.append(" {:<{}} |".format(item, columnWidth)) 86 | column += 1 87 | if column == columns: 88 | table.append("\n") 89 | column %= columns 90 | table.append(divider) 91 | return "".join(table) 92 | 93 | 94 | if __name__ == "__main__": 95 | main() 96 | -------------------------------------------------------------------------------- /tabulator3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import sys 13 | if sys.version_info[:2] < (3, 2): 14 | from xml.sax.saxutils import escape 15 | else: 16 | from html import escape 17 | 18 | 19 | WINNERS = ("Nikolai Andrianov", "Matt Biondi", "Bjørn Dæhlie", 20 | "Birgit Fischer", "Sawao Kato", "Larisa Latynina", "Carl Lewis", 21 | "Michael Phelps", "Mark Spitz", "Jenny Thompson") 22 | 23 | 24 | def main(): 25 | htmlLayout = Layout(html_tabulator) 26 | for rows in range(2, 6): 27 | print(htmlLayout.tabulate(rows, WINNERS)) 28 | textLayout = Layout(text_tabulator) 29 | for rows in range(2, 6): 30 | print(textLayout.tabulate(rows, WINNERS)) 31 | 32 | 33 | class Layout: 34 | 35 | def __init__(self, tabulator): 36 | self.tabulator = tabulator 37 | 38 | 39 | def tabulate(self, rows, items): 40 | return self.tabulator(rows, items) 41 | 42 | 43 | def html_tabulator(rows, items): 44 | columns, remainder = divmod(len(items), rows) 45 | if remainder: 46 | columns += 1 47 | column = 0 48 | table = ['\n'] 49 | for item in items: 50 | if column == 0: 51 | table.append("") 52 | table.append("".format(escape(str(item)))) 53 | column += 1 54 | if column == columns: 55 | table.append("\n") 56 | column %= columns 57 | if table[-1][-1] != "\n": 58 | table.append("\n") 59 | table.append("
{}
\n") 60 | return "".join(table) 61 | 62 | 63 | def text_tabulator(rows, items): 64 | columns, remainder = divmod(len(items), rows) 65 | if remainder: 66 | columns += 1 67 | remainder = (rows * columns) - len(items) 68 | if remainder == columns: 69 | remainder = 0 70 | column = columnWidth = 0 71 | for item in items: 72 | columnWidth = max(columnWidth, len(item)) 73 | columnDivider = ("-" * (columnWidth + 2)) + "+" 74 | divider = "+" + (columnDivider * columns) + "\n" 75 | table = [divider] 76 | for item in items + (("",) * remainder): 77 | if column == 0: 78 | table.append("|") 79 | table.append(" {:<{}} |".format(item, columnWidth)) 80 | column += 1 81 | if column == columns: 82 | table.append("\n") 83 | column %= columns 84 | table.append(divider) 85 | return "".join(table) 86 | 87 | 88 | if __name__ == "__main__": 89 | main() 90 | -------------------------------------------------------------------------------- /tabulator4.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import sys 13 | if sys.version_info[:2] < (3, 2): 14 | from xml.sax.saxutils import escape 15 | else: 16 | from html import escape 17 | 18 | 19 | WINNERS = ("Nikolai Andrianov", "Matt Biondi", "Bjørn Dæhlie", 20 | "Birgit Fischer", "Sawao Kato", "Larisa Latynina", "Carl Lewis", 21 | "Michael Phelps", "Mark Spitz", "Jenny Thompson") 22 | 23 | 24 | def main(): 25 | htmlLayout = Layout(html_tabulator) 26 | for rows in range(2, 6): 27 | print(htmlLayout.tabulate(rows, WINNERS)) 28 | textLayout = Layout(text_tabulator) 29 | for rows in range(2, 6): 30 | print(textLayout.tabulate(rows, WINNERS)) 31 | 32 | 33 | class Layout: 34 | 35 | def __init__(self, tabulator): 36 | self.tabulate = tabulator 37 | 38 | 39 | def html_tabulator(rows, items): 40 | columns, remainder = divmod(len(items), rows) 41 | if remainder: 42 | columns += 1 43 | column = 0 44 | table = ['\n'] 45 | for item in items: 46 | if column == 0: 47 | table.append("") 48 | table.append("".format(escape(str(item)))) 49 | column += 1 50 | if column == columns: 51 | table.append("\n") 52 | column %= columns 53 | if table[-1][-1] != "\n": 54 | table.append("\n") 55 | table.append("
{}
\n") 56 | return "".join(table) 57 | 58 | 59 | def text_tabulator(rows, items): 60 | columns, remainder = divmod(len(items), rows) 61 | if remainder: 62 | columns += 1 63 | remainder = (rows * columns) - len(items) 64 | if remainder == columns: 65 | remainder = 0 66 | column = columnWidth = 0 67 | for item in items: 68 | columnWidth = max(columnWidth, len(item)) 69 | columnDivider = ("-" * (columnWidth + 2)) + "+" 70 | divider = "+" + (columnDivider * columns) + "\n" 71 | table = [divider] 72 | for item in items + (("",) * remainder): 73 | if column == 0: 74 | table.append("|") 75 | table.append(" {:<{}} |".format(item, columnWidth)) 76 | column += 1 77 | if column == columns: 78 | table.append("\n") 79 | column %= columns 80 | table.append(divider) 81 | return "".join(table) 82 | 83 | 84 | if __name__ == "__main__": 85 | main() 86 | -------------------------------------------------------------------------------- /texteditor/About.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import sys 13 | import tkinter as tk 14 | import tkinter.font as tkfont 15 | if __name__ == "__main__": # For stand-alone testing with parallel TkUtil 16 | import os 17 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 18 | ".."))) 19 | import TkUtil 20 | import TkUtil.About 21 | from Globals import * 22 | 23 | 24 | class Window(TkUtil.About.Window): 25 | 26 | def __init__(self, master=None): 27 | super().__init__(master, APPNAME, height=18) 28 | 29 | 30 | def create_tags(self): 31 | super().create_tags() # for url tag 32 | # Don't modify predefined fonts! 33 | baseFont = tkfont.nametofont("TkDefaultFont") 34 | size = baseFont.cget("size") # -ve is pixels +ve is points 35 | bodyFont = tkfont.Font(family=baseFont.cget("family"), size=size) 36 | titleFont = tkfont.Font(family=baseFont.cget("family"), 37 | size=((size - 8) if size < 0 else (size + 3)), 38 | weight=tkfont.BOLD) 39 | 40 | self.text.config(font=bodyFont) 41 | self.text.tag_config("title", font=titleFont, 42 | foreground="navyblue", spacing1=3, spacing3=5) 43 | self.text.tag_config("versions", foreground="darkgreen") 44 | self.text.tag_config("above5", spacing1=5) 45 | self.text.tag_config("above3", spacing1=3) 46 | 47 | 48 | def populate_text(self): 49 | self.text.insert(tk.END, "{}\n".format(APPNAME), ("title", 50 | "center")) 51 | self.text.insert(tk.END, "Copyright © 2012-13 Qtrac Ltd. " 52 | "All rights reserved.\n", ("center",)) 53 | self.text.insert(tk.END, "www.qtrac.eu/pipbook.html\n", 54 | ("center", "url", "above5")) 55 | self.add_lines(""" 56 | This program or module is free software: you can redistribute it 57 | and/or modify it under the terms of the GNU General Public License as 58 | published by the Free Software Foundation, either version 3 of the 59 | License, or (at your option) any later version. It is provided for 60 | educational purposes and is distributed in the hope that it will be 61 | useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 62 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 63 | General Public License for more details.""") 64 | self.text.insert(tk.END, "\n" + TkUtil.about(self.master, APPNAME, 65 | VERSION), ("versions", "center", "above3")) 66 | 67 | 68 | if __name__ == "__main__": 69 | if sys.stdout.isatty(): 70 | application = tk.Tk() 71 | Window(application) 72 | application.mainloop() 73 | else: 74 | print("Loaded OK") 75 | -------------------------------------------------------------------------------- /texteditor/Globals.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | ABOUT = "About" 13 | ALIGN_CENTER = "Align Center" 14 | ALIGNCENTER = "AlignCenter" 15 | ALIGN_LEFT = "Align Left" 16 | ALIGNLEFT = "AlignLeft" 17 | ALIGNMENT_TOOLBAR = "Alignment Toolbar" 18 | ALIGN_RIGHT = "Align Right" 19 | ALIGNRIGHT = "AlignRight" 20 | APPNAME = "Text Editor" 21 | BLINK = "blink" 22 | BOLD = "Bold" 23 | COPY = "Copy" 24 | CUT = "Cut" 25 | DEFAULT_TOOLBAR_HEIGHT = 34 26 | EDIT_TOOLBAR = "Edit Toolbar" 27 | ELLIPSIS = "..." 28 | FAMILY = "family" 29 | FILE_TOOLBAR = "File Toolbar" 30 | FILE_TYPES = (("Text Files", ".txt"), ("All Files", "*")) # Tcl syntax 31 | FIND = "Find" 32 | FIND_TAG = "__FIND__" 33 | FONT = "Font" 34 | FONT_SIZE = "size" 35 | FORMAT_TOOLBAR = "Format Toolbar" 36 | GENERAL = "General" 37 | GEOMETRY = "geometry" 38 | HELP = "Help" 39 | ITALIC = "Italic" 40 | LAST_FILE = "lastfile" 41 | MAX_RECENT_FILES = 9 42 | NEW = "New" 43 | OPEN = "Open" 44 | OPEN_RECENT = "Open Recent" 45 | PAD = "0.75m" 46 | PASTE = "Paste" 47 | PREFERENCES = "Preferences" 48 | QUIT = "Quit" 49 | RECENT_FILES = "Recent Files" 50 | REDO = "Redo" 51 | REPLACE_TAG = "__REPLACE__" 52 | RESTORE_FONT = 0 53 | RESTORE_LAST_FILE = "restorelastfile" 54 | RESTORE = "restore" 55 | RESTORE_SESSION = 1 56 | RESTORE_WINDOW = 2 57 | SAVE_AS = "Save As" 58 | SAVEAS = "SaveAs" 59 | SAVE = "Save" 60 | SHAKE = 7 61 | STATUS_SHOW_TIME = 10000 # milliseconds 62 | TOOLBARMENU = "ToolbarMenu" 63 | UNDO = "Undo" 64 | VERSION = "1.0.0" 65 | VISIBLE = "visible" 66 | EXTEND = "Extend" 67 | UNEXTEND = "Unextend" 68 | -------------------------------------------------------------------------------- /texteditor/Options.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | 13 | class Options: 14 | 15 | def __init__(self, restoreWindow, restoreFont, restoreSession, blink): 16 | self.restoreWindow = restoreWindow 17 | self.restoreFont = restoreFont 18 | self.restoreSession = restoreSession 19 | self.blink = blink 20 | 21 | 22 | def __str__(self): 23 | return ("restoreWindow={} restoreFont={} restoreSession={} " 24 | "blink={}".format(self.restoreWindow, self.restoreFont, 25 | self.restoreSession, self.blink)) 26 | -------------------------------------------------------------------------------- /texteditor/images/About_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/About_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/AlignCenter_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/AlignCenter_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/AlignCenter_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/AlignCenter_24x24.gif -------------------------------------------------------------------------------- /texteditor/images/AlignLeft_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/AlignLeft_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/AlignLeft_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/AlignLeft_24x24.gif -------------------------------------------------------------------------------- /texteditor/images/AlignRight_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/AlignRight_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/AlignRight_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/AlignRight_24x24.gif -------------------------------------------------------------------------------- /texteditor/images/Bold_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Bold_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/Bold_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Bold_24x24.gif -------------------------------------------------------------------------------- /texteditor/images/Copy_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Copy_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/Copy_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Copy_24x24.gif -------------------------------------------------------------------------------- /texteditor/images/Cut_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Cut_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/Cut_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Cut_24x24.gif -------------------------------------------------------------------------------- /texteditor/images/Extend_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Extend_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/Find_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Find_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/Find_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Find_24x24.gif -------------------------------------------------------------------------------- /texteditor/images/Help_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Help_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/Italic_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Italic_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/Italic_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Italic_24x24.gif -------------------------------------------------------------------------------- /texteditor/images/New_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/New_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/New_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/New_24x24.gif -------------------------------------------------------------------------------- /texteditor/images/Open_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Open_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/Open_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Open_24x24.gif -------------------------------------------------------------------------------- /texteditor/images/Paste_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Paste_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/Paste_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Paste_24x24.gif -------------------------------------------------------------------------------- /texteditor/images/Preferences_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Preferences_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/Preferences_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Preferences_24x24.gif -------------------------------------------------------------------------------- /texteditor/images/Quit_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Quit_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/Redo_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Redo_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/Redo_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Redo_24x24.gif -------------------------------------------------------------------------------- /texteditor/images/SaveAs_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/SaveAs_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/Save_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Save_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/Save_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Save_24x24.gif -------------------------------------------------------------------------------- /texteditor/images/ToolbarMenu_3x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/ToolbarMenu_3x24.gif -------------------------------------------------------------------------------- /texteditor/images/Undo_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Undo_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/Undo_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Undo_24x24.gif -------------------------------------------------------------------------------- /texteditor/images/Unextend_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/Unextend_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/icon.png -------------------------------------------------------------------------------- /texteditor/images/icon_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/icon_16x16.gif -------------------------------------------------------------------------------- /texteditor/images/icon_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/icon_16x16.ico -------------------------------------------------------------------------------- /texteditor/images/icon_32x32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor/images/icon_32x32.gif -------------------------------------------------------------------------------- /texteditor/texteditor.pyw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import os 13 | import sys 14 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 15 | ".."))) 16 | import tkinter as tk 17 | import Main 18 | import TkUtil 19 | import TkUtil.Settings 20 | from Globals import * 21 | 22 | 23 | def main(): 24 | application = tk.Tk() 25 | application.withdraw() 26 | application.title(APPNAME) 27 | TkUtil.Settings.DOMAIN = "Qtrac" 28 | TkUtil.Settings.APPNAME = APPNAME 29 | settings = TkUtil.Settings.load() 30 | blink = settings.get_bool(GENERAL, BLINK, True) 31 | application.option_add("*tearOff", False) # Avoid ugly tear menu line 32 | application.option_add("*insertOffTime", 300 if blink else 0, 100) 33 | TkUtil.set_application_icons(application, os.path.join( 34 | os.path.dirname(os.path.realpath(__file__)), "images")) 35 | filename = sys.argv[1] if len(sys.argv) > 1 else None 36 | window = Main.Window(application, filename) 37 | application.protocol("WM_DELETE_WINDOW", window.close) 38 | application.deiconify() 39 | application.mainloop() 40 | 41 | 42 | main() 43 | -------------------------------------------------------------------------------- /texteditor2/About.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import sys 13 | import tkinter as tk 14 | import tkinter.font as tkfont 15 | if __name__ == "__main__": # For stand-alone testing with parallel TkUtil 16 | import os 17 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 18 | ".."))) 19 | import TkUtil 20 | import TkUtil.About 21 | from Globals import * 22 | 23 | 24 | class Window(TkUtil.About.Window): 25 | 26 | def __init__(self, master=None): 27 | super().__init__(master, APPNAME, height=18) 28 | 29 | 30 | def create_tags(self): 31 | super().create_tags() # for url tag 32 | # Don't modify predefined fonts! 33 | baseFont = tkfont.nametofont("TkDefaultFont") 34 | size = baseFont.cget("size") # -ve is pixels +ve is points 35 | bodyFont = tkfont.Font(family=baseFont.cget("family"), size=size) 36 | titleFont = tkfont.Font(family=baseFont.cget("family"), 37 | size=((size - 8) if size < 0 else (size + 3)), 38 | weight=tkfont.BOLD) 39 | 40 | self.text.config(font=bodyFont) 41 | self.text.tag_config("title", font=titleFont, 42 | foreground="navyblue", spacing1=3, spacing3=5) 43 | self.text.tag_config("versions", foreground="darkgreen") 44 | self.text.tag_config("above5", spacing1=5) 45 | self.text.tag_config("above3", spacing1=3) 46 | 47 | 48 | def populate_text(self): 49 | self.text.insert(tk.END, "{}\n".format(APPNAME), ("title", 50 | "center")) 51 | self.text.insert(tk.END, "Copyright © 2012-13 Qtrac Ltd. " 52 | "All rights reserved.\n", ("center",)) 53 | self.text.insert(tk.END, "www.qtrac.eu/pipbook.html\n", 54 | ("center", "url", "above5")) 55 | self.add_lines(""" 56 | This program or module is free software: you can redistribute it 57 | and/or modify it under the terms of the GNU General Public License as 58 | published by the Free Software Foundation, either version 3 of the 59 | License, or (at your option) any later version. It is provided for 60 | educational purposes and is distributed in the hope that it will be 61 | useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 62 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 63 | General Public License for more details.""") 64 | self.text.insert(tk.END, "\n" + TkUtil.about(self.master, APPNAME, 65 | VERSION), ("versions", "center", "above3")) 66 | 67 | 68 | if __name__ == "__main__": 69 | if sys.stdout.isatty(): 70 | application = tk.Tk() 71 | Window(application) 72 | application.mainloop() 73 | else: 74 | print("Loaded OK") 75 | -------------------------------------------------------------------------------- /texteditor2/Globals.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | ABOUT = "About" 13 | ALIGN_CENTER = "Align Center" 14 | ALIGNCENTER = "AlignCenter" 15 | ALIGN_LEFT = "Align Left" 16 | ALIGNLEFT = "AlignLeft" 17 | ALIGNMENT_TOOLBAR = "Alignment Toolbar" 18 | ALIGN_RIGHT = "Align Right" 19 | ALIGNRIGHT = "AlignRight" 20 | APPNAME = "Text Editor" 21 | BLINK = "blink" 22 | BOLD = "Bold" 23 | COPY = "Copy" 24 | CUT = "Cut" 25 | DEFAULT_TOOLBAR_HEIGHT = 34 26 | EDIT_TOOLBAR = "Edit Toolbar" 27 | ELLIPSIS = "..." 28 | FAMILY = "family" 29 | FILE_TOOLBAR = "File Toolbar" 30 | FILE_TYPES = (("Text Files", ".txt"), ("All Files", "*")) # Tcl syntax 31 | FIND = "Find" 32 | FIND_TAG = "__FIND__" 33 | FONT = "Font" 34 | FONT_SIZE = "size" 35 | FORMAT_TOOLBAR = "Format Toolbar" 36 | GENERAL = "General" 37 | GEOMETRY = "geometry" 38 | HELP = "Help" 39 | ITALIC = "Italic" 40 | LAST_FILE = "lastfile" 41 | MAX_RECENT_FILES = 9 42 | NEW = "New" 43 | OPEN = "Open" 44 | OPEN_RECENT = "Open Recent" 45 | PAD = "0.75m" 46 | PASTE = "Paste" 47 | PREFERENCES = "Preferences" 48 | QUIT = "Quit" 49 | RECENT_FILES = "Recent Files" 50 | REDO = "Redo" 51 | REPLACE_TAG = "__REPLACE__" 52 | RESTORE_FONT = 0 53 | RESTORE_LAST_FILE = "restorelastfile" 54 | RESTORE = "restore" 55 | RESTORE_SESSION = 1 56 | RESTORE_WINDOW = 2 57 | SAVE_AS = "Save As" 58 | SAVEAS = "SaveAs" 59 | SAVE = "Save" 60 | SHAKE = 7 61 | STATUS_SHOW_TIME = 10000 # milliseconds 62 | TOOLBARMENU = "ToolbarMenu" 63 | UNDO = "Undo" 64 | VERSION = "1.0.0" 65 | VISIBLE = "visible" 66 | EXTEND = "Extend" 67 | UNEXTEND = "Unextend" 68 | -------------------------------------------------------------------------------- /texteditor2/Options.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | 13 | class Options: 14 | 15 | def __init__(self, restoreWindow, restoreFont, restoreSession, blink): 16 | self.restoreWindow = restoreWindow 17 | self.restoreFont = restoreFont 18 | self.restoreSession = restoreSession 19 | self.blink = blink 20 | 21 | 22 | def __str__(self): 23 | return ("restoreWindow={} restoreFont={} restoreSession={} " 24 | "blink={}".format(self.restoreWindow, self.restoreFont, 25 | self.restoreSession, self.blink)) 26 | -------------------------------------------------------------------------------- /texteditor2/images/About_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/About_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/AlignCenter_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/AlignCenter_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/AlignCenter_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/AlignCenter_24x24.gif -------------------------------------------------------------------------------- /texteditor2/images/AlignLeft_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/AlignLeft_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/AlignLeft_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/AlignLeft_24x24.gif -------------------------------------------------------------------------------- /texteditor2/images/AlignRight_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/AlignRight_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/AlignRight_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/AlignRight_24x24.gif -------------------------------------------------------------------------------- /texteditor2/images/Bold_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Bold_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/Bold_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Bold_24x24.gif -------------------------------------------------------------------------------- /texteditor2/images/Copy_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Copy_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/Copy_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Copy_24x24.gif -------------------------------------------------------------------------------- /texteditor2/images/Cut_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Cut_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/Cut_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Cut_24x24.gif -------------------------------------------------------------------------------- /texteditor2/images/Extend_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Extend_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/Find_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Find_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/Find_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Find_24x24.gif -------------------------------------------------------------------------------- /texteditor2/images/Help_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Help_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/Italic_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Italic_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/Italic_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Italic_24x24.gif -------------------------------------------------------------------------------- /texteditor2/images/New_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/New_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/New_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/New_24x24.gif -------------------------------------------------------------------------------- /texteditor2/images/Open_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Open_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/Open_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Open_24x24.gif -------------------------------------------------------------------------------- /texteditor2/images/Paste_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Paste_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/Paste_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Paste_24x24.gif -------------------------------------------------------------------------------- /texteditor2/images/Preferences_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Preferences_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/Preferences_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Preferences_24x24.gif -------------------------------------------------------------------------------- /texteditor2/images/Quit_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Quit_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/Redo_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Redo_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/Redo_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Redo_24x24.gif -------------------------------------------------------------------------------- /texteditor2/images/SaveAs_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/SaveAs_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/Save_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Save_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/Save_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Save_24x24.gif -------------------------------------------------------------------------------- /texteditor2/images/ToolbarMenu_3x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/ToolbarMenu_3x24.gif -------------------------------------------------------------------------------- /texteditor2/images/Undo_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Undo_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/Undo_24x24.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Undo_24x24.gif -------------------------------------------------------------------------------- /texteditor2/images/Unextend_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/Unextend_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/drag.cur: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/drag.cur -------------------------------------------------------------------------------- /texteditor2/images/drag.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/drag.gif -------------------------------------------------------------------------------- /texteditor2/images/drag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/drag.png -------------------------------------------------------------------------------- /texteditor2/images/drag.xbm: -------------------------------------------------------------------------------- 1 | #define drag_width 16 2 | #define drag_height 16 3 | #define drag_x_hot 0 4 | #define drag_y_hot 0 5 | static char drag_bits[] = { 6 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 7 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 8 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }; 9 | -------------------------------------------------------------------------------- /texteditor2/images/drag_mask.xbm: -------------------------------------------------------------------------------- 1 | #define drag_width 16 2 | #define drag_height 16 3 | static unsigned char drag_bits[] = { 4 | 0xff, 0x01, 0xff, 0x00, 0x7f, 0x00, 0x1f, 0x00, 0x3f, 0x00, 0x77, 0x00, 5 | 0xe7, 0xff, 0xc3, 0xff, 0xc1, 0xff, 0xc0, 0xff, 0xc0, 0xff, 0xc0, 0xff, 6 | 0xc0, 0xff, 0xc0, 0xff, 0xc0, 0xff, 0xc0, 0xff } ; 7 | -------------------------------------------------------------------------------- /texteditor2/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/icon.png -------------------------------------------------------------------------------- /texteditor2/images/icon_16x16.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/icon_16x16.gif -------------------------------------------------------------------------------- /texteditor2/images/icon_16x16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/icon_16x16.ico -------------------------------------------------------------------------------- /texteditor2/images/icon_32x32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/texteditor2/images/icon_32x32.gif -------------------------------------------------------------------------------- /texteditor2/texteditor.pyw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import os 13 | import sys 14 | sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), 15 | ".."))) 16 | import tkinter as tk 17 | import Main 18 | import TkUtil 19 | import TkUtil.Settings 20 | from Globals import * 21 | 22 | 23 | def main(): 24 | application = tk.Tk() 25 | application.withdraw() 26 | application.title(APPNAME) 27 | TkUtil.Settings.DOMAIN = "Qtrac" 28 | TkUtil.Settings.APPNAME = APPNAME 29 | settings = TkUtil.Settings.load() 30 | blink = settings.get_bool(GENERAL, BLINK, True) 31 | application.option_add("*tearOff", False) # Avoid ugly tear menu line 32 | application.option_add("*insertOffTime", 300 if blink else 0, 100) 33 | TkUtil.set_application_icons(application, os.path.join( 34 | os.path.dirname(os.path.realpath(__file__)), "images")) 35 | filename = sys.argv[1] if len(sys.argv) > 1 else None 36 | window = Main.Window(application, filename) 37 | application.protocol("WM_DELETE_WINDOW", window.close) 38 | application.deiconify() 39 | application.mainloop() 40 | 41 | 42 | main() 43 | -------------------------------------------------------------------------------- /tilefall_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/tilefall_16x16.png -------------------------------------------------------------------------------- /tilefall_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daleathan/PythonInPractice-MarkSummerfield/62451d986401d7e2ad034b22c345c3e073798947/tilefall_32x32.png -------------------------------------------------------------------------------- /whatsnew-c.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import argparse 13 | import multiprocessing 14 | import os 15 | import sys 16 | import tempfile 17 | import webbrowser 18 | import Feed 19 | import Qtrac 20 | 21 | 22 | def main(): 23 | limit, concurrency = handle_commandline() 24 | Qtrac.report("starting...") 25 | datafile = os.path.join(os.path.dirname(__file__), "whatsnew.dat") 26 | filename = os.path.join(tempfile.gettempdir(), "whatsnew.html") 27 | canceled = False 28 | with open(filename, "wt", encoding="utf-8") as file: 29 | write_header(file) 30 | pipeline = create_pipeline(limit, concurrency, file) 31 | try: 32 | for i, feed in enumerate(Feed.iter(datafile)): 33 | pipeline.send((feed, i % concurrency)) 34 | except KeyboardInterrupt: 35 | Qtrac.report("canceling...") 36 | canceled = True 37 | write_footer(file, results.ok, results.todo, canceled, 38 | concurrency) 39 | if not canceled: 40 | webbrowser.open(filename) 41 | 42 | 43 | def handle_commandline(): 44 | parser = argparse.ArgumentParser() 45 | parser.add_argument("-l", "--limit", type=int, default=0, 46 | help="the maximum items per feed [default: unlimited]") 47 | parser.add_argument("-c", "--concurrency", type=int, 48 | default=multiprocessing.cpu_count() * 4, 49 | help="specify the concurrency (for debugging and " 50 | "timing) [default: %(default)d]") 51 | args = parser.parse_args() 52 | return args.limit, args.concurrency 53 | 54 | 55 | def write_header(file): 56 | file.write("\n") 57 | file.write("What's New\n") 58 | file.write("

What's New

\n") 59 | 60 | 61 | def write_footer(file, ok, todo, canceled, concurrency): 62 | file.write("\n") 63 | Qtrac.report("read {}/{} feeds using {} coroutines{}".format(ok, todo, 64 | concurrency, " [canceled]" if canceled else "")) 65 | print() 66 | 67 | 68 | def create_pipeline(limit, concurrency, file): 69 | pipeline = None 70 | sink = results(file) 71 | for who in range(concurrency): 72 | pipeline = reader(pipeline, sink, limit, who) 73 | return pipeline 74 | 75 | 76 | @Qtrac.coroutine 77 | def reader(receiver, sink, limit, me): 78 | while True: 79 | feed, who = (yield) 80 | if who == me: 81 | ok, result = Feed.read(feed, limit) 82 | if not ok: 83 | Qtrac.report(result, True) 84 | result = None 85 | else: 86 | Qtrac.report("read {} at {}".format(feed.title, feed.url)) 87 | sink.send(result) 88 | elif receiver is not None: 89 | receiver.send((feed, who)) 90 | 91 | 92 | @Qtrac.coroutine 93 | def results(file): 94 | while True: 95 | result = (yield) 96 | results.todo += 1 97 | if result is not None: 98 | results.ok += 1 99 | for item in result: 100 | file.write(item) 101 | results.todo = results.ok = 0 102 | 103 | 104 | if __name__ == "__main__": 105 | main() 106 | -------------------------------------------------------------------------------- /whatsnew-q-m.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import argparse 13 | import multiprocessing 14 | import os 15 | import tempfile 16 | import webbrowser 17 | import Feed 18 | import Qtrac 19 | 20 | 21 | def main(): 22 | limit, concurrency = handle_commandline() 23 | Qtrac.report("starting...") 24 | filename = os.path.join(os.path.dirname(__file__), "whatsnew.dat") 25 | jobs = multiprocessing.JoinableQueue() 26 | results = multiprocessing.Queue() 27 | create_processes(limit, jobs, results, concurrency) 28 | todo = add_jobs(filename, jobs) 29 | process(todo, jobs, results, concurrency) 30 | 31 | 32 | def handle_commandline(): 33 | parser = argparse.ArgumentParser() 34 | parser.add_argument("-l", "--limit", type=int, default=0, 35 | help="the maximum items per feed [default: unlimited]") 36 | parser.add_argument("-c", "--concurrency", type=int, 37 | default=multiprocessing.cpu_count() * 4, 38 | help="specify the concurrency (for debugging and " 39 | "timing) [default: %(default)d]") 40 | args = parser.parse_args() 41 | return args.limit, args.concurrency 42 | 43 | 44 | def create_processes(limit, jobs, results, concurrency): 45 | for _ in range(concurrency): 46 | process = multiprocessing.Process(target=worker, args=(limit, jobs, 47 | results)) 48 | process.daemon = True 49 | process.start() 50 | 51 | 52 | def worker(limit, jobs, results): 53 | while True: 54 | try: 55 | feed = jobs.get() 56 | ok, result = Feed.read(feed, limit) 57 | if not ok: 58 | Qtrac.report(result, True) 59 | elif result is not None: 60 | Qtrac.report("read {}".format(result[0][4:-6])) 61 | results.put(result) 62 | finally: 63 | jobs.task_done() 64 | 65 | 66 | def add_jobs(filename, jobs): 67 | for todo, feed in enumerate(Feed.iter(filename), start=1): 68 | jobs.put(feed) 69 | return todo 70 | 71 | 72 | def process(todo, jobs, results, concurrency): 73 | canceled = False 74 | try: 75 | jobs.join() # Wait for all the work to be done 76 | except KeyboardInterrupt: # May not work on Windows 77 | Qtrac.report("canceling...") 78 | canceled = True 79 | if canceled: 80 | done = results.qsize() 81 | else: 82 | done, filename = output(results) 83 | Qtrac.report("read {}/{} feeds using {} threads{}".format(done, todo, 84 | concurrency, " [canceled]" if canceled else "")) 85 | print() 86 | if not canceled: 87 | webbrowser.open(filename) 88 | 89 | 90 | def output(results): 91 | done = 0 92 | filename = os.path.join(tempfile.gettempdir(), "whatsnew.html") 93 | with open(filename, "wt", encoding="utf-8") as file: 94 | file.write("\n") 95 | file.write("What's New\n") 96 | file.write("

What's New

\n") 97 | while not results.empty(): # Safe because all jobs have finished 98 | result = results.get_nowait() 99 | done += 1 100 | for item in result: 101 | file.write(item) 102 | file.write("\n") 103 | return done, filename 104 | 105 | 106 | if __name__ == "__main__": 107 | main() 108 | -------------------------------------------------------------------------------- /whatsnew-q.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import argparse 13 | import multiprocessing 14 | import os 15 | import queue 16 | import tempfile 17 | import threading 18 | import webbrowser 19 | import Feed 20 | import Qtrac 21 | 22 | 23 | def main(): 24 | limit, concurrency = handle_commandline() 25 | Qtrac.report("starting...") 26 | filename = os.path.join(os.path.dirname(__file__), "whatsnew.dat") 27 | jobs = queue.Queue() 28 | results = queue.Queue() 29 | create_threads(limit, jobs, results, concurrency) 30 | todo = add_jobs(filename, jobs) 31 | process(todo, jobs, results, concurrency) 32 | 33 | 34 | def handle_commandline(): 35 | parser = argparse.ArgumentParser() 36 | parser.add_argument("-l", "--limit", type=int, default=0, 37 | help="the maximum items per feed [default: unlimited]") 38 | parser.add_argument("-c", "--concurrency", type=int, 39 | default=multiprocessing.cpu_count() * 4, 40 | help="specify the concurrency (for debugging and " 41 | "timing) [default: %(default)d]") 42 | args = parser.parse_args() 43 | return args.limit, args.concurrency 44 | 45 | 46 | def create_threads(limit, jobs, results, concurrency): 47 | for _ in range(concurrency): 48 | thread = threading.Thread(target=worker, args=(limit, jobs, 49 | results)) 50 | thread.daemon = True 51 | thread.start() 52 | 53 | 54 | def worker(limit, jobs, results): 55 | while True: 56 | try: 57 | feed = jobs.get() 58 | ok, result = Feed.read(feed, limit) 59 | if not ok: 60 | Qtrac.report(result, True) 61 | elif result is not None: 62 | Qtrac.report("read {}".format(result[0][4:-6])) 63 | results.put(result) 64 | finally: 65 | jobs.task_done() 66 | 67 | 68 | def add_jobs(filename, jobs): 69 | for todo, feed in enumerate(Feed.iter(filename), start=1): 70 | jobs.put(feed) 71 | return todo 72 | 73 | 74 | def process(todo, jobs, results, concurrency): 75 | canceled = False 76 | try: 77 | jobs.join() # Wait for all the work to be done 78 | except KeyboardInterrupt: # May not work on Windows 79 | Qtrac.report("canceling...") 80 | canceled = True 81 | if canceled: 82 | done = results.qsize() 83 | else: 84 | done, filename = output(results) 85 | Qtrac.report("read {}/{} feeds using {} threads{}".format(done, todo, 86 | concurrency, " [canceled]" if canceled else "")) 87 | print() 88 | if not canceled: 89 | webbrowser.open(filename) 90 | 91 | 92 | def output(results): 93 | done = 0 94 | filename = os.path.join(tempfile.gettempdir(), "whatsnew.html") 95 | with open(filename, "wt", encoding="utf-8") as file: 96 | file.write("\n") 97 | file.write("What's New\n") 98 | file.write("

What's New

\n") 99 | while not results.empty(): # Safe because all jobs have finished 100 | result = results.get_nowait() 101 | done += 1 102 | for item in result: 103 | file.write(item) 104 | file.write("\n") 105 | return done, filename 106 | 107 | 108 | if __name__ == "__main__": 109 | main() 110 | -------------------------------------------------------------------------------- /whatsnew.dat: -------------------------------------------------------------------------------- 1 | # Format: Title\nURL UTF-8 2 | 3 | ActiveState Blog 4 | http://feeds.feedburner.com/activestate/blog?format=xml 5 | 6 | BBC News - Technology 7 | http://feeds.bbci.co.uk/news/technology/rss.xml 8 | 9 | CNET 10 | http://www.cnet.com/rss/ 11 | 12 | ComputerWeekly 13 | http://www.computerweekly.com/rss/All-Computer-Weekly-content.xml 14 | 15 | comp.lang.python.announce 16 | http://groups.google.com/group/comp.lang.python.announce/feeds 17 | 18 | Electronic Frontier Foundation 19 | https://www.eff.org/rss/updates.xml 20 | 21 | End Software Patents: News 22 | http://news.swpat.org/feed/ 23 | 24 | Free Software Daily 25 | http://www.fsdaily.com/feed/published/All 26 | 27 | Free Software Foundation 28 | http://static.fsf.org/fsforg/rss/news.xml 29 | 30 | The Guardian - Technology 31 | http://feeds.pinboard.in/rss/u:guardiantech/ 32 | 33 | IBM developerWorks: Linux 34 | http://www.ibm.com/developerworks/views/linux/rss/libraryview.jsp 35 | 36 | InformIT: OnOpenSource 37 | http://www.informit.com/podcasts/index_rss.aspx?c=11 38 | 39 | Linux.com 40 | http://www.linux.com/rss/feeds.php 41 | 42 | Linux Today 43 | http://feeds.feedburner.com/linuxtoday/linux?format=xml 44 | 45 | Linux Weekly News - Headlines 46 | http://lwn.net/headlines/rss 47 | 48 | LXer Linux News 49 | http://lxer.com/module/newswire/headlines.rss 50 | 51 | O'Reilly Network Developer News 52 | http://www.oreillynet.com/pub/q/830 53 | 54 | Open Source Alternative New 55 | http://feeds.feedburner.com/OpenSourceAlternative 56 | 57 | Open Source Information News 58 | http://www.opensourcesinfo.org/journal/rss.xml 59 | 60 | Open Source Initiative 61 | http://opensource.org/blog/feed 62 | 63 | OpenSource.com 64 | http://opensource.com/feed 65 | 66 | OSDir.com 67 | http://osdir.com/rss.php 68 | 69 | Python News 70 | http://www.python.org/channews.rdf 71 | 72 | Python Package Index (PyPI) 73 | http://pypi.python.org/pypi?:action=rss 74 | 75 | The Register 76 | http://www.theregister.co.uk/headlines.atom 77 | 78 | Reuters: Technology News 79 | http://feeds.reuters.com/reuters/technologyNews?format=xml 80 | 81 | ScienceDaily: Robot News 82 | http://feeds.sciencedaily.com/sciencedaily/computers_math/robotics?format=xml 83 | 84 | Slashdot 85 | http://rss.slashdot.org/Slashdot/slashdot 86 | 87 | SourceForge Community Blog 88 | http://sourceforge.net/blog/feed/ 89 | 90 | Techradar - News 91 | http://feeds.feedburner.com/techradar/allnews?format=xml 92 | 93 | W3C Open Source Software 94 | http://www.w3.org/2003/05/Software/Overview.rss 95 | 96 | Wired Top Stories 97 | http://feeds.wired.com/wired/index?format=xml 98 | 99 | ZDNet 100 | http://www.zdnet.com/news/rss.xml 101 | -------------------------------------------------------------------------------- /whatsnew.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it 4 | # and/or modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. It is provided for 7 | # educational purposes and is distributed in the hope that it will be 8 | # useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import argparse 13 | import os 14 | import sys 15 | import tempfile 16 | import webbrowser 17 | import Feed 18 | import Qtrac 19 | 20 | 21 | def main(): 22 | limit = handle_commandline() 23 | filename = os.path.join(tempfile.gettempdir(), "whatsnew.html") 24 | canceled = False 25 | todo = done = 0 26 | with open(filename, "wt", encoding="utf-8") as file: 27 | file.write("\n") 28 | file.write("What's New\n") 29 | file.write("

What's New

\n") 30 | todo, done, canceled = write_body(file, limit) 31 | file.write("\n") 32 | Qtrac.report("read {}/{} feeds{}".format(done, todo, " [canceled]" if 33 | canceled else "")) 34 | print() 35 | if not canceled: 36 | webbrowser.open(filename) 37 | 38 | 39 | def handle_commandline(): 40 | parser = argparse.ArgumentParser() 41 | parser.add_argument("-l", "--limit", type=int, default=0, 42 | help="the maximum items per feed [default: unlimited]") 43 | args = parser.parse_args() 44 | return args.limit 45 | 46 | 47 | def write_body(file, limit): 48 | canceled = False 49 | todo = done = 0 50 | filename = os.path.join(os.path.dirname(__file__), "whatsnew.dat") 51 | for feed in Feed.iter(filename): 52 | todo += 1 53 | try: 54 | ok, result = Feed.read(feed, limit) 55 | if not ok: 56 | Qtrac.report(result, True) 57 | elif result is not None: 58 | Qtrac.report("read {} at {}".format(feed.title, feed.url)) 59 | for item in result: 60 | file.write(item) 61 | done += 1 62 | except KeyboardInterrupt: 63 | Qtrac.report("canceling...") 64 | canceled = True 65 | break 66 | return todo, done, canceled 67 | 68 | 69 | if __name__ == "__main__": 70 | main() 71 | -------------------------------------------------------------------------------- /wordcount1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as published 5 | # by the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. It is provided for educational 7 | # purposes and is distributed in the hope that it will be useful, but 8 | # WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import html.parser 13 | import os 14 | import re 15 | import sys 16 | 17 | 18 | def main(): 19 | if len(sys.argv) == 1 or sys.argv[1] in {"-h", "--help"}: 20 | print("usage: {} ".format(os.path.basename(sys.argv[0]))) 21 | sys.exit(1) 22 | count_words_in_files(sys.argv[1:]) 23 | 24 | 25 | def count_words_in_files(files): 26 | total = 0 27 | for filename in files: 28 | count = count_words(filename) 29 | if count is not None: 30 | total += count 31 | print("{:9,} words in {}".format(count, filename)) 32 | print("total: {:,} words".format(total)) 33 | 34 | 35 | def count_words(filename): 36 | for wordCounter in (PlainTextWordCounter, HtmlWordCounter): 37 | if wordCounter.can_count(filename): 38 | return wordCounter.count(filename) 39 | 40 | 41 | class AbstractWordCounter: 42 | 43 | @staticmethod 44 | def can_count(filename): 45 | raise NotImplementedError() 46 | 47 | 48 | @staticmethod 49 | def count(filename): 50 | raise NotImplementedError() 51 | 52 | 53 | class PlainTextWordCounter(AbstractWordCounter): 54 | 55 | @staticmethod 56 | def can_count(filename): 57 | return filename.lower().endswith(".txt") 58 | 59 | 60 | @staticmethod 61 | def count(filename): 62 | if not PlainTextWordCounter.can_count(filename): 63 | return 0 64 | regex = re.compile(r"\w+") 65 | total = 0 66 | with open(filename, encoding="utf-8") as file: 67 | for line in file: 68 | for _ in regex.finditer(line): 69 | total += 1 70 | return total 71 | 72 | 73 | class HtmlWordCounter(AbstractWordCounter): 74 | 75 | class __HtmlParser(html.parser.HTMLParser): 76 | 77 | def __init__(self): 78 | super().__init__() 79 | self.regex = re.compile(r"\w+") 80 | self.inText = True 81 | self.text = [] 82 | self.count = 0 83 | 84 | 85 | def handle_starttag(self, tag, attrs): 86 | if tag in {"script", "style"}: 87 | self.inText = False 88 | 89 | 90 | def handle_endtag(self, tag): 91 | if tag in {"script", "style"}: 92 | self.inText = True 93 | else: 94 | for _ in self.regex.finditer(" ".join(self.text)): 95 | self.count += 1 96 | self.text = [] 97 | 98 | 99 | def handle_data(self, text): 100 | if self.inText: 101 | text = text.rstrip() 102 | if text: 103 | self.text.append(text) 104 | 105 | 106 | @staticmethod 107 | def can_count(filename): 108 | return filename.lower().endswith((".htm", ".html")) 109 | 110 | 111 | @staticmethod 112 | def count(filename): 113 | if not HtmlWordCounter.can_count(filename): 114 | return 0 115 | parser = HtmlWordCounter.__HtmlParser() 116 | with open(filename, encoding="utf-8") as file: 117 | parser.feed(file.read()) 118 | return parser.count 119 | 120 | 121 | if __name__ == "__main__": 122 | main() 123 | -------------------------------------------------------------------------------- /wordcount2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright © 2012-13 Qtrac Ltd. All rights reserved. 3 | # This program or module is free software: you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as published 5 | # by the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. It is provided for educational 7 | # purposes and is distributed in the hope that it will be useful, but 8 | # WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | 12 | import abc 13 | import html.parser 14 | import os 15 | import re 16 | import sys 17 | 18 | 19 | def main(): 20 | if len(sys.argv) == 1 or sys.argv[1] in {"-h", "--help"}: 21 | print("usage: {} ".format(os.path.basename(sys.argv[0]))) 22 | sys.exit(1) 23 | count_words_in_files(sys.argv[1:]) 24 | 25 | 26 | def count_words_in_files(files): 27 | total = 0 28 | for filename in files: 29 | count = count_words(filename) 30 | if count is not None: 31 | total += count 32 | print("{:9,} words in {}".format(count, filename)) 33 | print("total: {:,} words".format(total)) 34 | 35 | 36 | def count_words(filename): 37 | for wordCounter in (PlainTextWordCounter, HtmlWordCounter): 38 | if wordCounter.can_count(filename): 39 | return wordCounter.count(filename) 40 | 41 | 42 | class AbstractWordCounter( 43 | metaclass=abc.ABCMeta): 44 | 45 | @staticmethod 46 | @abc.abstractmethod 47 | def can_count(filename): 48 | pass 49 | 50 | 51 | @staticmethod 52 | @abc.abstractmethod 53 | def count(filename): 54 | pass 55 | 56 | 57 | class PlainTextWordCounter(AbstractWordCounter): 58 | 59 | @staticmethod 60 | def can_count(filename): 61 | return filename.lower().endswith(".txt") 62 | 63 | 64 | @staticmethod 65 | def count(filename): 66 | if not PlainTextWordCounter.can_count(filename): 67 | return 0 68 | regex = re.compile(r"\w+") 69 | total = 0 70 | with open(filename, encoding="utf-8") as file: 71 | for line in file: 72 | for _ in regex.finditer(line): 73 | total += 1 74 | return total 75 | 76 | 77 | class HtmlWordCounter(AbstractWordCounter): 78 | 79 | class __HtmlParser(html.parser.HTMLParser): 80 | 81 | def __init__(self): 82 | super().__init__() 83 | self.regex = re.compile(r"\w+") 84 | self.inText = True 85 | self.text = [] 86 | self.count = 0 87 | 88 | 89 | def handle_starttag(self, tag, attrs): 90 | if tag in {"script", "style"}: 91 | self.inText = False 92 | 93 | 94 | def handle_endtag(self, tag): 95 | if tag in {"script", "style"}: 96 | self.inText = True 97 | else: 98 | for _ in self.regex.finditer(" ".join(self.text)): 99 | self.count += 1 100 | self.text = [] 101 | 102 | 103 | def handle_data(self, text): 104 | if self.inText: 105 | text = text.rstrip() 106 | if text: 107 | self.text.append(text) 108 | 109 | 110 | @staticmethod 111 | def can_count(filename): 112 | return filename.lower().endswith((".htm", ".html")) 113 | 114 | 115 | @staticmethod 116 | def count(filename): 117 | if not HtmlWordCounter.can_count(filename): 118 | return 0 119 | parser = HtmlWordCounter.__HtmlParser() 120 | with open(filename, encoding="utf-8") as file: 121 | parser.feed(file.read()) 122 | return parser.count 123 | 124 | 125 | if __name__ == "__main__": 126 | main() 127 | --------------------------------------------------------------------------------