| 1 | """Pynche -- The PYthon Natural Color and Hue Editor. | 
|---|
| 2 |  | 
|---|
| 3 | Contact: %(AUTHNAME)s | 
|---|
| 4 | Email:   %(AUTHEMAIL)s | 
|---|
| 5 | Version: %(__version__)s | 
|---|
| 6 |  | 
|---|
| 7 | Pynche is based largely on a similar color editor I wrote years ago for the | 
|---|
| 8 | SunView window system.  That editor was called ICE: the Interactive Color | 
|---|
| 9 | Editor.  I'd always wanted to port the editor to X but didn't feel like | 
|---|
| 10 | hacking X and C code to do it.  Fast forward many years, to where Python + | 
|---|
| 11 | Tkinter provides such a nice programming environment, with enough power, that | 
|---|
| 12 | I finally buckled down and implemented it.  I changed the name because these | 
|---|
| 13 | days, too many other systems have the acronym `ICE'. | 
|---|
| 14 |  | 
|---|
| 15 | This program currently requires Python 2.2 with Tkinter. | 
|---|
| 16 |  | 
|---|
| 17 | Usage: %(PROGRAM)s [-d file] [-i file] [-X] [-v] [-h] [initialcolor] | 
|---|
| 18 |  | 
|---|
| 19 | Where: | 
|---|
| 20 | --database file | 
|---|
| 21 | -d file | 
|---|
| 22 | Alternate location of a color database file | 
|---|
| 23 |  | 
|---|
| 24 | --initfile file | 
|---|
| 25 | -i file | 
|---|
| 26 | Alternate location of the initialization file.  This file contains a | 
|---|
| 27 | persistent database of the current Pynche options and color.  This | 
|---|
| 28 | means that Pynche restores its option settings and current color when | 
|---|
| 29 | it restarts, using this file (unless the -X option is used).  The | 
|---|
| 30 | default is ~/.pynche | 
|---|
| 31 |  | 
|---|
| 32 | --ignore | 
|---|
| 33 | -X | 
|---|
| 34 | Ignore the initialization file when starting up.  Pynche will still | 
|---|
| 35 | write the current option settings to this file when it quits. | 
|---|
| 36 |  | 
|---|
| 37 | --version | 
|---|
| 38 | -v | 
|---|
| 39 | print the version number and exit | 
|---|
| 40 |  | 
|---|
| 41 | --help | 
|---|
| 42 | -h | 
|---|
| 43 | print this message | 
|---|
| 44 |  | 
|---|
| 45 | initialcolor | 
|---|
| 46 | initial color, as a color name or #RRGGBB format | 
|---|
| 47 | """ | 
|---|
| 48 |  | 
|---|
| 49 | __version__ = '1.4.1' | 
|---|
| 50 |  | 
|---|
| 51 | import sys | 
|---|
| 52 | import os | 
|---|
| 53 | import getopt | 
|---|
| 54 | import ColorDB | 
|---|
| 55 |  | 
|---|
| 56 | from PyncheWidget import PyncheWidget | 
|---|
| 57 | from Switchboard import Switchboard | 
|---|
| 58 | from StripViewer import StripViewer | 
|---|
| 59 | from ChipViewer import ChipViewer | 
|---|
| 60 | from TypeinViewer import TypeinViewer | 
|---|
| 61 |  | 
|---|
| 62 |  | 
|---|
| 63 |  | 
|---|
| 64 |  | 
|---|
| 65 | PROGRAM = sys.argv[0] | 
|---|
| 66 | AUTHNAME = 'Barry Warsaw' | 
|---|
| 67 | AUTHEMAIL = 'barry@python.org' | 
|---|
| 68 |  | 
|---|
| 69 | # Default locations of rgb.txt or other textual color database | 
|---|
| 70 | RGB_TXT = [ | 
|---|
| 71 | # Solaris OpenWindows | 
|---|
| 72 | '/usr/openwin/lib/rgb.txt', | 
|---|
| 73 | # Linux | 
|---|
| 74 | '/usr/lib/X11/rgb.txt', | 
|---|
| 75 | # The X11R6.4 rgb.txt file | 
|---|
| 76 | os.path.join(sys.path[0], 'X/rgb.txt'), | 
|---|
| 77 | # add more here | 
|---|
| 78 | ] | 
|---|
| 79 |  | 
|---|
| 80 |  | 
|---|
| 81 |  | 
|---|
| 82 |  | 
|---|
| 83 | # Do this because PyncheWidget.py wants to get at the interpolated docstring | 
|---|
| 84 | # too, for its Help menu. | 
|---|
| 85 | def docstring(): | 
|---|
| 86 | return __doc__ % globals() | 
|---|
| 87 |  | 
|---|
| 88 |  | 
|---|
| 89 | def usage(code, msg=''): | 
|---|
| 90 | print docstring() | 
|---|
| 91 | if msg: | 
|---|
| 92 | print msg | 
|---|
| 93 | sys.exit(code) | 
|---|
| 94 |  | 
|---|
| 95 |  | 
|---|
| 96 |  | 
|---|
| 97 |  | 
|---|
| 98 | def initial_color(s, colordb): | 
|---|
| 99 | # function called on every color | 
|---|
| 100 | def scan_color(s, colordb=colordb): | 
|---|
| 101 | try: | 
|---|
| 102 | r, g, b = colordb.find_byname(s) | 
|---|
| 103 | except ColorDB.BadColor: | 
|---|
| 104 | try: | 
|---|
| 105 | r, g, b = ColorDB.rrggbb_to_triplet(s) | 
|---|
| 106 | except ColorDB.BadColor: | 
|---|
| 107 | return None, None, None | 
|---|
| 108 | return r, g, b | 
|---|
| 109 | # | 
|---|
| 110 | # First try the passed in color | 
|---|
| 111 | r, g, b = scan_color(s) | 
|---|
| 112 | if r is None: | 
|---|
| 113 | # try the same color with '#' prepended, since some shells require | 
|---|
| 114 | # this to be escaped, which is a pain | 
|---|
| 115 | r, g, b = scan_color('#' + s) | 
|---|
| 116 | if r is None: | 
|---|
| 117 | print 'Bad initial color, using gray50:', s | 
|---|
| 118 | r, g, b = scan_color('gray50') | 
|---|
| 119 | if r is None: | 
|---|
| 120 | usage(1, 'Cannot find an initial color to use') | 
|---|
| 121 | # does not return | 
|---|
| 122 | return r, g, b | 
|---|
| 123 |  | 
|---|
| 124 |  | 
|---|
| 125 |  | 
|---|
| 126 |  | 
|---|
| 127 | def build(master=None, initialcolor=None, initfile=None, ignore=None, | 
|---|
| 128 | dbfile=None): | 
|---|
| 129 | # create all output widgets | 
|---|
| 130 | s = Switchboard(not ignore and initfile) | 
|---|
| 131 | # defer to the command line chosen color database, falling back to the one | 
|---|
| 132 | # in the .pynche file. | 
|---|
| 133 | if dbfile is None: | 
|---|
| 134 | dbfile = s.optiondb().get('DBFILE') | 
|---|
| 135 | # find a parseable color database | 
|---|
| 136 | colordb = None | 
|---|
| 137 | files = RGB_TXT[:] | 
|---|
| 138 | if dbfile is None: | 
|---|
| 139 | dbfile = files.pop() | 
|---|
| 140 | while colordb is None: | 
|---|
| 141 | try: | 
|---|
| 142 | colordb = ColorDB.get_colordb(dbfile) | 
|---|
| 143 | except (KeyError, IOError): | 
|---|
| 144 | pass | 
|---|
| 145 | if colordb is None: | 
|---|
| 146 | if not files: | 
|---|
| 147 | break | 
|---|
| 148 | dbfile = files.pop(0) | 
|---|
| 149 | if not colordb: | 
|---|
| 150 | usage(1, 'No color database file found, see the -d option.') | 
|---|
| 151 | s.set_colordb(colordb) | 
|---|
| 152 |  | 
|---|
| 153 | # create the application window decorations | 
|---|
| 154 | app = PyncheWidget(__version__, s, master=master) | 
|---|
| 155 | w = app.window() | 
|---|
| 156 |  | 
|---|
| 157 | # these built-in viewers live inside the main Pynche window | 
|---|
| 158 | s.add_view(StripViewer(s, w)) | 
|---|
| 159 | s.add_view(ChipViewer(s, w)) | 
|---|
| 160 | s.add_view(TypeinViewer(s, w)) | 
|---|
| 161 |  | 
|---|
| 162 | # get the initial color as components and set the color on all views.  if | 
|---|
| 163 | # there was no initial color given on the command line, use the one that's | 
|---|
| 164 | # stored in the option database | 
|---|
| 165 | if initialcolor is None: | 
|---|
| 166 | optiondb = s.optiondb() | 
|---|
| 167 | red = optiondb.get('RED') | 
|---|
| 168 | green = optiondb.get('GREEN') | 
|---|
| 169 | blue = optiondb.get('BLUE') | 
|---|
| 170 | # but if there wasn't any stored in the database, use grey50 | 
|---|
| 171 | if red is None or blue is None or green is None: | 
|---|
| 172 | red, green, blue = initial_color('grey50', colordb) | 
|---|
| 173 | else: | 
|---|
| 174 | red, green, blue = initial_color(initialcolor, colordb) | 
|---|
| 175 | s.update_views(red, green, blue) | 
|---|
| 176 | return app, s | 
|---|
| 177 |  | 
|---|
| 178 |  | 
|---|
| 179 | def run(app, s): | 
|---|
| 180 | try: | 
|---|
| 181 | app.start() | 
|---|
| 182 | except KeyboardInterrupt: | 
|---|
| 183 | pass | 
|---|
| 184 |  | 
|---|
| 185 |  | 
|---|
| 186 |  | 
|---|
| 187 |  | 
|---|
| 188 | def main(): | 
|---|
| 189 | try: | 
|---|
| 190 | opts, args = getopt.getopt( | 
|---|
| 191 | sys.argv[1:], | 
|---|
| 192 | 'hd:i:Xv', | 
|---|
| 193 | ['database=', 'initfile=', 'ignore', 'help', 'version']) | 
|---|
| 194 | except getopt.error, msg: | 
|---|
| 195 | usage(1, msg) | 
|---|
| 196 |  | 
|---|
| 197 | if len(args) == 0: | 
|---|
| 198 | initialcolor = None | 
|---|
| 199 | elif len(args) == 1: | 
|---|
| 200 | initialcolor = args[0] | 
|---|
| 201 | else: | 
|---|
| 202 | usage(1) | 
|---|
| 203 |  | 
|---|
| 204 | ignore = False | 
|---|
| 205 | dbfile = None | 
|---|
| 206 | initfile = os.path.expanduser('~/.pynche') | 
|---|
| 207 | for opt, arg in opts: | 
|---|
| 208 | if opt in ('-h', '--help'): | 
|---|
| 209 | usage(0) | 
|---|
| 210 | elif opt in ('-v', '--version'): | 
|---|
| 211 | print """\ | 
|---|
| 212 | Pynche -- The PYthon Natural Color and Hue Editor. | 
|---|
| 213 | Contact: %(AUTHNAME)s | 
|---|
| 214 | Email:   %(AUTHEMAIL)s | 
|---|
| 215 | Version: %(__version__)s""" % globals() | 
|---|
| 216 | sys.exit(0) | 
|---|
| 217 | elif opt in ('-d', '--database'): | 
|---|
| 218 | dbfile = arg | 
|---|
| 219 | elif opt in ('-X', '--ignore'): | 
|---|
| 220 | ignore = True | 
|---|
| 221 | elif opt in ('-i', '--initfile'): | 
|---|
| 222 | initfile = arg | 
|---|
| 223 |  | 
|---|
| 224 | app, sb = build(initialcolor=initialcolor, | 
|---|
| 225 | initfile=initfile, | 
|---|
| 226 | ignore=ignore, | 
|---|
| 227 | dbfile=dbfile) | 
|---|
| 228 | run(app, sb) | 
|---|
| 229 | sb.save_views() | 
|---|
| 230 |  | 
|---|
| 231 |  | 
|---|
| 232 |  | 
|---|
| 233 |  | 
|---|
| 234 | if __name__ == '__main__': | 
|---|
| 235 | main() | 
|---|