summaryrefslogtreecommitdiff
path: root/sys/src/cmd/python/Tools/pynche
diff options
context:
space:
mode:
authorcinap_lenrek <cinap_lenrek@localhost>2011-05-03 11:25:13 +0000
committercinap_lenrek <cinap_lenrek@localhost>2011-05-03 11:25:13 +0000
commit458120dd40db6b4df55a4e96b650e16798ef06a0 (patch)
tree8f82685be24fef97e715c6f5ca4c68d34d5074ee /sys/src/cmd/python/Tools/pynche
parent3a742c699f6806c1145aea5149bf15de15a0afd7 (diff)
add hg and python
Diffstat (limited to 'sys/src/cmd/python/Tools/pynche')
-rw-r--r--sys/src/cmd/python/Tools/pynche/ChipViewer.py131
-rw-r--r--sys/src/cmd/python/Tools/pynche/ColorDB.py279
-rw-r--r--sys/src/cmd/python/Tools/pynche/DetailsViewer.py273
-rw-r--r--sys/src/cmd/python/Tools/pynche/ListViewer.py175
-rw-r--r--sys/src/cmd/python/Tools/pynche/Main.py229
-rw-r--r--sys/src/cmd/python/Tools/pynche/PyncheWidget.py309
-rw-r--r--sys/src/cmd/python/Tools/pynche/README398
-rw-r--r--sys/src/cmd/python/Tools/pynche/StripViewer.py433
-rw-r--r--sys/src/cmd/python/Tools/pynche/Switchboard.py139
-rw-r--r--sys/src/cmd/python/Tools/pynche/TextViewer.py188
-rw-r--r--sys/src/cmd/python/Tools/pynche/TypeinViewer.py163
-rw-r--r--sys/src/cmd/python/Tools/pynche/X/rgb.txt753
-rw-r--r--sys/src/cmd/python/Tools/pynche/X/xlicense.txt29
-rw-r--r--sys/src/cmd/python/Tools/pynche/__init__.py1
-rw-r--r--sys/src/cmd/python/Tools/pynche/html40colors.txt17
-rw-r--r--sys/src/cmd/python/Tools/pynche/namedcolors.txt100
-rw-r--r--sys/src/cmd/python/Tools/pynche/pyColorChooser.py125
-rwxr-xr-xsys/src/cmd/python/Tools/pynche/pynche7
-rwxr-xr-xsys/src/cmd/python/Tools/pynche/pynche.pyw7
-rw-r--r--sys/src/cmd/python/Tools/pynche/webcolors.txt141
-rw-r--r--sys/src/cmd/python/Tools/pynche/websafe.txt217
21 files changed, 4114 insertions, 0 deletions
diff --git a/sys/src/cmd/python/Tools/pynche/ChipViewer.py b/sys/src/cmd/python/Tools/pynche/ChipViewer.py
new file mode 100644
index 000000000..05412cec4
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/ChipViewer.py
@@ -0,0 +1,131 @@
+"""Chip viewer and widget.
+
+In the lower left corner of the main Pynche window, you will see two
+ChipWidgets, one for the selected color and one for the nearest color. The
+selected color is the actual RGB value expressed as an X11 #COLOR name. The
+nearest color is the named color from the X11 database that is closest to the
+selected color in 3D space. There may be other colors equally close, but the
+nearest one is the first one found.
+
+Clicking on the nearest color chip selects that named color.
+
+The ChipViewer class includes the entire lower left quandrant; i.e. both the
+selected and nearest ChipWidgets.
+"""
+
+from types import StringType
+from Tkinter import *
+import ColorDB
+
+
+class ChipWidget:
+ _WIDTH = 150
+ _HEIGHT = 80
+
+ def __init__(self,
+ master = None,
+ width = _WIDTH,
+ height = _HEIGHT,
+ text = 'Color',
+ initialcolor = 'blue',
+ presscmd = None,
+ releasecmd = None):
+ # create the text label
+ self.__label = Label(master, text=text)
+ self.__label.grid(row=0, column=0)
+ # create the color chip, implemented as a frame
+ self.__chip = Frame(master, relief=RAISED, borderwidth=2,
+ width=width,
+ height=height,
+ background=initialcolor)
+ self.__chip.grid(row=1, column=0)
+ # create the color name
+ self.__namevar = StringVar()
+ self.__namevar.set(initialcolor)
+ self.__name = Entry(master, textvariable=self.__namevar,
+ relief=FLAT, justify=CENTER, state=DISABLED,
+ font=self.__label['font'])
+ self.__name.grid(row=2, column=0)
+ # create the message area
+ self.__msgvar = StringVar()
+ self.__name = Entry(master, textvariable=self.__msgvar,
+ relief=FLAT, justify=CENTER, state=DISABLED,
+ font=self.__label['font'])
+ self.__name.grid(row=3, column=0)
+ # set bindings
+ if presscmd:
+ self.__chip.bind('<ButtonPress-1>', presscmd)
+ if releasecmd:
+ self.__chip.bind('<ButtonRelease-1>', releasecmd)
+
+ def set_color(self, color):
+ self.__chip.config(background=color)
+
+ def get_color(self):
+ return self.__chip['background']
+
+ def set_name(self, colorname):
+ self.__namevar.set(colorname)
+
+ def set_message(self, message):
+ self.__msgvar.set(message)
+
+ def press(self):
+ self.__chip.configure(relief=SUNKEN)
+
+ def release(self):
+ self.__chip.configure(relief=RAISED)
+
+
+
+class ChipViewer:
+ def __init__(self, switchboard, master=None):
+ self.__sb = switchboard
+ self.__frame = Frame(master, relief=RAISED, borderwidth=1)
+ self.__frame.grid(row=3, column=0, ipadx=5, sticky='NSEW')
+ # create the chip that will display the currently selected color
+ # exactly
+ self.__sframe = Frame(self.__frame)
+ self.__sframe.grid(row=0, column=0)
+ self.__selected = ChipWidget(self.__sframe, text='Selected')
+ # create the chip that will display the nearest real X11 color
+ # database color name
+ self.__nframe = Frame(self.__frame)
+ self.__nframe.grid(row=0, column=1)
+ self.__nearest = ChipWidget(self.__nframe, text='Nearest',
+ presscmd = self.__buttonpress,
+ releasecmd = self.__buttonrelease)
+
+ def update_yourself(self, red, green, blue):
+ # Selected always shows the #rrggbb name of the color, nearest always
+ # shows the name of the nearest color in the database. BAW: should
+ # an exact match be indicated in some way?
+ #
+ # Always use the #rrggbb style to actually set the color, since we may
+ # not be using X color names (e.g. "web-safe" names)
+ colordb = self.__sb.colordb()
+ rgbtuple = (red, green, blue)
+ rrggbb = ColorDB.triplet_to_rrggbb(rgbtuple)
+ # find the nearest
+ nearest = colordb.nearest(red, green, blue)
+ nearest_tuple = colordb.find_byname(nearest)
+ nearest_rrggbb = ColorDB.triplet_to_rrggbb(nearest_tuple)
+ self.__selected.set_color(rrggbb)
+ self.__nearest.set_color(nearest_rrggbb)
+ # set the name and messages areas
+ self.__selected.set_name(rrggbb)
+ if rrggbb == nearest_rrggbb:
+ self.__selected.set_message(nearest)
+ else:
+ self.__selected.set_message('')
+ self.__nearest.set_name(nearest_rrggbb)
+ self.__nearest.set_message(nearest)
+
+ def __buttonpress(self, event=None):
+ self.__nearest.press()
+
+ def __buttonrelease(self, event=None):
+ self.__nearest.release()
+ rrggbb = self.__nearest.get_color()
+ red, green, blue = ColorDB.rrggbb_to_triplet(rrggbb)
+ self.__sb.update_views(red, green, blue)
diff --git a/sys/src/cmd/python/Tools/pynche/ColorDB.py b/sys/src/cmd/python/Tools/pynche/ColorDB.py
new file mode 100644
index 000000000..96b6ce67c
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/ColorDB.py
@@ -0,0 +1,279 @@
+"""Color Database.
+
+This file contains one class, called ColorDB, and several utility functions.
+The class must be instantiated by the get_colordb() function in this file,
+passing it a filename to read a database out of.
+
+The get_colordb() function will try to examine the file to figure out what the
+format of the file is. If it can't figure out the file format, or it has
+trouble reading the file, None is returned. You can pass get_colordb() an
+optional filetype argument.
+
+Supporte file types are:
+
+ X_RGB_TXT -- X Consortium rgb.txt format files. Three columns of numbers
+ from 0 .. 255 separated by whitespace. Arbitrary trailing
+ columns used as the color name.
+
+The utility functions are useful for converting between the various expected
+color formats, and for calculating other color values.
+
+"""
+
+import sys
+import re
+from types import *
+import operator
+
+class BadColor(Exception):
+ pass
+
+DEFAULT_DB = None
+SPACE = ' '
+COMMASPACE = ', '
+
+
+
+# generic class
+class ColorDB:
+ def __init__(self, fp):
+ lineno = 2
+ self.__name = fp.name
+ # Maintain several dictionaries for indexing into the color database.
+ # Note that while Tk supports RGB intensities of 4, 8, 12, or 16 bits,
+ # for now we only support 8 bit intensities. At least on OpenWindows,
+ # all intensities in the /usr/openwin/lib/rgb.txt file are 8-bit
+ #
+ # key is (red, green, blue) tuple, value is (name, [aliases])
+ self.__byrgb = {}
+ # key is name, value is (red, green, blue)
+ self.__byname = {}
+ # all unique names (non-aliases). built-on demand
+ self.__allnames = None
+ while 1:
+ line = fp.readline()
+ if not line:
+ break
+ # get this compiled regular expression from derived class
+ mo = self._re.match(line)
+ if not mo:
+ print >> sys.stderr, 'Error in', fp.name, ' line', lineno
+ lineno += 1
+ continue
+ # extract the red, green, blue, and name
+ red, green, blue = self._extractrgb(mo)
+ name = self._extractname(mo)
+ keyname = name.lower()
+ # BAW: for now the `name' is just the first named color with the
+ # rgb values we find. Later, we might want to make the two word
+ # version the `name', or the CapitalizedVersion, etc.
+ key = (red, green, blue)
+ foundname, aliases = self.__byrgb.get(key, (name, []))
+ if foundname <> name and foundname not in aliases:
+ aliases.append(name)
+ self.__byrgb[key] = (foundname, aliases)
+ # add to byname lookup
+ self.__byname[keyname] = key
+ lineno = lineno + 1
+
+ # override in derived classes
+ def _extractrgb(self, mo):
+ return [int(x) for x in mo.group('red', 'green', 'blue')]
+
+ def _extractname(self, mo):
+ return mo.group('name')
+
+ def filename(self):
+ return self.__name
+
+ def find_byrgb(self, rgbtuple):
+ """Return name for rgbtuple"""
+ try:
+ return self.__byrgb[rgbtuple]
+ except KeyError:
+ raise BadColor(rgbtuple)
+
+ def find_byname(self, name):
+ """Return (red, green, blue) for name"""
+ name = name.lower()
+ try:
+ return self.__byname[name]
+ except KeyError:
+ raise BadColor(name)
+
+ def nearest(self, red, green, blue):
+ """Return the name of color nearest (red, green, blue)"""
+ # BAW: should we use Voronoi diagrams, Delaunay triangulation, or
+ # octree for speeding up the locating of nearest point? Exhaustive
+ # search is inefficient, but seems fast enough.
+ nearest = -1
+ nearest_name = ''
+ for name, aliases in self.__byrgb.values():
+ r, g, b = self.__byname[name.lower()]
+ rdelta = red - r
+ gdelta = green - g
+ bdelta = blue - b
+ distance = rdelta * rdelta + gdelta * gdelta + bdelta * bdelta
+ if nearest == -1 or distance < nearest:
+ nearest = distance
+ nearest_name = name
+ return nearest_name
+
+ def unique_names(self):
+ # sorted
+ if not self.__allnames:
+ self.__allnames = []
+ for name, aliases in self.__byrgb.values():
+ self.__allnames.append(name)
+ # sort irregardless of case
+ def nocase_cmp(n1, n2):
+ return cmp(n1.lower(), n2.lower())
+ self.__allnames.sort(nocase_cmp)
+ return self.__allnames
+
+ def aliases_of(self, red, green, blue):
+ try:
+ name, aliases = self.__byrgb[(red, green, blue)]
+ except KeyError:
+ raise BadColor((red, green, blue))
+ return [name] + aliases
+
+
+class RGBColorDB(ColorDB):
+ _re = re.compile(
+ '\s*(?P<red>\d+)\s+(?P<green>\d+)\s+(?P<blue>\d+)\s+(?P<name>.*)')
+
+
+class HTML40DB(ColorDB):
+ _re = re.compile('(?P<name>\S+)\s+(?P<hexrgb>#[0-9a-fA-F]{6})')
+
+ def _extractrgb(self, mo):
+ return rrggbb_to_triplet(mo.group('hexrgb'))
+
+class LightlinkDB(HTML40DB):
+ _re = re.compile('(?P<name>(.+))\s+(?P<hexrgb>#[0-9a-fA-F]{6})')
+
+ def _extractname(self, mo):
+ return mo.group('name').strip()
+
+class WebsafeDB(ColorDB):
+ _re = re.compile('(?P<hexrgb>#[0-9a-fA-F]{6})')
+
+ def _extractrgb(self, mo):
+ return rrggbb_to_triplet(mo.group('hexrgb'))
+
+ def _extractname(self, mo):
+ return mo.group('hexrgb').upper()
+
+
+
+# format is a tuple (RE, SCANLINES, CLASS) where RE is a compiled regular
+# expression, SCANLINES is the number of header lines to scan, and CLASS is
+# the class to instantiate if a match is found
+
+FILETYPES = [
+ (re.compile('Xorg'), RGBColorDB),
+ (re.compile('XConsortium'), RGBColorDB),
+ (re.compile('HTML'), HTML40DB),
+ (re.compile('lightlink'), LightlinkDB),
+ (re.compile('Websafe'), WebsafeDB),
+ ]
+
+def get_colordb(file, filetype=None):
+ colordb = None
+ fp = open(file)
+ try:
+ line = fp.readline()
+ if not line:
+ return None
+ # try to determine the type of RGB file it is
+ if filetype is None:
+ filetypes = FILETYPES
+ else:
+ filetypes = [filetype]
+ for typere, class_ in filetypes:
+ mo = typere.search(line)
+ if mo:
+ break
+ else:
+ # no matching type
+ return None
+ # we know the type and the class to grok the type, so suck it in
+ colordb = class_(fp)
+ finally:
+ fp.close()
+ # save a global copy
+ global DEFAULT_DB
+ DEFAULT_DB = colordb
+ return colordb
+
+
+
+_namedict = {}
+
+def rrggbb_to_triplet(color):
+ """Converts a #rrggbb color to the tuple (red, green, blue)."""
+ rgbtuple = _namedict.get(color)
+ if rgbtuple is None:
+ if color[0] <> '#':
+ raise BadColor(color)
+ red = color[1:3]
+ green = color[3:5]
+ blue = color[5:7]
+ rgbtuple = int(red, 16), int(green, 16), int(blue, 16)
+ _namedict[color] = rgbtuple
+ return rgbtuple
+
+
+_tripdict = {}
+def triplet_to_rrggbb(rgbtuple):
+ """Converts a (red, green, blue) tuple to #rrggbb."""
+ global _tripdict
+ hexname = _tripdict.get(rgbtuple)
+ if hexname is None:
+ hexname = '#%02x%02x%02x' % rgbtuple
+ _tripdict[rgbtuple] = hexname
+ return hexname
+
+
+_maxtuple = (256.0,) * 3
+def triplet_to_fractional_rgb(rgbtuple):
+ return map(operator.__div__, rgbtuple, _maxtuple)
+
+
+def triplet_to_brightness(rgbtuple):
+ # return the brightness (grey level) along the scale 0.0==black to
+ # 1.0==white
+ r = 0.299
+ g = 0.587
+ b = 0.114
+ return r*rgbtuple[0] + g*rgbtuple[1] + b*rgbtuple[2]
+
+
+
+if __name__ == '__main__':
+ colordb = get_colordb('/usr/openwin/lib/rgb.txt')
+ if not colordb:
+ print 'No parseable color database found'
+ sys.exit(1)
+ # on my system, this color matches exactly
+ target = 'navy'
+ red, green, blue = rgbtuple = colordb.find_byname(target)
+ print target, ':', red, green, blue, triplet_to_rrggbb(rgbtuple)
+ name, aliases = colordb.find_byrgb(rgbtuple)
+ print 'name:', name, 'aliases:', COMMASPACE.join(aliases)
+ r, g, b = (1, 1, 128) # nearest to navy
+ r, g, b = (145, 238, 144) # nearest to lightgreen
+ r, g, b = (255, 251, 250) # snow
+ print 'finding nearest to', target, '...'
+ import time
+ t0 = time.time()
+ nearest = colordb.nearest(r, g, b)
+ t1 = time.time()
+ print 'found nearest color', nearest, 'in', t1-t0, 'seconds'
+ # dump the database
+ for n in colordb.unique_names():
+ r, g, b = colordb.find_byname(n)
+ aliases = colordb.aliases_of(r, g, b)
+ print '%20s: (%3d/%3d/%3d) == %s' % (n, r, g, b,
+ SPACE.join(aliases[1:]))
diff --git a/sys/src/cmd/python/Tools/pynche/DetailsViewer.py b/sys/src/cmd/python/Tools/pynche/DetailsViewer.py
new file mode 100644
index 000000000..11a99a6af
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/DetailsViewer.py
@@ -0,0 +1,273 @@
+"""DetailsViewer class.
+
+This class implements a pure input window which allows you to meticulously
+edit the current color. You have both mouse control of the color (via the
+buttons along the bottom row), and there are keyboard bindings for each of the
+increment/decrement buttons.
+
+The top three check buttons allow you to specify which of the three color
+variations are tied together when incrementing and decrementing. Red, green,
+and blue are self evident. By tying together red and green, you can modify
+the yellow level of the color. By tying together red and blue, you can modify
+the magenta level of the color. By tying together green and blue, you can
+modify the cyan level, and by tying all three together, you can modify the
+grey level.
+
+The behavior at the boundaries (0 and 255) are defined by the `At boundary'
+option menu:
+
+ Stop
+ When the increment or decrement would send any of the tied variations
+ out of bounds, the entire delta is discarded.
+
+ Wrap Around
+ When the increment or decrement would send any of the tied variations
+ out of bounds, the out of bounds variation is wrapped around to the
+ other side. Thus if red were at 238 and 25 were added to it, red
+ would have the value 7.
+
+ Preseve Distance
+ When the increment or decrement would send any of the tied variations
+ out of bounds, all tied variations are wrapped as one, so as to
+ preserve the distance between them. Thus if green and blue were tied,
+ and green was at 238 while blue was at 223, and an increment of 25
+ were applied, green would be at 15 and blue would be at 0.
+
+ Squash
+ When the increment or decrement would send any of the tied variations
+ out of bounds, the out of bounds variation is set to the ceiling of
+ 255 or floor of 0, as appropriate. In this way, all tied variations
+ are squashed to one edge or the other.
+
+The following key bindings can be used as accelerators. Note that Pynche can
+fall behind if you hold the key down as a key repeat:
+
+Left arrow == -1
+Right arrow == +1
+
+Control + Left == -10
+Control + Right == 10
+
+Shift + Left == -25
+Shift + Right == +25
+"""
+
+from Tkinter import *
+
+STOP = 'Stop'
+WRAP = 'Wrap Around'
+RATIO = 'Preserve Distance'
+GRAV = 'Squash'
+
+ADDTOVIEW = 'Details Window...'
+
+
+class DetailsViewer:
+ def __init__(self, switchboard, master=None):
+ self.__sb = switchboard
+ optiondb = switchboard.optiondb()
+ self.__red, self.__green, self.__blue = switchboard.current_rgb()
+ # GUI
+ root = self.__root = Toplevel(master, class_='Pynche')
+ root.protocol('WM_DELETE_WINDOW', self.withdraw)
+ root.title('Pynche Details Window')
+ root.iconname('Pynche Details Window')
+ root.bind('<Alt-q>', self.__quit)
+ root.bind('<Alt-Q>', self.__quit)
+ root.bind('<Alt-w>', self.withdraw)
+ root.bind('<Alt-W>', self.withdraw)
+ # accelerators
+ root.bind('<KeyPress-Left>', self.__minus1)
+ root.bind('<KeyPress-Right>', self.__plus1)
+ root.bind('<Control-KeyPress-Left>', self.__minus10)
+ root.bind('<Control-KeyPress-Right>', self.__plus10)
+ root.bind('<Shift-KeyPress-Left>', self.__minus25)
+ root.bind('<Shift-KeyPress-Right>', self.__plus25)
+ #
+ # color ties
+ frame = self.__frame = Frame(root)
+ frame.pack(expand=YES, fill=X)
+ self.__l1 = Label(frame, text='Move Sliders:')
+ self.__l1.grid(row=1, column=0, sticky=E)
+ self.__rvar = IntVar()
+ self.__rvar.set(optiondb.get('RSLIDER', 4))
+ self.__radio1 = Checkbutton(frame, text='Red',
+ variable=self.__rvar,
+ command=self.__effect,
+ onvalue=4, offvalue=0)
+ self.__radio1.grid(row=1, column=1, sticky=W)
+ self.__gvar = IntVar()
+ self.__gvar.set(optiondb.get('GSLIDER', 2))
+ self.__radio2 = Checkbutton(frame, text='Green',
+ variable=self.__gvar,
+ command=self.__effect,
+ onvalue=2, offvalue=0)
+ self.__radio2.grid(row=2, column=1, sticky=W)
+ self.__bvar = IntVar()
+ self.__bvar.set(optiondb.get('BSLIDER', 1))
+ self.__radio3 = Checkbutton(frame, text='Blue',
+ variable=self.__bvar,
+ command=self.__effect,
+ onvalue=1, offvalue=0)
+ self.__radio3.grid(row=3, column=1, sticky=W)
+ self.__l2 = Label(frame)
+ self.__l2.grid(row=4, column=1, sticky=W)
+ self.__effect()
+ #
+ # Boundary behavior
+ self.__l3 = Label(frame, text='At boundary:')
+ self.__l3.grid(row=5, column=0, sticky=E)
+ self.__boundvar = StringVar()
+ self.__boundvar.set(optiondb.get('ATBOUND', STOP))
+ self.__omenu = OptionMenu(frame, self.__boundvar,
+ STOP, WRAP, RATIO, GRAV)
+ self.__omenu.grid(row=5, column=1, sticky=W)
+ self.__omenu.configure(width=17)
+ #
+ # Buttons
+ frame = self.__btnframe = Frame(frame)
+ frame.grid(row=0, column=0, columnspan=2, sticky='EW')
+ self.__down25 = Button(frame, text='-25',
+ command=self.__minus25)
+ self.__down10 = Button(frame, text='-10',
+ command=self.__minus10)
+ self.__down1 = Button(frame, text='-1',
+ command=self.__minus1)
+ self.__up1 = Button(frame, text='+1',
+ command=self.__plus1)
+ self.__up10 = Button(frame, text='+10',
+ command=self.__plus10)
+ self.__up25 = Button(frame, text='+25',
+ command=self.__plus25)
+ self.__down25.pack(expand=YES, fill=X, side=LEFT)
+ self.__down10.pack(expand=YES, fill=X, side=LEFT)
+ self.__down1.pack(expand=YES, fill=X, side=LEFT)
+ self.__up1.pack(expand=YES, fill=X, side=LEFT)
+ self.__up10.pack(expand=YES, fill=X, side=LEFT)
+ self.__up25.pack(expand=YES, fill=X, side=LEFT)
+
+ def __effect(self, event=None):
+ tie = self.__rvar.get() + self.__gvar.get() + self.__bvar.get()
+ if tie in (0, 1, 2, 4):
+ text = ''
+ else:
+ text = '(= %s Level)' % {3: 'Cyan',
+ 5: 'Magenta',
+ 6: 'Yellow',
+ 7: 'Grey'}[tie]
+ self.__l2.configure(text=text)
+
+ def __quit(self, event=None):
+ self.__root.quit()
+
+ def withdraw(self, event=None):
+ self.__root.withdraw()
+
+ def deiconify(self, event=None):
+ self.__root.deiconify()
+
+ def __minus25(self, event=None):
+ self.__delta(-25)
+
+ def __minus10(self, event=None):
+ self.__delta(-10)
+
+ def __minus1(self, event=None):
+ self.__delta(-1)
+
+ def __plus1(self, event=None):
+ self.__delta(1)
+
+ def __plus10(self, event=None):
+ self.__delta(10)
+
+ def __plus25(self, event=None):
+ self.__delta(25)
+
+ def __delta(self, delta):
+ tie = []
+ if self.__rvar.get():
+ red = self.__red + delta
+ tie.append(red)
+ else:
+ red = self.__red
+ if self.__gvar.get():
+ green = self.__green + delta
+ tie.append(green)
+ else:
+ green = self.__green
+ if self.__bvar.get():
+ blue = self.__blue + delta
+ tie.append(blue)
+ else:
+ blue = self.__blue
+ # now apply at boundary behavior
+ atbound = self.__boundvar.get()
+ if atbound == STOP:
+ if red < 0 or green < 0 or blue < 0 or \
+ red > 255 or green > 255 or blue > 255:
+ # then
+ red, green, blue = self.__red, self.__green, self.__blue
+ elif atbound == WRAP or (atbound == RATIO and len(tie) < 2):
+ if red < 0:
+ red += 256
+ if green < 0:
+ green += 256
+ if blue < 0:
+ blue += 256
+ if red > 255:
+ red -= 256
+ if green > 255:
+ green -= 256
+ if blue > 255:
+ blue -= 256
+ elif atbound == RATIO:
+ # for when 2 or 3 colors are tied together
+ dir = 0
+ for c in tie:
+ if c < 0:
+ dir = -1
+ elif c > 255:
+ dir = 1
+ if dir == -1:
+ delta = max(tie)
+ if self.__rvar.get():
+ red = red + 255 - delta
+ if self.__gvar.get():
+ green = green + 255 - delta
+ if self.__bvar.get():
+ blue = blue + 255 - delta
+ elif dir == 1:
+ delta = min(tie)
+ if self.__rvar.get():
+ red = red - delta
+ if self.__gvar.get():
+ green = green - delta
+ if self.__bvar.get():
+ blue = blue - delta
+ elif atbound == GRAV:
+ if red < 0:
+ red = 0
+ if green < 0:
+ green = 0
+ if blue < 0:
+ blue = 0
+ if red > 255:
+ red = 255
+ if green > 255:
+ green = 255
+ if blue > 255:
+ blue = 255
+ self.__sb.update_views(red, green, blue)
+ self.__root.update_idletasks()
+
+ def update_yourself(self, red, green, blue):
+ self.__red = red
+ self.__green = green
+ self.__blue = blue
+
+ def save_options(self, optiondb):
+ optiondb['RSLIDER'] = self.__rvar.get()
+ optiondb['GSLIDER'] = self.__gvar.get()
+ optiondb['BSLIDER'] = self.__bvar.get()
+ optiondb['ATBOUND'] = self.__boundvar.get()
diff --git a/sys/src/cmd/python/Tools/pynche/ListViewer.py b/sys/src/cmd/python/Tools/pynche/ListViewer.py
new file mode 100644
index 000000000..213cd08ba
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/ListViewer.py
@@ -0,0 +1,175 @@
+"""ListViewer class.
+
+This class implements an input/output view on the color model. It lists every
+unique color (e.g. unique r/g/b value) found in the color database. Each
+color is shown by small swatch and primary color name. Some colors have
+aliases -- more than one name for the same r/g/b value. These aliases are
+displayed in the small listbox at the bottom of the screen.
+
+Clicking on a color name or swatch selects that color and updates all other
+windows. When a color is selected in a different viewer, the color list is
+scrolled to the selected color and it is highlighted. If the selected color
+is an r/g/b value without a name, no scrolling occurs.
+
+You can turn off Update On Click if all you want to see is the alias for a
+given name, without selecting the color.
+"""
+
+from Tkinter import *
+import ColorDB
+
+ADDTOVIEW = 'Color %List Window...'
+
+class ListViewer:
+ def __init__(self, switchboard, master=None):
+ self.__sb = switchboard
+ optiondb = switchboard.optiondb()
+ self.__lastbox = None
+ self.__dontcenter = 0
+ # GUI
+ root = self.__root = Toplevel(master, class_='Pynche')
+ root.protocol('WM_DELETE_WINDOW', self.withdraw)
+ root.title('Pynche Color List')
+ root.iconname('Pynche Color List')
+ root.bind('<Alt-q>', self.__quit)
+ root.bind('<Alt-Q>', self.__quit)
+ root.bind('<Alt-w>', self.withdraw)
+ root.bind('<Alt-W>', self.withdraw)
+ #
+ # create the canvas which holds everything, and its scrollbar
+ #
+ frame = self.__frame = Frame(root)
+ frame.pack()
+ canvas = self.__canvas = Canvas(frame, width=160, height=300,
+ borderwidth=2, relief=SUNKEN)
+ self.__scrollbar = Scrollbar(frame)
+ self.__scrollbar.pack(fill=Y, side=RIGHT)
+ canvas.pack(fill=BOTH, expand=1)
+ canvas.configure(yscrollcommand=(self.__scrollbar, 'set'))
+ self.__scrollbar.configure(command=(canvas, 'yview'))
+ self.__populate()
+ #
+ # Update on click
+ self.__uoc = BooleanVar()
+ self.__uoc.set(optiondb.get('UPONCLICK', 1))
+ self.__uocbtn = Checkbutton(root,
+ text='Update on Click',
+ variable=self.__uoc,
+ command=self.__toggleupdate)
+ self.__uocbtn.pack(expand=1, fill=BOTH)
+ #
+ # alias list
+ self.__alabel = Label(root, text='Aliases:')
+ self.__alabel.pack()
+ self.__aliases = Listbox(root, height=5,
+ selectmode=BROWSE)
+ self.__aliases.pack(expand=1, fill=BOTH)
+
+ def __populate(self):
+ #
+ # create all the buttons
+ colordb = self.__sb.colordb()
+ canvas = self.__canvas
+ row = 0
+ widest = 0
+ bboxes = self.__bboxes = []
+ for name in colordb.unique_names():
+ exactcolor = ColorDB.triplet_to_rrggbb(colordb.find_byname(name))
+ canvas.create_rectangle(5, row*20 + 5,
+ 20, row*20 + 20,
+ fill=exactcolor)
+ textid = canvas.create_text(25, row*20 + 13,
+ text=name,
+ anchor=W)
+ x1, y1, textend, y2 = canvas.bbox(textid)
+ boxid = canvas.create_rectangle(3, row*20+3,
+ textend+3, row*20 + 23,
+ outline='',
+ tags=(exactcolor, 'all'))
+ canvas.bind('<ButtonRelease>', self.__onrelease)
+ bboxes.append(boxid)
+ if textend+3 > widest:
+ widest = textend+3
+ row += 1
+ canvheight = (row-1)*20 + 25
+ canvas.config(scrollregion=(0, 0, 150, canvheight))
+ for box in bboxes:
+ x1, y1, x2, y2 = canvas.coords(box)
+ canvas.coords(box, x1, y1, widest, y2)
+
+ def __onrelease(self, event=None):
+ canvas = self.__canvas
+ # find the current box
+ x = canvas.canvasx(event.x)
+ y = canvas.canvasy(event.y)
+ ids = canvas.find_overlapping(x, y, x, y)
+ for boxid in ids:
+ if boxid in self.__bboxes:
+ break
+ else:
+## print 'No box found!'
+ return
+ tags = self.__canvas.gettags(boxid)
+ for t in tags:
+ if t[0] == '#':
+ break
+ else:
+## print 'No color tag found!'
+ return
+ red, green, blue = ColorDB.rrggbb_to_triplet(t)
+ self.__dontcenter = 1
+ if self.__uoc.get():
+ self.__sb.update_views(red, green, blue)
+ else:
+ self.update_yourself(red, green, blue)
+ self.__red, self.__green, self.__blue = red, green, blue
+
+ def __toggleupdate(self, event=None):
+ if self.__uoc.get():
+ self.__sb.update_views(self.__red, self.__green, self.__blue)
+
+ def __quit(self, event=None):
+ self.__root.quit()
+
+ def withdraw(self, event=None):
+ self.__root.withdraw()
+
+ def deiconify(self, event=None):
+ self.__root.deiconify()
+
+ def update_yourself(self, red, green, blue):
+ canvas = self.__canvas
+ # turn off the last box
+ if self.__lastbox:
+ canvas.itemconfigure(self.__lastbox, outline='')
+ # turn on the current box
+ colortag = ColorDB.triplet_to_rrggbb((red, green, blue))
+ canvas.itemconfigure(colortag, outline='black')
+ self.__lastbox = colortag
+ # fill the aliases
+ self.__aliases.delete(0, END)
+ try:
+ aliases = self.__sb.colordb().aliases_of(red, green, blue)[1:]
+ except ColorDB.BadColor:
+ self.__aliases.insert(END, '<no matching color>')
+ return
+ if not aliases:
+ self.__aliases.insert(END, '<no aliases>')
+ else:
+ for name in aliases:
+ self.__aliases.insert(END, name)
+ # maybe scroll the canvas so that the item is visible
+ if self.__dontcenter:
+ self.__dontcenter = 0
+ else:
+ ig, ig, ig, y1 = canvas.coords(colortag)
+ ig, ig, ig, y2 = canvas.coords(self.__bboxes[-1])
+ h = int(canvas['height']) * 0.5
+ canvas.yview('moveto', (y1-h) / y2)
+
+ def save_options(self, optiondb):
+ optiondb['UPONCLICK'] = self.__uoc.get()
+
+ def colordb_changed(self, colordb):
+ self.__canvas.delete('all')
+ self.__populate()
diff --git a/sys/src/cmd/python/Tools/pynche/Main.py b/sys/src/cmd/python/Tools/pynche/Main.py
new file mode 100644
index 000000000..1fa3f175c
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/Main.py
@@ -0,0 +1,229 @@
+"""Pynche -- The PYthon Natural Color and Hue Editor.
+
+Contact: %(AUTHNAME)s
+Email: %(AUTHEMAIL)s
+Version: %(__version__)s
+
+Pynche is based largely on a similar color editor I wrote years ago for the
+SunView window system. That editor was called ICE: the Interactive Color
+Editor. I'd always wanted to port the editor to X but didn't feel like
+hacking X and C code to do it. Fast forward many years, to where Python +
+Tkinter provides such a nice programming environment, with enough power, that
+I finally buckled down and implemented it. I changed the name because these
+days, too many other systems have the acronym `ICE'.
+
+This program currently requires Python 2.2 with Tkinter.
+
+Usage: %(PROGRAM)s [-d file] [-i file] [-X] [-v] [-h] [initialcolor]
+
+Where:
+ --database file
+ -d file
+ Alternate location of a color database file
+
+ --initfile file
+ -i file
+ Alternate location of the initialization file. This file contains a
+ persistent database of the current Pynche options and color. This
+ means that Pynche restores its option settings and current color when
+ it restarts, using this file (unless the -X option is used). The
+ default is ~/.pynche
+
+ --ignore
+ -X
+ Ignore the initialization file when starting up. Pynche will still
+ write the current option settings to this file when it quits.
+
+ --version
+ -v
+ print the version number and exit
+
+ --help
+ -h
+ print this message
+
+ initialcolor
+ initial color, as a color name or #RRGGBB format
+"""
+
+__version__ = '1.4.1'
+
+import sys
+import os
+import getopt
+import ColorDB
+
+from PyncheWidget import PyncheWidget
+from Switchboard import Switchboard
+from StripViewer import StripViewer
+from ChipViewer import ChipViewer
+from TypeinViewer import TypeinViewer
+
+
+
+PROGRAM = sys.argv[0]
+AUTHNAME = 'Barry Warsaw'
+AUTHEMAIL = 'barry@python.org'
+
+# Default locations of rgb.txt or other textual color database
+RGB_TXT = [
+ # Solaris OpenWindows
+ '/usr/openwin/lib/rgb.txt',
+ # Linux
+ '/usr/lib/X11/rgb.txt',
+ # The X11R6.4 rgb.txt file
+ os.path.join(sys.path[0], 'X/rgb.txt'),
+ # add more here
+ ]
+
+
+
+# Do this because PyncheWidget.py wants to get at the interpolated docstring
+# too, for its Help menu.
+def docstring():
+ return __doc__ % globals()
+
+
+def usage(code, msg=''):
+ print docstring()
+ if msg:
+ print msg
+ sys.exit(code)
+
+
+
+def initial_color(s, colordb):
+ # function called on every color
+ def scan_color(s, colordb=colordb):
+ try:
+ r, g, b = colordb.find_byname(s)
+ except ColorDB.BadColor:
+ try:
+ r, g, b = ColorDB.rrggbb_to_triplet(s)
+ except ColorDB.BadColor:
+ return None, None, None
+ return r, g, b
+ #
+ # First try the passed in color
+ r, g, b = scan_color(s)
+ if r is None:
+ # try the same color with '#' prepended, since some shells require
+ # this to be escaped, which is a pain
+ r, g, b = scan_color('#' + s)
+ if r is None:
+ print 'Bad initial color, using gray50:', s
+ r, g, b = scan_color('gray50')
+ if r is None:
+ usage(1, 'Cannot find an initial color to use')
+ # does not return
+ return r, g, b
+
+
+
+def build(master=None, initialcolor=None, initfile=None, ignore=None,
+ dbfile=None):
+ # create all output widgets
+ s = Switchboard(not ignore and initfile)
+ # defer to the command line chosen color database, falling back to the one
+ # in the .pynche file.
+ if dbfile is None:
+ dbfile = s.optiondb().get('DBFILE')
+ # find a parseable color database
+ colordb = None
+ files = RGB_TXT[:]
+ if dbfile is None:
+ dbfile = files.pop()
+ while colordb is None:
+ try:
+ colordb = ColorDB.get_colordb(dbfile)
+ except (KeyError, IOError):
+ pass
+ if colordb is None:
+ if not files:
+ break
+ dbfile = files.pop(0)
+ if not colordb:
+ usage(1, 'No color database file found, see the -d option.')
+ s.set_colordb(colordb)
+
+ # create the application window decorations
+ app = PyncheWidget(__version__, s, master=master)
+ w = app.window()
+
+ # these built-in viewers live inside the main Pynche window
+ s.add_view(StripViewer(s, w))
+ s.add_view(ChipViewer(s, w))
+ s.add_view(TypeinViewer(s, w))
+
+ # get the initial color as components and set the color on all views. if
+ # there was no initial color given on the command line, use the one that's
+ # stored in the option database
+ if initialcolor is None:
+ optiondb = s.optiondb()
+ red = optiondb.get('RED')
+ green = optiondb.get('GREEN')
+ blue = optiondb.get('BLUE')
+ # but if there wasn't any stored in the database, use grey50
+ if red is None or blue is None or green is None:
+ red, green, blue = initial_color('grey50', colordb)
+ else:
+ red, green, blue = initial_color(initialcolor, colordb)
+ s.update_views(red, green, blue)
+ return app, s
+
+
+def run(app, s):
+ try:
+ app.start()
+ except KeyboardInterrupt:
+ pass
+
+
+
+def main():
+ try:
+ opts, args = getopt.getopt(
+ sys.argv[1:],
+ 'hd:i:Xv',
+ ['database=', 'initfile=', 'ignore', 'help', 'version'])
+ except getopt.error, msg:
+ usage(1, msg)
+
+ if len(args) == 0:
+ initialcolor = None
+ elif len(args) == 1:
+ initialcolor = args[0]
+ else:
+ usage(1)
+
+ ignore = False
+ dbfile = None
+ initfile = os.path.expanduser('~/.pynche')
+ for opt, arg in opts:
+ if opt in ('-h', '--help'):
+ usage(0)
+ elif opt in ('-v', '--version'):
+ print """\
+Pynche -- The PYthon Natural Color and Hue Editor.
+Contact: %(AUTHNAME)s
+Email: %(AUTHEMAIL)s
+Version: %(__version__)s""" % globals()
+ sys.exit(0)
+ elif opt in ('-d', '--database'):
+ dbfile = arg
+ elif opt in ('-X', '--ignore'):
+ ignore = True
+ elif opt in ('-i', '--initfile'):
+ initfile = arg
+
+ app, sb = build(initialcolor=initialcolor,
+ initfile=initfile,
+ ignore=ignore,
+ dbfile=dbfile)
+ run(app, sb)
+ sb.save_views()
+
+
+
+if __name__ == '__main__':
+ main()
diff --git a/sys/src/cmd/python/Tools/pynche/PyncheWidget.py b/sys/src/cmd/python/Tools/pynche/PyncheWidget.py
new file mode 100644
index 000000000..fcfe7ce6c
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/PyncheWidget.py
@@ -0,0 +1,309 @@
+"""Main Pynche (Pythonically Natural Color and Hue Editor) widget.
+
+This window provides the basic decorations, primarily including the menubar.
+It is used to bring up other windows.
+"""
+
+import sys
+import os
+from Tkinter import *
+import tkMessageBox
+import tkFileDialog
+import ColorDB
+
+# Milliseconds between interrupt checks
+KEEPALIVE_TIMER = 500
+
+
+
+class PyncheWidget:
+ def __init__(self, version, switchboard, master=None, extrapath=[]):
+ self.__sb = switchboard
+ self.__version = version
+ self.__textwin = None
+ self.__listwin = None
+ self.__detailswin = None
+ self.__helpwin = None
+ self.__dialogstate = {}
+ modal = self.__modal = not not master
+ # If a master was given, we are running as a modal dialog servant to
+ # some other application. We rearrange our UI in this case (there's
+ # no File menu and we get `Okay' and `Cancel' buttons), and we do a
+ # grab_set() to make ourselves modal
+ if modal:
+ self.__tkroot = tkroot = Toplevel(master, class_='Pynche')
+ tkroot.grab_set()
+ tkroot.withdraw()
+ else:
+ # Is there already a default root for Tk, say because we're
+ # running under Guido's IDE? :-) Two conditions say no, either the
+ # import fails or _default_root is None.
+ tkroot = None
+ try:
+ from Tkinter import _default_root
+ tkroot = self.__tkroot = _default_root
+ except ImportError:
+ pass
+ if not tkroot:
+ tkroot = self.__tkroot = Tk(className='Pynche')
+ # but this isn't our top level widget, so make it invisible
+ tkroot.withdraw()
+ # create the menubar
+ menubar = self.__menubar = Menu(tkroot)
+ #
+ # File menu
+ #
+ filemenu = self.__filemenu = Menu(menubar, tearoff=0)
+ filemenu.add_command(label='Load palette...',
+ command=self.__load,
+ underline=0)
+ if not modal:
+ filemenu.add_command(label='Quit',
+ command=self.__quit,
+ accelerator='Alt-Q',
+ underline=0)
+ #
+ # View menu
+ #
+ views = make_view_popups(self.__sb, self.__tkroot, extrapath)
+ viewmenu = Menu(menubar, tearoff=0)
+ for v in views:
+ viewmenu.add_command(label=v.menutext(),
+ command=v.popup,
+ underline=v.underline())
+ #
+ # Help menu
+ #
+ helpmenu = Menu(menubar, name='help', tearoff=0)
+ helpmenu.add_command(label='About Pynche...',
+ command=self.__popup_about,
+ underline=0)
+ helpmenu.add_command(label='Help...',
+ command=self.__popup_usage,
+ underline=0)
+ #
+ # Tie them all together
+ #
+ menubar.add_cascade(label='File',
+ menu=filemenu,
+ underline=0)
+ menubar.add_cascade(label='View',
+ menu=viewmenu,
+ underline=0)
+ menubar.add_cascade(label='Help',
+ menu=helpmenu,
+ underline=0)
+
+ # now create the top level window
+ root = self.__root = Toplevel(tkroot, class_='Pynche', menu=menubar)
+ root.protocol('WM_DELETE_WINDOW',
+ modal and self.__bell or self.__quit)
+ root.title('Pynche %s' % version)
+ root.iconname('Pynche')
+ # Only bind accelerators for the File->Quit menu item if running as a
+ # standalone app
+ if not modal:
+ root.bind('<Alt-q>', self.__quit)
+ root.bind('<Alt-Q>', self.__quit)
+ else:
+ # We're a modal dialog so we have a new row of buttons
+ bframe = Frame(root, borderwidth=1, relief=RAISED)
+ bframe.grid(row=4, column=0, columnspan=2,
+ sticky='EW',
+ ipady=5)
+ okay = Button(bframe,
+ text='Okay',
+ command=self.__okay)
+ okay.pack(side=LEFT, expand=1)
+ cancel = Button(bframe,
+ text='Cancel',
+ command=self.__cancel)
+ cancel.pack(side=LEFT, expand=1)
+
+ def __quit(self, event=None):
+ self.__tkroot.quit()
+
+ def __bell(self, event=None):
+ self.__tkroot.bell()
+
+ def __okay(self, event=None):
+ self.__sb.withdraw_views()
+ self.__tkroot.grab_release()
+ self.__quit()
+
+ def __cancel(self, event=None):
+ self.__sb.canceled()
+ self.__okay()
+
+ def __keepalive(self):
+ # Exercise the Python interpreter regularly so keyboard interrupts get
+ # through.
+ self.__tkroot.tk.createtimerhandler(KEEPALIVE_TIMER, self.__keepalive)
+
+ def start(self):
+ if not self.__modal:
+ self.__keepalive()
+ self.__tkroot.mainloop()
+
+ def window(self):
+ return self.__root
+
+ def __popup_about(self, event=None):
+ from Main import __version__
+ tkMessageBox.showinfo('About Pynche ' + __version__,
+ '''\
+Pynche %s
+The PYthonically Natural
+Color and Hue Editor
+
+For information
+contact: Barry A. Warsaw
+email: bwarsaw@python.org''' % __version__)
+
+ def __popup_usage(self, event=None):
+ if not self.__helpwin:
+ self.__helpwin = Helpwin(self.__root, self.__quit)
+ self.__helpwin.deiconify()
+
+ def __load(self, event=None):
+ while 1:
+ idir, ifile = os.path.split(self.__sb.colordb().filename())
+ file = tkFileDialog.askopenfilename(
+ filetypes=[('Text files', '*.txt'),
+ ('All files', '*'),
+ ],
+ initialdir=idir,
+ initialfile=ifile)
+ if not file:
+ # cancel button
+ return
+ try:
+ colordb = ColorDB.get_colordb(file)
+ except IOError:
+ tkMessageBox.showerror('Read error', '''\
+Could not open file for reading:
+%s''' % file)
+ continue
+ if colordb is None:
+ tkMessageBox.showerror('Unrecognized color file type', '''\
+Unrecognized color file type in file:
+%s''' % file)
+ continue
+ break
+ self.__sb.set_colordb(colordb)
+
+ def withdraw(self):
+ self.__root.withdraw()
+
+ def deiconify(self):
+ self.__root.deiconify()
+
+
+
+class Helpwin:
+ def __init__(self, master, quitfunc):
+ from Main import docstring
+ self.__root = root = Toplevel(master, class_='Pynche')
+ root.protocol('WM_DELETE_WINDOW', self.__withdraw)
+ root.title('Pynche Help Window')
+ root.iconname('Pynche Help Window')
+ root.bind('<Alt-q>', quitfunc)
+ root.bind('<Alt-Q>', quitfunc)
+ root.bind('<Alt-w>', self.__withdraw)
+ root.bind('<Alt-W>', self.__withdraw)
+
+ # more elaborate help is available in the README file
+ readmefile = os.path.join(sys.path[0], 'README')
+ try:
+ fp = None
+ try:
+ fp = open(readmefile)
+ contents = fp.read()
+ # wax the last page, it contains Emacs cruft
+ i = contents.rfind('\f')
+ if i > 0:
+ contents = contents[:i].rstrip()
+ finally:
+ if fp:
+ fp.close()
+ except IOError:
+ sys.stderr.write("Couldn't open Pynche's README, "
+ 'using docstring instead.\n')
+ contents = docstring()
+
+ self.__text = text = Text(root, relief=SUNKEN,
+ width=80, height=24)
+ self.__text.focus_set()
+ text.insert(0.0, contents)
+ scrollbar = Scrollbar(root)
+ scrollbar.pack(fill=Y, side=RIGHT)
+ text.pack(fill=BOTH, expand=YES)
+ text.configure(yscrollcommand=(scrollbar, 'set'))
+ scrollbar.configure(command=(text, 'yview'))
+
+ def __withdraw(self, event=None):
+ self.__root.withdraw()
+
+ def deiconify(self):
+ self.__root.deiconify()
+
+
+
+class PopupViewer:
+ def __init__(self, module, name, switchboard, root):
+ self.__m = module
+ self.__name = name
+ self.__sb = switchboard
+ self.__root = root
+ self.__menutext = module.ADDTOVIEW
+ # find the underline character
+ underline = module.ADDTOVIEW.find('%')
+ if underline == -1:
+ underline = 0
+ else:
+ self.__menutext = module.ADDTOVIEW.replace('%', '', 1)
+ self.__underline = underline
+ self.__window = None
+
+ def menutext(self):
+ return self.__menutext
+
+ def underline(self):
+ return self.__underline
+
+ def popup(self, event=None):
+ if not self.__window:
+ # class and module must have the same name
+ class_ = getattr(self.__m, self.__name)
+ self.__window = class_(self.__sb, self.__root)
+ self.__sb.add_view(self.__window)
+ self.__window.deiconify()
+
+ def __cmp__(self, other):
+ return cmp(self.__menutext, other.__menutext)
+
+
+def make_view_popups(switchboard, root, extrapath):
+ viewers = []
+ # where we are in the file system
+ dirs = [os.path.dirname(__file__)] + extrapath
+ for dir in dirs:
+ if dir == '':
+ dir = '.'
+ for file in os.listdir(dir):
+ if file[-9:] == 'Viewer.py':
+ name = file[:-3]
+ try:
+ module = __import__(name)
+ except ImportError:
+ # Pynche is running from inside a package, so get the
+ # module using the explicit path.
+ pkg = __import__('pynche.'+name)
+ module = getattr(pkg, name)
+ if hasattr(module, 'ADDTOVIEW') and module.ADDTOVIEW:
+ # this is an external viewer
+ v = PopupViewer(module, name, switchboard, root)
+ viewers.append(v)
+ # sort alphabetically
+ viewers.sort()
+ return viewers
diff --git a/sys/src/cmd/python/Tools/pynche/README b/sys/src/cmd/python/Tools/pynche/README
new file mode 100644
index 000000000..d20efc316
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/README
@@ -0,0 +1,398 @@
+Pynche - The PYthonically Natural Color and Hue Editor
+
+Contact: Barry A. Warsaw
+Email: bwarsaw@python.org
+Version: 1.3
+
+Introduction
+
+ Pynche is a color editor based largely on a similar program that I
+ originally wrote back in 1987 for the Sunview window system. That
+ editor was called ICE, the Interactive Color Editor. I'd always
+ wanted to port this program to X but didn't feel like hacking X
+ and C code to do it. Fast forward many years, to where Python +
+ Tkinter provides such a nice programming environment, with enough
+ power, that I finally buckled down and re-implemented it. I
+ changed the name because these days, too many other systems have
+ the acronym `ICE'.
+
+ Pynche should work with any variant of Python after 1.5.2
+ (e.g. 2.0.1 and 2.1.1), using Tk 8.0.x. It's been tested on
+ Solaris 2.6, Windows NT 4, and various Linux distros. You'll want
+ to be sure to have at least Tk 8.0.3 for Windows. Also, Pynche is
+ very colormap intensive, so it doesn't work very well on 8-bit
+ graphics cards; 24bit+ graphics cards are so cheap these days,
+ I'll probably never "fix" that.
+
+ Pynche must find a text database of colors names in order to
+ provide `nearest' color matching. Pynche is distributed with an
+ rgb.txt file from the X11R6.4 distribution for this reason, along
+ with other "Web related" database (see below). You can use a
+ different file with the -d option. The file xlicense.txt contains
+ the license only for rgb.txt and both files are in the X/
+ subdirectory.
+
+ Pynche is pronounced: Pin'-chee
+
+
+Running Standalone
+
+ On Unix, start it by running the `pynche' script. On Windows, run
+ pynche.pyw to inhibit the console window. When run from the
+ command line, the following options are recognized:
+
+ --database file
+ -d file
+ Alternate location of the color database file. Without this
+ option, the first valid file found will be used (see below).
+
+ --initfile file
+ -i file
+ Alternate location of the persistent initialization file. See
+ the section on Persistency below.
+
+ --ignore
+ -X
+ Ignore the persistent initialization file when starting up.
+ Pynche will still write the current option settings to the
+ persistent init file when it quits.
+
+ --help
+ -h
+ Print the help message.
+
+ initialcolor
+ a Tk color name or #rrggbb color spec to be used as the
+ initially selected color. This overrides any color saved in
+ the persistent init file. Since `#' needs to be escaped in
+ many shells, it is optional in the spec (e.g. #45dd1f is the
+ same as 45dd1f).
+
+
+Running as a Modal Dialog
+
+ Pynche can be run as a modal dialog, inside another application,
+ say as a general color chooser. In fact, Grail 0.6 uses Pynche
+ and a future version of IDLE may as well. Pynche supports the API
+ implemented by the Tkinter standard tkColorChooser module, with a
+ few changes as described below. By importing pyColorChooser from
+ the Pynche package, you can run
+
+ pyColorChooser.askcolor()
+
+ which will popup Pynche as a modal dialog, and return the selected
+ color.
+
+ There are some UI differences when running as a modal
+ vs. standalone. When running as a modal, there is no "Quit" menu
+ item under the "File" menu. Instead there are "Okay" and "Cancel"
+ buttons.
+
+ When "Okay" is hit, askcolor() returns the tuple
+
+ ((r, g, b), "name")
+
+ where r, g, and b are red, green, and blue color values
+ respectively (in the range 0 to 255). "name" will be a color name
+ from the color database if there is an exact match, otherwise it
+ will be an X11 color spec of the form "#rrggbb". Note that this
+ is different than tkColorChooser, which doesn't know anything
+ about color names.
+
+ askcolor() supports the following optional keyword arguments:
+
+ color
+ the color to set as the initial selected color
+
+ master[*]
+ the master window to use as the parent of the modal
+ dialog. Without this argument, pyColorChooser will create
+ its own Tkinter.Tk instance as the master. This may not
+ be what you want.
+
+ databasefile
+ similar to the --database option, the value must be a
+ file name
+
+ initfile[*]
+ similar to the --initfile option, the value must be a
+ file name
+
+ ignore[*]
+ similar to the --ignore flag, the value is a boolean
+
+ wantspec
+ When this is true, the "name" field in the return tuple
+ will always be a color spec of the form "#rrggbb". It
+ will not return a color name even if there is a match;
+ this is so pyColorChooser can exactly match the API of
+ tkColorChooser.
+
+ [*] these arguments must be specified the first time
+ askcolor() is used and cannot be changed on subsequent calls.
+
+
+The Colorstrip Window
+
+ The top part of the main Pynche window contains the "variation
+ strips". Each strip contains a number of "color chips". The
+ strips always indicate the currently selected color by a highlight
+ rectangle around the selected color chip, with an arrow pointing
+ to the chip. Each arrow has an associated number giving you the
+ color value along the variation's axis. Each variation strip
+ shows you the colors that are reachable from the selected color by
+ varying just one axis of the color solid.
+
+ For example, when the selected color is (in Red/Green/Blue
+ notation) 127/127/127, the Red Variations strip shows you every
+ color in the range 0/127/127 to 255/127/127. Similarly for the
+ green and blue axes. You can select any color by clicking on its
+ chip. This will update the highlight rectangle and the arrow, as
+ well as other displays in Pynche.
+
+ Click on "Update while dragging" if you want Pynche to update the
+ selected color while you drag along any variation strip (this will
+ be a bit slower). Click on "Hexadecimal" to display the arrow
+ numbers in hex.
+
+ There are also two shortcut buttons in this window, which
+ auto-select Black (0/0/0) and White (255/255/255).
+
+
+The Proof Window
+
+ In the lower left corner of the main window you see two larger
+ color chips. The Selected chip shows you a larger version of the
+ color selected in the variation strips, along with its X11 color
+ specification. The Nearest chip shows you the closest color in
+ the X11 database to the selected color, giving its X11 color
+ specification, and below that, its X11 color name. When the
+ Selected chip color exactly matches the Nearest chip color, you
+ will see the color name appear below the color specification for
+ the Selected chip.
+
+ Clicking on the Nearest color chip selects that color. Color
+ distance is calculated in the 3D space of the RGB color solid and
+ if more than one color name is the same distance from the selected
+ color, the first one found will be chosen.
+
+ Note that there may be more than one X11 color name for the same
+ RGB value. In that case, the first one found in the text database
+ is designated the "primary" name, and this is shown under the
+ Nearest chip. The other names are "aliases" and they are visible
+ in the Color List Window (see below).
+
+ Both the color specifications and color names are selectable for
+ copying and pasting into another window.
+
+
+The Type-in Window
+
+ At the lower right of the main window are three entry fields.
+ Here you can type numeric values for any of the three color axes.
+ Legal values are between 0 and 255, and these fields do not allow
+ you to enter illegal values. You must hit Enter or Tab to select
+ the new color.
+
+ Click on "Update while typing" if you want Pynche to select the
+ color on every keystroke (well, every one that produces a legal
+ value!) Click on "Hexadecimal" to display and enter color values
+ in hex.
+
+
+Other Views
+
+ There are three secondary windows which are not displayed by
+ default. You can bring these up via the "View" menu on the main
+ Pynche window.
+
+
+The Text Window
+
+ The "Text Window" allows you to see what effects various colors
+ have on the standard Tk text widget elements. In the upper part
+ of the window is a plain Tk text widget and here you can edit the
+ text, select a region of text, etc. Below this is a button "Track
+ color changes". When this is turned on, any colors selected in
+ the other windows will change the text widget element specified in
+ the radio buttons below. When this is turned off, text widget
+ elements are not affected by color selection.
+
+ You can choose which element gets changed by color selection by
+ clicking on one of the radio buttons in the bottom part of this
+ window. Text foreground and background affect the text in the
+ upper part of the window. Selection foreground and background
+ affect the colors of the primary selection which is what you see
+ when you click the middle button (depending on window system) and
+ drag it through some text.
+
+ The Insertion is the insertion cursor in the text window, where
+ new text will be inserted as you type. The insertion cursor only
+ has a background.
+
+
+The Color List Window
+
+ The "Color List" window shows every named color in the color name
+ database (this window may take a while to come up). In the upper
+ part of the window you see a scrolling list of all the color names
+ in the database, in alphabetical order. Click on any color to
+ select it. In the bottom part of the window is displayed any
+ aliases for the selected color (those color names that have the
+ same RGB value, but were found later in the text database). For
+ example, find the color "Black" and you'll see that its aliases
+ are "gray0" and "grey0".
+
+ If the color has no aliases you'll see "<no aliases>" here. If you
+ just want to see if a color has an alias, and do not want to select a
+ color when you click on it, turn off "Update on Click".
+
+ Note that the color list is always updated when a color is selected
+ from the main window. There's no way to turn this feature off. If
+ the selected color has no matching color name you'll see
+ "<no matching color>" in the Aliases window.
+
+
+The Details Window
+
+ The "Details" window gives you more control over color selection
+ than just clicking on a color chip in the main window. The row of
+ buttons along the top apply the specified increment and decrement
+ amounts to the selected color. These delta amounts are applied to
+ the variation strips specified by the check boxes labeled "Move
+ Sliders". Thus if just Red and Green are selected, hitting -10
+ will subtract 10 from the color value along the red and green
+ variation only. Note the message under the checkboxes; this
+ indicates the primary color level being changed when more than one
+ slider is tied together. For example, if Red and Green are
+ selected, you will be changing the Yellow level of the selected
+ color.
+
+ The "At Boundary" behavior determines what happens when any color
+ variation hits either the lower or upper boundaries (0 or 255) as
+ a result of clicking on the top row buttons:
+
+ Stop
+ When the increment or decrement would send any of the tied
+ variations out of bounds, the entire delta is discarded.
+
+ Wrap Around
+ When the increment or decrement would send any of the tied
+ variations out of bounds, the out of bounds value is wrapped
+ around to the other side. Thus if red were at 238 and +25
+ were clicked, red would have the value 7.
+
+ Preserve Distance
+ When the increment or decrement would send any of the tied
+ variations out of bounds, all tied variations are wrapped as
+ one, so as to preserve the distance between them. Thus if
+ green and blue were tied, and green was at 238 while blue was
+ at 223, and +25 were clicked, green would be at 15 and blue
+ would be at 0.
+
+ Squash
+ When the increment or decrement would send any of the tied
+ variations out of bounds, the out of bounds variation is set
+ to the ceiling of 255 or floor of 0, as appropriate. In this
+ way, all tied variations are squashed to one edge or the
+ other.
+
+ The top row buttons have the following keyboard accelerators:
+
+ -25 == Shift Left Arrow
+ -10 == Control Left Arrow
+ -1 == Left Arrow
+ +1 == Right Arrow
+ +10 == Control Right Arrow
+ +25 == Shift Right Arrow
+
+
+Keyboard Accelerators
+
+ Alt-w in any secondary window dismisses the window. In the main
+ window it exits Pynche (except when running as a modal).
+
+ Alt-q in any window exits Pynche (except when running as a modal).
+
+
+Persistency
+
+ Pynche remembers various settings of options and colors between
+ invocations, storing these values in a `persistent initialization
+ file'. The actual location of this file is specified by the
+ --initfile option (see above), and defaults to ~/.pynche.
+
+ When Pynche exits, it saves these values in the init file, and
+ re-reads them when it starts up. There is no locking on this
+ file, so if you run multiple instances of Pynche at a time, you
+ may clobber the init file.
+
+ The actual options stored include
+
+ - the currently selected color
+
+ - all settings of checkbox and radio button options in all windows
+
+ - the contents of the text window, the current text selection and
+ insertion point, and all current text widget element color
+ settings.
+
+ - the name of the color database file (but not its contents)
+
+ You can inhibit Pynche from reading the init file by supplying the
+ --ignore option on the command line. However, you cannot suppress
+ the storing of the settings in the init file on Pynche exit. If
+ you really want to do this, use /dev/null as the init file, using
+ --initfile.
+
+
+Color Name Database Files
+
+ Pynche uses a color name database file to calculate the nearest
+ color to the selected color, and to display in the Color List
+ view. Several files are distributed with Pynche, described
+ below. By default, the X11 color name database file is selected.
+ Other files:
+
+ html40colors.txt -- the HTML 4.0 guaranteed color names
+
+ websafe.txt -- the 216 "Web-safe" colors that Netscape and MSIE
+ guarantee will not be dithered. These are specified in #rrggbb
+ format for both values and names
+
+ webcolors.txt -- The 140 color names that Tim Peters and his
+ sister say NS and MSIE both understand (with some controversy over
+ AliceBlue).
+
+ namedcolors.txt -- an alternative set of Netscape colors.
+
+ You can switch between files by choosing "Load palette..." from
+ the "File" menu. This brings up a standard Tk file dialog.
+ Choose the file you want and then click "Ok". If Pynche
+ understands the format in this file, it will load the database and
+ update the appropriate windows. If not, it will bring up an error
+ dialog.
+
+
+To Do
+
+ Here's a brief list of things I want to do (some mythical day):
+
+ - Better support for resizing the top level windows
+
+ - More output views, e.g. color solids
+
+ - Have the notion of a `last color selected'; this may require a
+ new output view
+
+ - Support setting the font in the text view
+
+ - Support distutils setup.py for installation
+
+ I'm open to suggestions!
+
+
+
+Local Variables:
+indent-tabs-mode: nil
+End:
diff --git a/sys/src/cmd/python/Tools/pynche/StripViewer.py b/sys/src/cmd/python/Tools/pynche/StripViewer.py
new file mode 100644
index 000000000..01bcbf6d0
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/StripViewer.py
@@ -0,0 +1,433 @@
+"""Strip viewer and related widgets.
+
+The classes in this file implement the StripViewer shown in the top two thirds
+of the main Pynche window. It consists of three StripWidgets which display
+the variations in red, green, and blue respectively of the currently selected
+r/g/b color value.
+
+Each StripWidget shows the color variations that are reachable by varying an
+axis of the currently selected color. So for example, if the color is
+
+ (R,G,B)=(127,163,196)
+
+then the Red variations show colors from (0,163,196) to (255,163,196), the
+Green variations show colors from (127,0,196) to (127,255,196), and the Blue
+variations show colors from (127,163,0) to (127,163,255).
+
+The selected color is always visible in all three StripWidgets, and in fact
+each StripWidget highlights the selected color, and has an arrow pointing to
+the selected chip, which includes the value along that particular axis.
+
+Clicking on any chip in any StripWidget selects that color, and updates all
+arrows and other windows. By toggling on Update while dragging, Pynche will
+select the color under the cursor while you drag it, but be forewarned that
+this can be slow.
+"""
+
+from Tkinter import *
+import ColorDB
+
+# Load this script into the Tcl interpreter and call it in
+# StripWidget.set_color(). This is about as fast as it can be with the
+# current _tkinter.c interface, which doesn't support Tcl Objects.
+TCLPROC = '''\
+proc setcolor {canv colors} {
+ set i 1
+ foreach c $colors {
+ $canv itemconfigure $i -fill $c -outline $c
+ incr i
+ }
+}
+'''
+
+# Tcl event types
+BTNDOWN = 4
+BTNUP = 5
+BTNDRAG = 6
+
+SPACE = ' '
+
+
+
+def constant(numchips):
+ step = 255.0 / (numchips - 1)
+ start = 0.0
+ seq = []
+ while numchips > 0:
+ seq.append(int(start))
+ start = start + step
+ numchips = numchips - 1
+ return seq
+
+# red variations, green+blue = cyan constant
+def constant_red_generator(numchips, red, green, blue):
+ seq = constant(numchips)
+ return map(None, [red] * numchips, seq, seq)
+
+# green variations, red+blue = magenta constant
+def constant_green_generator(numchips, red, green, blue):
+ seq = constant(numchips)
+ return map(None, seq, [green] * numchips, seq)
+
+# blue variations, red+green = yellow constant
+def constant_blue_generator(numchips, red, green, blue):
+ seq = constant(numchips)
+ return map(None, seq, seq, [blue] * numchips)
+
+# red variations, green+blue = cyan constant
+def constant_cyan_generator(numchips, red, green, blue):
+ seq = constant(numchips)
+ return map(None, seq, [green] * numchips, [blue] * numchips)
+
+# green variations, red+blue = magenta constant
+def constant_magenta_generator(numchips, red, green, blue):
+ seq = constant(numchips)
+ return map(None, [red] * numchips, seq, [blue] * numchips)
+
+# blue variations, red+green = yellow constant
+def constant_yellow_generator(numchips, red, green, blue):
+ seq = constant(numchips)
+ return map(None, [red] * numchips, [green] * numchips, seq)
+
+
+
+class LeftArrow:
+ _ARROWWIDTH = 30
+ _ARROWHEIGHT = 15
+ _YOFFSET = 13
+ _TEXTYOFFSET = 1
+ _TAG = ('leftarrow',)
+
+ def __init__(self, canvas, x):
+ self._canvas = canvas
+ self.__arrow, self.__text = self._create(x)
+ self.move_to(x)
+
+ def _create(self, x):
+ arrow = self._canvas.create_line(
+ x, self._ARROWHEIGHT + self._YOFFSET,
+ x, self._YOFFSET,
+ x + self._ARROWWIDTH, self._YOFFSET,
+ arrow='first',
+ width=3.0,
+ tags=self._TAG)
+ text = self._canvas.create_text(
+ x + self._ARROWWIDTH + 13,
+ self._ARROWHEIGHT - self._TEXTYOFFSET,
+ tags=self._TAG,
+ text='128')
+ return arrow, text
+
+ def _x(self):
+ coords = self._canvas.coords(self._TAG)
+ assert coords
+ return coords[0]
+
+ def move_to(self, x):
+ deltax = x - self._x()
+ self._canvas.move(self._TAG, deltax, 0)
+
+ def set_text(self, text):
+ self._canvas.itemconfigure(self.__text, text=text)
+
+
+class RightArrow(LeftArrow):
+ _TAG = ('rightarrow',)
+
+ def _create(self, x):
+ arrow = self._canvas.create_line(
+ x, self._YOFFSET,
+ x + self._ARROWWIDTH, self._YOFFSET,
+ x + self._ARROWWIDTH, self._ARROWHEIGHT + self._YOFFSET,
+ arrow='last',
+ width=3.0,
+ tags=self._TAG)
+ text = self._canvas.create_text(
+ x - self._ARROWWIDTH + 15, # BAW: kludge
+ self._ARROWHEIGHT - self._TEXTYOFFSET,
+ justify=RIGHT,
+ text='128',
+ tags=self._TAG)
+ return arrow, text
+
+ def _x(self):
+ coords = self._canvas.coords(self._TAG)
+ assert coords
+ return coords[0] + self._ARROWWIDTH
+
+
+
+class StripWidget:
+ _CHIPHEIGHT = 50
+ _CHIPWIDTH = 10
+ _NUMCHIPS = 40
+
+ def __init__(self, switchboard,
+ master = None,
+ chipwidth = _CHIPWIDTH,
+ chipheight = _CHIPHEIGHT,
+ numchips = _NUMCHIPS,
+ generator = None,
+ axis = None,
+ label = '',
+ uwdvar = None,
+ hexvar = None):
+ # instance variables
+ self.__generator = generator
+ self.__axis = axis
+ self.__numchips = numchips
+ assert self.__axis in (0, 1, 2)
+ self.__uwd = uwdvar
+ self.__hexp = hexvar
+ # the last chip selected
+ self.__lastchip = None
+ self.__sb = switchboard
+
+ canvaswidth = numchips * (chipwidth + 1)
+ canvasheight = chipheight + 43 # BAW: Kludge
+
+ # create the canvas and pack it
+ canvas = self.__canvas = Canvas(master,
+ width=canvaswidth,
+ height=canvasheight,
+## borderwidth=2,
+## relief=GROOVE
+ )
+
+ canvas.pack()
+ canvas.bind('<ButtonPress-1>', self.__select_chip)
+ canvas.bind('<ButtonRelease-1>', self.__select_chip)
+ canvas.bind('<B1-Motion>', self.__select_chip)
+
+ # Load a proc into the Tcl interpreter. This is used in the
+ # set_color() method to speed up setting the chip colors.
+ canvas.tk.eval(TCLPROC)
+
+ # create the color strip
+ chips = self.__chips = []
+ x = 1
+ y = 30
+ tags = ('chip',)
+ for c in range(self.__numchips):
+ color = 'grey'
+ canvas.create_rectangle(
+ x, y, x+chipwidth, y+chipheight,
+ fill=color, outline=color,
+ tags=tags)
+ x = x + chipwidth + 1 # for outline
+ chips.append(color)
+
+ # create the strip label
+ self.__label = canvas.create_text(
+ 3, y + chipheight + 8,
+ text=label,
+ anchor=W)
+
+ # create the arrow and text item
+ chipx = self.__arrow_x(0)
+ self.__leftarrow = LeftArrow(canvas, chipx)
+
+ chipx = self.__arrow_x(len(chips) - 1)
+ self.__rightarrow = RightArrow(canvas, chipx)
+
+ def __arrow_x(self, chipnum):
+ coords = self.__canvas.coords(chipnum+1)
+ assert coords
+ x0, y0, x1, y1 = coords
+ return (x1 + x0) / 2.0
+
+ # Invoked when one of the chips is clicked. This should just tell the
+ # switchboard to set the color on all the output components
+ def __select_chip(self, event=None):
+ x = event.x
+ y = event.y
+ canvas = self.__canvas
+ chip = canvas.find_overlapping(x, y, x, y)
+ if chip and (1 <= chip[0] <= self.__numchips):
+ color = self.__chips[chip[0]-1]
+ red, green, blue = ColorDB.rrggbb_to_triplet(color)
+ etype = int(event.type)
+ if (etype == BTNUP or self.__uwd.get()):
+ # update everyone
+ self.__sb.update_views(red, green, blue)
+ else:
+ # just track the arrows
+ self.__trackarrow(chip[0], (red, green, blue))
+
+ def __trackarrow(self, chip, rgbtuple):
+ # invert the last chip
+ if self.__lastchip is not None:
+ color = self.__canvas.itemcget(self.__lastchip, 'fill')
+ self.__canvas.itemconfigure(self.__lastchip, outline=color)
+ self.__lastchip = chip
+ # get the arrow's text
+ coloraxis = rgbtuple[self.__axis]
+ if self.__hexp.get():
+ # hex
+ text = hex(coloraxis)
+ else:
+ # decimal
+ text = repr(coloraxis)
+ # move the arrow, and set its text
+ if coloraxis <= 128:
+ # use the left arrow
+ self.__leftarrow.set_text(text)
+ self.__leftarrow.move_to(self.__arrow_x(chip-1))
+ self.__rightarrow.move_to(-100)
+ else:
+ # use the right arrow
+ self.__rightarrow.set_text(text)
+ self.__rightarrow.move_to(self.__arrow_x(chip-1))
+ self.__leftarrow.move_to(-100)
+ # and set the chip's outline
+ brightness = ColorDB.triplet_to_brightness(rgbtuple)
+ if brightness <= 128:
+ outline = 'white'
+ else:
+ outline = 'black'
+ self.__canvas.itemconfigure(chip, outline=outline)
+
+
+ def update_yourself(self, red, green, blue):
+ assert self.__generator
+ i = 1
+ chip = 0
+ chips = self.__chips = []
+ tk = self.__canvas.tk
+ # get the red, green, and blue components for all chips
+ for t in self.__generator(self.__numchips, red, green, blue):
+ rrggbb = ColorDB.triplet_to_rrggbb(t)
+ chips.append(rrggbb)
+ tred, tgreen, tblue = t
+ if tred <= red and tgreen <= green and tblue <= blue:
+ chip = i
+ i = i + 1
+ # call the raw tcl script
+ colors = SPACE.join(chips)
+ tk.eval('setcolor %s {%s}' % (self.__canvas._w, colors))
+ # move the arrows around
+ self.__trackarrow(chip, (red, green, blue))
+
+ def set(self, label, generator):
+ self.__canvas.itemconfigure(self.__label, text=label)
+ self.__generator = generator
+
+
+class StripViewer:
+ def __init__(self, switchboard, master=None):
+ self.__sb = switchboard
+ optiondb = switchboard.optiondb()
+ # create a frame inside the master.
+ frame = Frame(master, relief=RAISED, borderwidth=1)
+ frame.grid(row=1, column=0, columnspan=2, sticky='NSEW')
+ # create the options to be used later
+ uwd = self.__uwdvar = BooleanVar()
+ uwd.set(optiondb.get('UPWHILEDRAG', 0))
+ hexp = self.__hexpvar = BooleanVar()
+ hexp.set(optiondb.get('HEXSTRIP', 0))
+ # create the red, green, blue strips inside their own frame
+ frame1 = Frame(frame)
+ frame1.pack(expand=YES, fill=BOTH)
+ self.__reds = StripWidget(switchboard, frame1,
+ generator=constant_cyan_generator,
+ axis=0,
+ label='Red Variations',
+ uwdvar=uwd, hexvar=hexp)
+
+ self.__greens = StripWidget(switchboard, frame1,
+ generator=constant_magenta_generator,
+ axis=1,
+ label='Green Variations',
+ uwdvar=uwd, hexvar=hexp)
+
+ self.__blues = StripWidget(switchboard, frame1,
+ generator=constant_yellow_generator,
+ axis=2,
+ label='Blue Variations',
+ uwdvar=uwd, hexvar=hexp)
+
+ # create a frame to contain the controls
+ frame2 = Frame(frame)
+ frame2.pack(expand=YES, fill=BOTH)
+ frame2.columnconfigure(0, weight=20)
+ frame2.columnconfigure(2, weight=20)
+
+ padx = 8
+
+ # create the black button
+ blackbtn = Button(frame2,
+ text='Black',
+ command=self.__toblack)
+ blackbtn.grid(row=0, column=0, rowspan=2, sticky=W, padx=padx)
+
+ # create the controls
+ uwdbtn = Checkbutton(frame2,
+ text='Update while dragging',
+ variable=uwd)
+ uwdbtn.grid(row=0, column=1, sticky=W)
+ hexbtn = Checkbutton(frame2,
+ text='Hexadecimal',
+ variable=hexp,
+ command=self.__togglehex)
+ hexbtn.grid(row=1, column=1, sticky=W)
+
+ # XXX: ignore this feature for now; it doesn't work quite right yet
+
+## gentypevar = self.__gentypevar = IntVar()
+## self.__variations = Radiobutton(frame,
+## text='Variations',
+## variable=gentypevar,
+## value=0,
+## command=self.__togglegentype)
+## self.__variations.grid(row=0, column=1, sticky=W)
+## self.__constants = Radiobutton(frame,
+## text='Constants',
+## variable=gentypevar,
+## value=1,
+## command=self.__togglegentype)
+## self.__constants.grid(row=1, column=1, sticky=W)
+
+ # create the white button
+ whitebtn = Button(frame2,
+ text='White',
+ command=self.__towhite)
+ whitebtn.grid(row=0, column=2, rowspan=2, sticky=E, padx=padx)
+
+ def update_yourself(self, red, green, blue):
+ self.__reds.update_yourself(red, green, blue)
+ self.__greens.update_yourself(red, green, blue)
+ self.__blues.update_yourself(red, green, blue)
+
+ def __togglehex(self, event=None):
+ red, green, blue = self.__sb.current_rgb()
+ self.update_yourself(red, green, blue)
+
+## def __togglegentype(self, event=None):
+## which = self.__gentypevar.get()
+## if which == 0:
+## self.__reds.set(label='Red Variations',
+## generator=constant_cyan_generator)
+## self.__greens.set(label='Green Variations',
+## generator=constant_magenta_generator)
+## self.__blues.set(label='Blue Variations',
+## generator=constant_yellow_generator)
+## elif which == 1:
+## self.__reds.set(label='Red Constant',
+## generator=constant_red_generator)
+## self.__greens.set(label='Green Constant',
+## generator=constant_green_generator)
+## self.__blues.set(label='Blue Constant',
+## generator=constant_blue_generator)
+## else:
+## assert 0
+## self.__sb.update_views_current()
+
+ def __toblack(self, event=None):
+ self.__sb.update_views(0, 0, 0)
+
+ def __towhite(self, event=None):
+ self.__sb.update_views(255, 255, 255)
+
+ def save_options(self, optiondb):
+ optiondb['UPWHILEDRAG'] = self.__uwdvar.get()
+ optiondb['HEXSTRIP'] = self.__hexpvar.get()
diff --git a/sys/src/cmd/python/Tools/pynche/Switchboard.py b/sys/src/cmd/python/Tools/pynche/Switchboard.py
new file mode 100644
index 000000000..ee83d4766
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/Switchboard.py
@@ -0,0 +1,139 @@
+"""Switchboard class.
+
+This class is used to coordinate updates among all Viewers. Every Viewer must
+conform to the following interface:
+
+ - it must include a method called update_yourself() which takes three
+ arguments; the red, green, and blue values of the selected color.
+
+ - When a Viewer selects a color and wishes to update all other Views, it
+ should call update_views() on the Switchboard object. Note that the
+ Viewer typically does *not* update itself before calling update_views(),
+ since this would cause it to get updated twice.
+
+Optionally, Viewers can also implement:
+
+ - save_options() which takes an optiondb (a dictionary). Store into this
+ dictionary any values the Viewer wants to save in the persistent
+ ~/.pynche file. This dictionary is saved using marshal. The namespace
+ for the keys is ad-hoc; make sure you don't clobber some other Viewer's
+ keys!
+
+ - withdraw() which takes no arguments. This is called when Pynche is
+ unmapped. All Viewers should implement this.
+
+ - colordb_changed() which takes a single argument, an instance of
+ ColorDB. This is called whenever the color name database is changed and
+ gives a chance for the Viewers to do something on those events. See
+ ListViewer for details.
+
+External Viewers are found dynamically. Viewer modules should have names such
+as FooViewer.py. If such a named module has a module global variable called
+ADDTOVIEW and this variable is true, the Viewer will be added dynamically to
+the `View' menu. ADDTOVIEW contains a string which is used as the menu item
+to display the Viewer (one kludge: if the string contains a `%', this is used
+to indicate that the next character will get an underline in the menu,
+otherwise the first character is underlined).
+
+FooViewer.py should contain a class called FooViewer, and its constructor
+should take two arguments, an instance of Switchboard, and optionally a Tk
+master window.
+
+"""
+
+import sys
+from types import DictType
+import marshal
+
+
+
+class Switchboard:
+ def __init__(self, initfile):
+ self.__initfile = initfile
+ self.__colordb = None
+ self.__optiondb = {}
+ self.__views = []
+ self.__red = 0
+ self.__green = 0
+ self.__blue = 0
+ self.__canceled = 0
+ # read the initialization file
+ fp = None
+ if initfile:
+ try:
+ try:
+ fp = open(initfile)
+ self.__optiondb = marshal.load(fp)
+ if not isinstance(self.__optiondb, DictType):
+ print >> sys.stderr, \
+ 'Problem reading options from file:', initfile
+ self.__optiondb = {}
+ except (IOError, EOFError, ValueError):
+ pass
+ finally:
+ if fp:
+ fp.close()
+
+ def add_view(self, view):
+ self.__views.append(view)
+
+ def update_views(self, red, green, blue):
+ self.__red = red
+ self.__green = green
+ self.__blue = blue
+ for v in self.__views:
+ v.update_yourself(red, green, blue)
+
+ def update_views_current(self):
+ self.update_views(self.__red, self.__green, self.__blue)
+
+ def current_rgb(self):
+ return self.__red, self.__green, self.__blue
+
+ def colordb(self):
+ return self.__colordb
+
+ def set_colordb(self, colordb):
+ self.__colordb = colordb
+ for v in self.__views:
+ if hasattr(v, 'colordb_changed'):
+ v.colordb_changed(colordb)
+ self.update_views_current()
+
+ def optiondb(self):
+ return self.__optiondb
+
+ def save_views(self):
+ # save the current color
+ self.__optiondb['RED'] = self.__red
+ self.__optiondb['GREEN'] = self.__green
+ self.__optiondb['BLUE'] = self.__blue
+ for v in self.__views:
+ if hasattr(v, 'save_options'):
+ v.save_options(self.__optiondb)
+ # save the name of the file used for the color database. we'll try to
+ # load this first.
+ self.__optiondb['DBFILE'] = self.__colordb.filename()
+ fp = None
+ try:
+ try:
+ fp = open(self.__initfile, 'w')
+ except IOError:
+ print >> sys.stderr, 'Cannot write options to file:', \
+ self.__initfile
+ else:
+ marshal.dump(self.__optiondb, fp)
+ finally:
+ if fp:
+ fp.close()
+
+ def withdraw_views(self):
+ for v in self.__views:
+ if hasattr(v, 'withdraw'):
+ v.withdraw()
+
+ def canceled(self, flag=1):
+ self.__canceled = flag
+
+ def canceled_p(self):
+ return self.__canceled
diff --git a/sys/src/cmd/python/Tools/pynche/TextViewer.py b/sys/src/cmd/python/Tools/pynche/TextViewer.py
new file mode 100644
index 000000000..456bd96e3
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/TextViewer.py
@@ -0,0 +1,188 @@
+"""TextViewer class.
+
+The TextViewer allows you to see how the selected color would affect various
+characteristics of a Tk text widget. This is an output viewer only.
+
+In the top part of the window is a standard text widget with some sample text
+in it. You are free to edit this text in any way you want (BAW: allow you to
+change font characteristics). If you want changes in other viewers to update
+text characteristics, turn on Track color changes.
+
+To select which characteristic tracks the change, select one of the radio
+buttons in the window below. Text foreground and background affect the text
+in the window above. The Selection is what you see when you click the middle
+button and drag it through some text. The Insertion is the insertion cursor
+in the text window (which only has a background).
+"""
+
+from Tkinter import *
+import ColorDB
+
+ADDTOVIEW = 'Text Window...'
+
+
+
+class TextViewer:
+ def __init__(self, switchboard, master=None):
+ self.__sb = switchboard
+ optiondb = switchboard.optiondb()
+ root = self.__root = Toplevel(master, class_='Pynche')
+ root.protocol('WM_DELETE_WINDOW', self.withdraw)
+ root.title('Pynche Text Window')
+ root.iconname('Pynche Text Window')
+ root.bind('<Alt-q>', self.__quit)
+ root.bind('<Alt-Q>', self.__quit)
+ root.bind('<Alt-w>', self.withdraw)
+ root.bind('<Alt-W>', self.withdraw)
+ #
+ # create the text widget
+ #
+ self.__text = Text(root, relief=SUNKEN,
+ background=optiondb.get('TEXTBG', 'black'),
+ foreground=optiondb.get('TEXTFG', 'white'),
+ width=35, height=15)
+ sfg = optiondb.get('TEXT_SFG')
+ if sfg:
+ self.__text.configure(selectforeground=sfg)
+ sbg = optiondb.get('TEXT_SBG')
+ if sbg:
+ self.__text.configure(selectbackground=sbg)
+ ibg = optiondb.get('TEXT_IBG')
+ if ibg:
+ self.__text.configure(insertbackground=ibg)
+ self.__text.pack()
+ self.__text.insert(0.0, optiondb.get('TEXT', '''\
+Insert some stuff here and play
+with the buttons below to see
+how the colors interact in
+textual displays.
+
+See how the selection can also
+be affected by tickling the buttons
+and choosing a color.'''))
+ insert = optiondb.get('TEXTINS')
+ if insert:
+ self.__text.mark_set(INSERT, insert)
+ try:
+ start, end = optiondb.get('TEXTSEL', (6.0, END))
+ self.__text.tag_add(SEL, start, end)
+ except ValueError:
+ # selection wasn't set
+ pass
+ self.__text.focus_set()
+ #
+ # variables
+ self.__trackp = BooleanVar()
+ self.__trackp.set(optiondb.get('TRACKP', 0))
+ self.__which = IntVar()
+ self.__which.set(optiondb.get('WHICH', 0))
+ #
+ # track toggle
+ self.__t = Checkbutton(root, text='Track color changes',
+ variable=self.__trackp,
+ relief=GROOVE,
+ command=self.__toggletrack)
+ self.__t.pack(fill=X, expand=YES)
+ frame = self.__frame = Frame(root)
+ frame.pack()
+ #
+ # labels
+ self.__labels = []
+ row = 2
+ for text in ('Text:', 'Selection:', 'Insertion:'):
+ l = Label(frame, text=text)
+ l.grid(row=row, column=0, sticky=E)
+ self.__labels.append(l)
+ row += 1
+ col = 1
+ for text in ('Foreground', 'Background'):
+ l = Label(frame, text=text)
+ l.grid(row=1, column=col)
+ self.__labels.append(l)
+ col += 1
+ #
+ # radios
+ self.__radios = []
+ for col in (1, 2):
+ for row in (2, 3, 4):
+ # there is no insertforeground option
+ if row==4 and col==1:
+ continue
+ r = Radiobutton(frame, variable=self.__which,
+ value=(row-2)*2 + col-1,
+ command=self.__set_color)
+ r.grid(row=row, column=col)
+ self.__radios.append(r)
+ self.__toggletrack()
+
+ def __quit(self, event=None):
+ self.__root.quit()
+
+ def withdraw(self, event=None):
+ self.__root.withdraw()
+
+ def deiconify(self, event=None):
+ self.__root.deiconify()
+
+ def __forceupdate(self, event=None):
+ self.__sb.update_views_current()
+
+ def __toggletrack(self, event=None):
+ if self.__trackp.get():
+ state = NORMAL
+ fg = self.__radios[0]['foreground']
+ else:
+ state = DISABLED
+ fg = self.__radios[0]['disabledforeground']
+ for r in self.__radios:
+ r.configure(state=state)
+ for l in self.__labels:
+ l.configure(foreground=fg)
+
+ def __set_color(self, event=None):
+ which = self.__which.get()
+ text = self.__text
+ if which == 0:
+ color = text['foreground']
+ elif which == 1:
+ color = text['background']
+ elif which == 2:
+ color = text['selectforeground']
+ elif which == 3:
+ color = text['selectbackground']
+ elif which == 5:
+ color = text['insertbackground']
+ try:
+ red, green, blue = ColorDB.rrggbb_to_triplet(color)
+ except ColorDB.BadColor:
+ # must have been a color name
+ red, green, blue = self.__sb.colordb().find_byname(color)
+ self.__sb.update_views(red, green, blue)
+
+ def update_yourself(self, red, green, blue):
+ if self.__trackp.get():
+ colorname = ColorDB.triplet_to_rrggbb((red, green, blue))
+ which = self.__which.get()
+ text = self.__text
+ if which == 0:
+ text.configure(foreground=colorname)
+ elif which == 1:
+ text.configure(background=colorname)
+ elif which == 2:
+ text.configure(selectforeground=colorname)
+ elif which == 3:
+ text.configure(selectbackground=colorname)
+ elif which == 5:
+ text.configure(insertbackground=colorname)
+
+ def save_options(self, optiondb):
+ optiondb['TRACKP'] = self.__trackp.get()
+ optiondb['WHICH'] = self.__which.get()
+ optiondb['TEXT'] = self.__text.get(0.0, 'end - 1c')
+ optiondb['TEXTSEL'] = self.__text.tag_ranges(SEL)[0:2]
+ optiondb['TEXTINS'] = self.__text.index(INSERT)
+ optiondb['TEXTFG'] = self.__text['foreground']
+ optiondb['TEXTBG'] = self.__text['background']
+ optiondb['TEXT_SFG'] = self.__text['selectforeground']
+ optiondb['TEXT_SBG'] = self.__text['selectbackground']
+ optiondb['TEXT_IBG'] = self.__text['insertbackground']
diff --git a/sys/src/cmd/python/Tools/pynche/TypeinViewer.py b/sys/src/cmd/python/Tools/pynche/TypeinViewer.py
new file mode 100644
index 000000000..bcc3cda86
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/TypeinViewer.py
@@ -0,0 +1,163 @@
+"""TypeinViewer class.
+
+The TypeinViewer is what you see at the lower right of the main Pynche
+widget. It contains three text entry fields, one each for red, green, blue.
+Input into these windows is highly constrained; it only allows you to enter
+values that are legal for a color axis. This usually means 0-255 for decimal
+input and 0x0 - 0xff for hex input.
+
+You can toggle whether you want to view and input the values in either decimal
+or hex by clicking on Hexadecimal. By clicking on Update while typing, the
+color selection will be made on every change to the text field. Otherwise,
+you must hit Return or Tab to select the color.
+"""
+
+import sys
+import re
+from Tkinter import *
+
+
+
+class TypeinViewer:
+ def __init__(self, switchboard, master=None):
+ # non-gui ivars
+ self.__sb = switchboard
+ optiondb = switchboard.optiondb()
+ self.__hexp = BooleanVar()
+ self.__hexp.set(optiondb.get('HEXTYPE', 0))
+ self.__uwtyping = BooleanVar()
+ self.__uwtyping.set(optiondb.get('UPWHILETYPE', 0))
+ # create the gui
+ self.__frame = Frame(master, relief=RAISED, borderwidth=1)
+ self.__frame.grid(row=3, column=1, sticky='NSEW')
+ # Red
+ self.__xl = Label(self.__frame, text='Red:')
+ self.__xl.grid(row=0, column=0, sticky=E)
+ subframe = Frame(self.__frame)
+ subframe.grid(row=0, column=1)
+ self.__xox = Label(subframe, text='0x')
+ self.__xox.grid(row=0, column=0, sticky=E)
+ self.__xox['font'] = 'courier'
+ self.__x = Entry(subframe, width=3)
+ self.__x.grid(row=0, column=1)
+ self.__x.bindtags(self.__x.bindtags() + ('Normalize', 'Update'))
+ self.__x.bind_class('Normalize', '<Key>', self.__normalize)
+ self.__x.bind_class('Update' , '<Key>', self.__maybeupdate)
+ # Green
+ self.__yl = Label(self.__frame, text='Green:')
+ self.__yl.grid(row=1, column=0, sticky=E)
+ subframe = Frame(self.__frame)
+ subframe.grid(row=1, column=1)
+ self.__yox = Label(subframe, text='0x')
+ self.__yox.grid(row=0, column=0, sticky=E)
+ self.__yox['font'] = 'courier'
+ self.__y = Entry(subframe, width=3)
+ self.__y.grid(row=0, column=1)
+ self.__y.bindtags(self.__y.bindtags() + ('Normalize', 'Update'))
+ # Blue
+ self.__zl = Label(self.__frame, text='Blue:')
+ self.__zl.grid(row=2, column=0, sticky=E)
+ subframe = Frame(self.__frame)
+ subframe.grid(row=2, column=1)
+ self.__zox = Label(subframe, text='0x')
+ self.__zox.grid(row=0, column=0, sticky=E)
+ self.__zox['font'] = 'courier'
+ self.__z = Entry(subframe, width=3)
+ self.__z.grid(row=0, column=1)
+ self.__z.bindtags(self.__z.bindtags() + ('Normalize', 'Update'))
+ # Update while typing?
+ self.__uwt = Checkbutton(self.__frame,
+ text='Update while typing',
+ variable=self.__uwtyping)
+ self.__uwt.grid(row=3, column=0, columnspan=2, sticky=W)
+ # Hex/Dec
+ self.__hex = Checkbutton(self.__frame,
+ text='Hexadecimal',
+ variable=self.__hexp,
+ command=self.__togglehex)
+ self.__hex.grid(row=4, column=0, columnspan=2, sticky=W)
+
+ def __togglehex(self, event=None):
+ red, green, blue = self.__sb.current_rgb()
+ if self.__hexp.get():
+ label = '0x'
+ else:
+ label = ' '
+ self.__xox['text'] = label
+ self.__yox['text'] = label
+ self.__zox['text'] = label
+ self.update_yourself(red, green, blue)
+
+ def __normalize(self, event=None):
+ ew = event.widget
+ contents = ew.get()
+ icursor = ew.index(INSERT)
+ if contents and contents[0] in 'xX' and self.__hexp.get():
+ contents = '0' + contents
+ # Figure out the contents in the current base.
+ try:
+ if self.__hexp.get():
+ v = int(contents, 16)
+ else:
+ v = int(contents)
+ except ValueError:
+ v = None
+ # If value is not legal, or empty, delete the last character inserted
+ # and ring the bell. Don't ring the bell if the field is empty (it'll
+ # just equal zero.
+ if v is None:
+ pass
+ elif v < 0 or v > 255:
+ i = ew.index(INSERT)
+ if event.char:
+ contents = contents[:i-1] + contents[i:]
+ icursor -= 1
+ ew.bell()
+ elif self.__hexp.get():
+ contents = hex(v)[2:]
+ else:
+ contents = int(v)
+ ew.delete(0, END)
+ ew.insert(0, contents)
+ ew.icursor(icursor)
+
+ def __maybeupdate(self, event=None):
+ if self.__uwtyping.get() or event.keysym in ('Return', 'Tab'):
+ self.__update(event)
+
+ def __update(self, event=None):
+ redstr = self.__x.get() or '0'
+ greenstr = self.__y.get() or '0'
+ bluestr = self.__z.get() or '0'
+ if self.__hexp.get():
+ base = 16
+ else:
+ base = 10
+ red, green, blue = [int(x, base) for x in (redstr, greenstr, bluestr)]
+ self.__sb.update_views(red, green, blue)
+
+ def update_yourself(self, red, green, blue):
+ if self.__hexp.get():
+ sred, sgreen, sblue = [hex(x)[2:] for x in (red, green, blue)]
+ else:
+ sred, sgreen, sblue = red, green, blue
+ x, y, z = self.__x, self.__y, self.__z
+ xicursor = x.index(INSERT)
+ yicursor = y.index(INSERT)
+ zicursor = z.index(INSERT)
+ x.delete(0, END)
+ y.delete(0, END)
+ z.delete(0, END)
+ x.insert(0, sred)
+ y.insert(0, sgreen)
+ z.insert(0, sblue)
+ x.icursor(xicursor)
+ y.icursor(yicursor)
+ z.icursor(zicursor)
+
+ def hexp_var(self):
+ return self.__hexp
+
+ def save_options(self, optiondb):
+ optiondb['HEXTYPE'] = self.__hexp.get()
+ optiondb['UPWHILETYPE'] = self.__uwtyping.get()
diff --git a/sys/src/cmd/python/Tools/pynche/X/rgb.txt b/sys/src/cmd/python/Tools/pynche/X/rgb.txt
new file mode 100644
index 000000000..b11ffd058
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/X/rgb.txt
@@ -0,0 +1,753 @@
+! $XConsortium: rgb.txt,v 10.41 94/02/20 18:39:36 rws Exp $
+255 250 250 snow
+248 248 255 ghost white
+248 248 255 GhostWhite
+245 245 245 white smoke
+245 245 245 WhiteSmoke
+220 220 220 gainsboro
+255 250 240 floral white
+255 250 240 FloralWhite
+253 245 230 old lace
+253 245 230 OldLace
+250 240 230 linen
+250 235 215 antique white
+250 235 215 AntiqueWhite
+255 239 213 papaya whip
+255 239 213 PapayaWhip
+255 235 205 blanched almond
+255 235 205 BlanchedAlmond
+255 228 196 bisque
+255 218 185 peach puff
+255 218 185 PeachPuff
+255 222 173 navajo white
+255 222 173 NavajoWhite
+255 228 181 moccasin
+255 248 220 cornsilk
+255 255 240 ivory
+255 250 205 lemon chiffon
+255 250 205 LemonChiffon
+255 245 238 seashell
+240 255 240 honeydew
+245 255 250 mint cream
+245 255 250 MintCream
+240 255 255 azure
+240 248 255 alice blue
+240 248 255 AliceBlue
+230 230 250 lavender
+255 240 245 lavender blush
+255 240 245 LavenderBlush
+255 228 225 misty rose
+255 228 225 MistyRose
+255 255 255 white
+ 0 0 0 black
+ 47 79 79 dark slate gray
+ 47 79 79 DarkSlateGray
+ 47 79 79 dark slate grey
+ 47 79 79 DarkSlateGrey
+105 105 105 dim gray
+105 105 105 DimGray
+105 105 105 dim grey
+105 105 105 DimGrey
+112 128 144 slate gray
+112 128 144 SlateGray
+112 128 144 slate grey
+112 128 144 SlateGrey
+119 136 153 light slate gray
+119 136 153 LightSlateGray
+119 136 153 light slate grey
+119 136 153 LightSlateGrey
+190 190 190 gray
+190 190 190 grey
+211 211 211 light grey
+211 211 211 LightGrey
+211 211 211 light gray
+211 211 211 LightGray
+ 25 25 112 midnight blue
+ 25 25 112 MidnightBlue
+ 0 0 128 navy
+ 0 0 128 navy blue
+ 0 0 128 NavyBlue
+100 149 237 cornflower blue
+100 149 237 CornflowerBlue
+ 72 61 139 dark slate blue
+ 72 61 139 DarkSlateBlue
+106 90 205 slate blue
+106 90 205 SlateBlue
+123 104 238 medium slate blue
+123 104 238 MediumSlateBlue
+132 112 255 light slate blue
+132 112 255 LightSlateBlue
+ 0 0 205 medium blue
+ 0 0 205 MediumBlue
+ 65 105 225 royal blue
+ 65 105 225 RoyalBlue
+ 0 0 255 blue
+ 30 144 255 dodger blue
+ 30 144 255 DodgerBlue
+ 0 191 255 deep sky blue
+ 0 191 255 DeepSkyBlue
+135 206 235 sky blue
+135 206 235 SkyBlue
+135 206 250 light sky blue
+135 206 250 LightSkyBlue
+ 70 130 180 steel blue
+ 70 130 180 SteelBlue
+176 196 222 light steel blue
+176 196 222 LightSteelBlue
+173 216 230 light blue
+173 216 230 LightBlue
+176 224 230 powder blue
+176 224 230 PowderBlue
+175 238 238 pale turquoise
+175 238 238 PaleTurquoise
+ 0 206 209 dark turquoise
+ 0 206 209 DarkTurquoise
+ 72 209 204 medium turquoise
+ 72 209 204 MediumTurquoise
+ 64 224 208 turquoise
+ 0 255 255 cyan
+224 255 255 light cyan
+224 255 255 LightCyan
+ 95 158 160 cadet blue
+ 95 158 160 CadetBlue
+102 205 170 medium aquamarine
+102 205 170 MediumAquamarine
+127 255 212 aquamarine
+ 0 100 0 dark green
+ 0 100 0 DarkGreen
+ 85 107 47 dark olive green
+ 85 107 47 DarkOliveGreen
+143 188 143 dark sea green
+143 188 143 DarkSeaGreen
+ 46 139 87 sea green
+ 46 139 87 SeaGreen
+ 60 179 113 medium sea green
+ 60 179 113 MediumSeaGreen
+ 32 178 170 light sea green
+ 32 178 170 LightSeaGreen
+152 251 152 pale green
+152 251 152 PaleGreen
+ 0 255 127 spring green
+ 0 255 127 SpringGreen
+124 252 0 lawn green
+124 252 0 LawnGreen
+ 0 255 0 green
+127 255 0 chartreuse
+ 0 250 154 medium spring green
+ 0 250 154 MediumSpringGreen
+173 255 47 green yellow
+173 255 47 GreenYellow
+ 50 205 50 lime green
+ 50 205 50 LimeGreen
+154 205 50 yellow green
+154 205 50 YellowGreen
+ 34 139 34 forest green
+ 34 139 34 ForestGreen
+107 142 35 olive drab
+107 142 35 OliveDrab
+189 183 107 dark khaki
+189 183 107 DarkKhaki
+240 230 140 khaki
+238 232 170 pale goldenrod
+238 232 170 PaleGoldenrod
+250 250 210 light goldenrod yellow
+250 250 210 LightGoldenrodYellow
+255 255 224 light yellow
+255 255 224 LightYellow
+255 255 0 yellow
+255 215 0 gold
+238 221 130 light goldenrod
+238 221 130 LightGoldenrod
+218 165 32 goldenrod
+184 134 11 dark goldenrod
+184 134 11 DarkGoldenrod
+188 143 143 rosy brown
+188 143 143 RosyBrown
+205 92 92 indian red
+205 92 92 IndianRed
+139 69 19 saddle brown
+139 69 19 SaddleBrown
+160 82 45 sienna
+205 133 63 peru
+222 184 135 burlywood
+245 245 220 beige
+245 222 179 wheat
+244 164 96 sandy brown
+244 164 96 SandyBrown
+210 180 140 tan
+210 105 30 chocolate
+178 34 34 firebrick
+165 42 42 brown
+233 150 122 dark salmon
+233 150 122 DarkSalmon
+250 128 114 salmon
+255 160 122 light salmon
+255 160 122 LightSalmon
+255 165 0 orange
+255 140 0 dark orange
+255 140 0 DarkOrange
+255 127 80 coral
+240 128 128 light coral
+240 128 128 LightCoral
+255 99 71 tomato
+255 69 0 orange red
+255 69 0 OrangeRed
+255 0 0 red
+255 105 180 hot pink
+255 105 180 HotPink
+255 20 147 deep pink
+255 20 147 DeepPink
+255 192 203 pink
+255 182 193 light pink
+255 182 193 LightPink
+219 112 147 pale violet red
+219 112 147 PaleVioletRed
+176 48 96 maroon
+199 21 133 medium violet red
+199 21 133 MediumVioletRed
+208 32 144 violet red
+208 32 144 VioletRed
+255 0 255 magenta
+238 130 238 violet
+221 160 221 plum
+218 112 214 orchid
+186 85 211 medium orchid
+186 85 211 MediumOrchid
+153 50 204 dark orchid
+153 50 204 DarkOrchid
+148 0 211 dark violet
+148 0 211 DarkViolet
+138 43 226 blue violet
+138 43 226 BlueViolet
+160 32 240 purple
+147 112 219 medium purple
+147 112 219 MediumPurple
+216 191 216 thistle
+255 250 250 snow1
+238 233 233 snow2
+205 201 201 snow3
+139 137 137 snow4
+255 245 238 seashell1
+238 229 222 seashell2
+205 197 191 seashell3
+139 134 130 seashell4
+255 239 219 AntiqueWhite1
+238 223 204 AntiqueWhite2
+205 192 176 AntiqueWhite3
+139 131 120 AntiqueWhite4
+255 228 196 bisque1
+238 213 183 bisque2
+205 183 158 bisque3
+139 125 107 bisque4
+255 218 185 PeachPuff1
+238 203 173 PeachPuff2
+205 175 149 PeachPuff3
+139 119 101 PeachPuff4
+255 222 173 NavajoWhite1
+238 207 161 NavajoWhite2
+205 179 139 NavajoWhite3
+139 121 94 NavajoWhite4
+255 250 205 LemonChiffon1
+238 233 191 LemonChiffon2
+205 201 165 LemonChiffon3
+139 137 112 LemonChiffon4
+255 248 220 cornsilk1
+238 232 205 cornsilk2
+205 200 177 cornsilk3
+139 136 120 cornsilk4
+255 255 240 ivory1
+238 238 224 ivory2
+205 205 193 ivory3
+139 139 131 ivory4
+240 255 240 honeydew1
+224 238 224 honeydew2
+193 205 193 honeydew3
+131 139 131 honeydew4
+255 240 245 LavenderBlush1
+238 224 229 LavenderBlush2
+205 193 197 LavenderBlush3
+139 131 134 LavenderBlush4
+255 228 225 MistyRose1
+238 213 210 MistyRose2
+205 183 181 MistyRose3
+139 125 123 MistyRose4
+240 255 255 azure1
+224 238 238 azure2
+193 205 205 azure3
+131 139 139 azure4
+131 111 255 SlateBlue1
+122 103 238 SlateBlue2
+105 89 205 SlateBlue3
+ 71 60 139 SlateBlue4
+ 72 118 255 RoyalBlue1
+ 67 110 238 RoyalBlue2
+ 58 95 205 RoyalBlue3
+ 39 64 139 RoyalBlue4
+ 0 0 255 blue1
+ 0 0 238 blue2
+ 0 0 205 blue3
+ 0 0 139 blue4
+ 30 144 255 DodgerBlue1
+ 28 134 238 DodgerBlue2
+ 24 116 205 DodgerBlue3
+ 16 78 139 DodgerBlue4
+ 99 184 255 SteelBlue1
+ 92 172 238 SteelBlue2
+ 79 148 205 SteelBlue3
+ 54 100 139 SteelBlue4
+ 0 191 255 DeepSkyBlue1
+ 0 178 238 DeepSkyBlue2
+ 0 154 205 DeepSkyBlue3
+ 0 104 139 DeepSkyBlue4
+135 206 255 SkyBlue1
+126 192 238 SkyBlue2
+108 166 205 SkyBlue3
+ 74 112 139 SkyBlue4
+176 226 255 LightSkyBlue1
+164 211 238 LightSkyBlue2
+141 182 205 LightSkyBlue3
+ 96 123 139 LightSkyBlue4
+198 226 255 SlateGray1
+185 211 238 SlateGray2
+159 182 205 SlateGray3
+108 123 139 SlateGray4
+202 225 255 LightSteelBlue1
+188 210 238 LightSteelBlue2
+162 181 205 LightSteelBlue3
+110 123 139 LightSteelBlue4
+191 239 255 LightBlue1
+178 223 238 LightBlue2
+154 192 205 LightBlue3
+104 131 139 LightBlue4
+224 255 255 LightCyan1
+209 238 238 LightCyan2
+180 205 205 LightCyan3
+122 139 139 LightCyan4
+187 255 255 PaleTurquoise1
+174 238 238 PaleTurquoise2
+150 205 205 PaleTurquoise3
+102 139 139 PaleTurquoise4
+152 245 255 CadetBlue1
+142 229 238 CadetBlue2
+122 197 205 CadetBlue3
+ 83 134 139 CadetBlue4
+ 0 245 255 turquoise1
+ 0 229 238 turquoise2
+ 0 197 205 turquoise3
+ 0 134 139 turquoise4
+ 0 255 255 cyan1
+ 0 238 238 cyan2
+ 0 205 205 cyan3
+ 0 139 139 cyan4
+151 255 255 DarkSlateGray1
+141 238 238 DarkSlateGray2
+121 205 205 DarkSlateGray3
+ 82 139 139 DarkSlateGray4
+127 255 212 aquamarine1
+118 238 198 aquamarine2
+102 205 170 aquamarine3
+ 69 139 116 aquamarine4
+193 255 193 DarkSeaGreen1
+180 238 180 DarkSeaGreen2
+155 205 155 DarkSeaGreen3
+105 139 105 DarkSeaGreen4
+ 84 255 159 SeaGreen1
+ 78 238 148 SeaGreen2
+ 67 205 128 SeaGreen3
+ 46 139 87 SeaGreen4
+154 255 154 PaleGreen1
+144 238 144 PaleGreen2
+124 205 124 PaleGreen3
+ 84 139 84 PaleGreen4
+ 0 255 127 SpringGreen1
+ 0 238 118 SpringGreen2
+ 0 205 102 SpringGreen3
+ 0 139 69 SpringGreen4
+ 0 255 0 green1
+ 0 238 0 green2
+ 0 205 0 green3
+ 0 139 0 green4
+127 255 0 chartreuse1
+118 238 0 chartreuse2
+102 205 0 chartreuse3
+ 69 139 0 chartreuse4
+192 255 62 OliveDrab1
+179 238 58 OliveDrab2
+154 205 50 OliveDrab3
+105 139 34 OliveDrab4
+202 255 112 DarkOliveGreen1
+188 238 104 DarkOliveGreen2
+162 205 90 DarkOliveGreen3
+110 139 61 DarkOliveGreen4
+255 246 143 khaki1
+238 230 133 khaki2
+205 198 115 khaki3
+139 134 78 khaki4
+255 236 139 LightGoldenrod1
+238 220 130 LightGoldenrod2
+205 190 112 LightGoldenrod3
+139 129 76 LightGoldenrod4
+255 255 224 LightYellow1
+238 238 209 LightYellow2
+205 205 180 LightYellow3
+139 139 122 LightYellow4
+255 255 0 yellow1
+238 238 0 yellow2
+205 205 0 yellow3
+139 139 0 yellow4
+255 215 0 gold1
+238 201 0 gold2
+205 173 0 gold3
+139 117 0 gold4
+255 193 37 goldenrod1
+238 180 34 goldenrod2
+205 155 29 goldenrod3
+139 105 20 goldenrod4
+255 185 15 DarkGoldenrod1
+238 173 14 DarkGoldenrod2
+205 149 12 DarkGoldenrod3
+139 101 8 DarkGoldenrod4
+255 193 193 RosyBrown1
+238 180 180 RosyBrown2
+205 155 155 RosyBrown3
+139 105 105 RosyBrown4
+255 106 106 IndianRed1
+238 99 99 IndianRed2
+205 85 85 IndianRed3
+139 58 58 IndianRed4
+255 130 71 sienna1
+238 121 66 sienna2
+205 104 57 sienna3
+139 71 38 sienna4
+255 211 155 burlywood1
+238 197 145 burlywood2
+205 170 125 burlywood3
+139 115 85 burlywood4
+255 231 186 wheat1
+238 216 174 wheat2
+205 186 150 wheat3
+139 126 102 wheat4
+255 165 79 tan1
+238 154 73 tan2
+205 133 63 tan3
+139 90 43 tan4
+255 127 36 chocolate1
+238 118 33 chocolate2
+205 102 29 chocolate3
+139 69 19 chocolate4
+255 48 48 firebrick1
+238 44 44 firebrick2
+205 38 38 firebrick3
+139 26 26 firebrick4
+255 64 64 brown1
+238 59 59 brown2
+205 51 51 brown3
+139 35 35 brown4
+255 140 105 salmon1
+238 130 98 salmon2
+205 112 84 salmon3
+139 76 57 salmon4
+255 160 122 LightSalmon1
+238 149 114 LightSalmon2
+205 129 98 LightSalmon3
+139 87 66 LightSalmon4
+255 165 0 orange1
+238 154 0 orange2
+205 133 0 orange3
+139 90 0 orange4
+255 127 0 DarkOrange1
+238 118 0 DarkOrange2
+205 102 0 DarkOrange3
+139 69 0 DarkOrange4
+255 114 86 coral1
+238 106 80 coral2
+205 91 69 coral3
+139 62 47 coral4
+255 99 71 tomato1
+238 92 66 tomato2
+205 79 57 tomato3
+139 54 38 tomato4
+255 69 0 OrangeRed1
+238 64 0 OrangeRed2
+205 55 0 OrangeRed3
+139 37 0 OrangeRed4
+255 0 0 red1
+238 0 0 red2
+205 0 0 red3
+139 0 0 red4
+255 20 147 DeepPink1
+238 18 137 DeepPink2
+205 16 118 DeepPink3
+139 10 80 DeepPink4
+255 110 180 HotPink1
+238 106 167 HotPink2
+205 96 144 HotPink3
+139 58 98 HotPink4
+255 181 197 pink1
+238 169 184 pink2
+205 145 158 pink3
+139 99 108 pink4
+255 174 185 LightPink1
+238 162 173 LightPink2
+205 140 149 LightPink3
+139 95 101 LightPink4
+255 130 171 PaleVioletRed1
+238 121 159 PaleVioletRed2
+205 104 137 PaleVioletRed3
+139 71 93 PaleVioletRed4
+255 52 179 maroon1
+238 48 167 maroon2
+205 41 144 maroon3
+139 28 98 maroon4
+255 62 150 VioletRed1
+238 58 140 VioletRed2
+205 50 120 VioletRed3
+139 34 82 VioletRed4
+255 0 255 magenta1
+238 0 238 magenta2
+205 0 205 magenta3
+139 0 139 magenta4
+255 131 250 orchid1
+238 122 233 orchid2
+205 105 201 orchid3
+139 71 137 orchid4
+255 187 255 plum1
+238 174 238 plum2
+205 150 205 plum3
+139 102 139 plum4
+224 102 255 MediumOrchid1
+209 95 238 MediumOrchid2
+180 82 205 MediumOrchid3
+122 55 139 MediumOrchid4
+191 62 255 DarkOrchid1
+178 58 238 DarkOrchid2
+154 50 205 DarkOrchid3
+104 34 139 DarkOrchid4
+155 48 255 purple1
+145 44 238 purple2
+125 38 205 purple3
+ 85 26 139 purple4
+171 130 255 MediumPurple1
+159 121 238 MediumPurple2
+137 104 205 MediumPurple3
+ 93 71 139 MediumPurple4
+255 225 255 thistle1
+238 210 238 thistle2
+205 181 205 thistle3
+139 123 139 thistle4
+ 0 0 0 gray0
+ 0 0 0 grey0
+ 3 3 3 gray1
+ 3 3 3 grey1
+ 5 5 5 gray2
+ 5 5 5 grey2
+ 8 8 8 gray3
+ 8 8 8 grey3
+ 10 10 10 gray4
+ 10 10 10 grey4
+ 13 13 13 gray5
+ 13 13 13 grey5
+ 15 15 15 gray6
+ 15 15 15 grey6
+ 18 18 18 gray7
+ 18 18 18 grey7
+ 20 20 20 gray8
+ 20 20 20 grey8
+ 23 23 23 gray9
+ 23 23 23 grey9
+ 26 26 26 gray10
+ 26 26 26 grey10
+ 28 28 28 gray11
+ 28 28 28 grey11
+ 31 31 31 gray12
+ 31 31 31 grey12
+ 33 33 33 gray13
+ 33 33 33 grey13
+ 36 36 36 gray14
+ 36 36 36 grey14
+ 38 38 38 gray15
+ 38 38 38 grey15
+ 41 41 41 gray16
+ 41 41 41 grey16
+ 43 43 43 gray17
+ 43 43 43 grey17
+ 46 46 46 gray18
+ 46 46 46 grey18
+ 48 48 48 gray19
+ 48 48 48 grey19
+ 51 51 51 gray20
+ 51 51 51 grey20
+ 54 54 54 gray21
+ 54 54 54 grey21
+ 56 56 56 gray22
+ 56 56 56 grey22
+ 59 59 59 gray23
+ 59 59 59 grey23
+ 61 61 61 gray24
+ 61 61 61 grey24
+ 64 64 64 gray25
+ 64 64 64 grey25
+ 66 66 66 gray26
+ 66 66 66 grey26
+ 69 69 69 gray27
+ 69 69 69 grey27
+ 71 71 71 gray28
+ 71 71 71 grey28
+ 74 74 74 gray29
+ 74 74 74 grey29
+ 77 77 77 gray30
+ 77 77 77 grey30
+ 79 79 79 gray31
+ 79 79 79 grey31
+ 82 82 82 gray32
+ 82 82 82 grey32
+ 84 84 84 gray33
+ 84 84 84 grey33
+ 87 87 87 gray34
+ 87 87 87 grey34
+ 89 89 89 gray35
+ 89 89 89 grey35
+ 92 92 92 gray36
+ 92 92 92 grey36
+ 94 94 94 gray37
+ 94 94 94 grey37
+ 97 97 97 gray38
+ 97 97 97 grey38
+ 99 99 99 gray39
+ 99 99 99 grey39
+102 102 102 gray40
+102 102 102 grey40
+105 105 105 gray41
+105 105 105 grey41
+107 107 107 gray42
+107 107 107 grey42
+110 110 110 gray43
+110 110 110 grey43
+112 112 112 gray44
+112 112 112 grey44
+115 115 115 gray45
+115 115 115 grey45
+117 117 117 gray46
+117 117 117 grey46
+120 120 120 gray47
+120 120 120 grey47
+122 122 122 gray48
+122 122 122 grey48
+125 125 125 gray49
+125 125 125 grey49
+127 127 127 gray50
+127 127 127 grey50
+130 130 130 gray51
+130 130 130 grey51
+133 133 133 gray52
+133 133 133 grey52
+135 135 135 gray53
+135 135 135 grey53
+138 138 138 gray54
+138 138 138 grey54
+140 140 140 gray55
+140 140 140 grey55
+143 143 143 gray56
+143 143 143 grey56
+145 145 145 gray57
+145 145 145 grey57
+148 148 148 gray58
+148 148 148 grey58
+150 150 150 gray59
+150 150 150 grey59
+153 153 153 gray60
+153 153 153 grey60
+156 156 156 gray61
+156 156 156 grey61
+158 158 158 gray62
+158 158 158 grey62
+161 161 161 gray63
+161 161 161 grey63
+163 163 163 gray64
+163 163 163 grey64
+166 166 166 gray65
+166 166 166 grey65
+168 168 168 gray66
+168 168 168 grey66
+171 171 171 gray67
+171 171 171 grey67
+173 173 173 gray68
+173 173 173 grey68
+176 176 176 gray69
+176 176 176 grey69
+179 179 179 gray70
+179 179 179 grey70
+181 181 181 gray71
+181 181 181 grey71
+184 184 184 gray72
+184 184 184 grey72
+186 186 186 gray73
+186 186 186 grey73
+189 189 189 gray74
+189 189 189 grey74
+191 191 191 gray75
+191 191 191 grey75
+194 194 194 gray76
+194 194 194 grey76
+196 196 196 gray77
+196 196 196 grey77
+199 199 199 gray78
+199 199 199 grey78
+201 201 201 gray79
+201 201 201 grey79
+204 204 204 gray80
+204 204 204 grey80
+207 207 207 gray81
+207 207 207 grey81
+209 209 209 gray82
+209 209 209 grey82
+212 212 212 gray83
+212 212 212 grey83
+214 214 214 gray84
+214 214 214 grey84
+217 217 217 gray85
+217 217 217 grey85
+219 219 219 gray86
+219 219 219 grey86
+222 222 222 gray87
+222 222 222 grey87
+224 224 224 gray88
+224 224 224 grey88
+227 227 227 gray89
+227 227 227 grey89
+229 229 229 gray90
+229 229 229 grey90
+232 232 232 gray91
+232 232 232 grey91
+235 235 235 gray92
+235 235 235 grey92
+237 237 237 gray93
+237 237 237 grey93
+240 240 240 gray94
+240 240 240 grey94
+242 242 242 gray95
+242 242 242 grey95
+245 245 245 gray96
+245 245 245 grey96
+247 247 247 gray97
+247 247 247 grey97
+250 250 250 gray98
+250 250 250 grey98
+252 252 252 gray99
+252 252 252 grey99
+255 255 255 gray100
+255 255 255 grey100
+169 169 169 dark grey
+169 169 169 DarkGrey
+169 169 169 dark gray
+169 169 169 DarkGray
+0 0 139 dark blue
+0 0 139 DarkBlue
+0 139 139 dark cyan
+0 139 139 DarkCyan
+139 0 139 dark magenta
+139 0 139 DarkMagenta
+139 0 0 dark red
+139 0 0 DarkRed
+144 238 144 light green
+144 238 144 LightGreen
diff --git a/sys/src/cmd/python/Tools/pynche/X/xlicense.txt b/sys/src/cmd/python/Tools/pynche/X/xlicense.txt
new file mode 100644
index 000000000..b4471db67
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/X/xlicense.txt
@@ -0,0 +1,29 @@
+X Window System License - X11R6.4
+
+Copyright (c) 1998 The Open Group
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name of The Open Group shall
+not be used in advertising or otherwise to promote the sale, use or
+other dealings in this Software without prior written authorization
+from The Open Group.
+
+X Window System is a trademark of The Open Group
diff --git a/sys/src/cmd/python/Tools/pynche/__init__.py b/sys/src/cmd/python/Tools/pynche/__init__.py
new file mode 100644
index 000000000..b93054b3e
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/__init__.py
@@ -0,0 +1 @@
+# Dummy file to make this directory a package.
diff --git a/sys/src/cmd/python/Tools/pynche/html40colors.txt b/sys/src/cmd/python/Tools/pynche/html40colors.txt
new file mode 100644
index 000000000..3eeb0a10a
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/html40colors.txt
@@ -0,0 +1,17 @@
+# HTML 4.0 color names
+Black #000000
+Silver #c0c0c0
+Gray #808080
+White #ffffff
+Maroon #800000
+Red #ff0000
+Purple #800080
+Fuchsia #ff00ff
+Green #008000
+Lime #00ff00
+Olive #808000
+Yellow #ffff00
+Navy #000080
+Blue #0000ff
+Teal #008080
+Aqua #00ffff
diff --git a/sys/src/cmd/python/Tools/pynche/namedcolors.txt b/sys/src/cmd/python/Tools/pynche/namedcolors.txt
new file mode 100644
index 000000000..3658e85f2
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/namedcolors.txt
@@ -0,0 +1,100 @@
+# named colors from http://www.lightlink.com/xine/bells/namedcolors.html
+White #FFFFFF
+Red #FF0000
+Green #00FF00
+Blue #0000FF
+Magenta #FF00FF
+Cyan #00FFFF
+Yellow #FFFF00
+Black #000000
+Aquamarine #70DB93
+Baker's Chocolate #5C3317
+Blue Violet #9F5F9F
+Brass #B5A642
+Bright Gold #D9D919
+Brown #A62A2A
+Bronze #8C7853
+Bronze II #A67D3D
+Cadet Blue #5F9F9F
+Cool Copper #D98719
+Copper #B87333
+Coral #FF7F00
+Corn Flower Blue #42426F
+Dark Brown #5C4033
+Dark Green #2F4F2F
+Dark Green Copper #4A766E
+Dark Olive Green #4F4F2F
+Dark Orchid #9932CD
+Dark Purple #871F78
+Dark Slate Blue #6B238E
+Dark Slate Grey #2F4F4F
+Dark Tan #97694F
+Dark Turquoise #7093DB
+Dark Wood #855E42
+Dim Grey #545454
+Dusty Rose #856363
+Feldspar #D19275
+Firebrick #8E2323
+Forest Green #238E23
+Gold #CD7F32
+Goldenrod #DBDB70
+Grey #C0C0C0
+Green Copper #527F76
+Green Yellow #93DB70
+Hunter Green #215E21
+Indian Red #4E2F2F
+Khaki #9F9F5F
+Light Blue #C0D9D9
+Light Grey #A8A8A8
+Light Steel Blue #8F8FBD
+Light Wood #E9C2A6
+Lime Green #32CD32
+Mandarian Orange #E47833
+Maroon #8E236B
+Medium Aquamarine #32CD99
+Medium Blue #3232CD
+Medium Forest Green #6B8E23
+Medium Goldenrod #EAEAAE
+Medium Orchid #9370DB
+Medium Sea Green #426F42
+Medium Slate Blue #7F00FF
+Medium Spring Green #7FFF00
+Medium Turquoise #70DBDB
+Medium Violet Red #DB7093
+Medium Wood #A68064
+Midnight Blue #2F2F4F
+Navy Blue #23238E
+Neon Blue #4D4DFF
+Neon Pink #FF6EC7
+New Midnight Blue #00009C
+New Tan #EBC79E
+Old Gold #CFB53B
+Orange #FF7F00
+Orange Red #FF2400
+Orchid #DB70DB
+Pale Green #8FBC8F
+Pink #BC8F8F
+Plum #EAADEA
+Quartz #D9D9F3
+Rich Blue #5959AB
+Salmon #6F4242
+Scarlet #8C1717
+Sea Green #238E68
+Semi-Sweet Chocolate #6B4226
+Sienna #8E6B23
+Silver #E6E8FA
+Sky Blue #3299CC
+Slate Blue #007FFF
+Spicy Pink #FF1CAE
+Spring Green #00FF7F
+Steel Blue #236B8E
+Summer Sky #38B0DE
+Tan #DB9370
+Thistle #D8BFD8
+Turquoise #ADEAEA
+Very Dark Brown #5C4033
+Very Light Grey #CDCDCD
+Violet #4F2F4F
+Violet Red #CC3299
+Wheat #D8D8BF
+Yellow Green #99CC32
diff --git a/sys/src/cmd/python/Tools/pynche/pyColorChooser.py b/sys/src/cmd/python/Tools/pynche/pyColorChooser.py
new file mode 100644
index 000000000..56f694062
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/pyColorChooser.py
@@ -0,0 +1,125 @@
+"""Color chooser implementing (almost) the tkColorColor interface
+"""
+
+import os
+import Main
+import ColorDB
+
+
+
+class Chooser:
+ """Ask for a color"""
+ def __init__(self,
+ master = None,
+ databasefile = None,
+ initfile = None,
+ ignore = None,
+ wantspec = None):
+ self.__master = master
+ self.__databasefile = databasefile
+ self.__initfile = initfile or os.path.expanduser('~/.pynche')
+ self.__ignore = ignore
+ self.__pw = None
+ self.__wantspec = wantspec
+
+ def show(self, color, options):
+ # scan for options that can override the ctor options
+ self.__wantspec = options.get('wantspec', self.__wantspec)
+ dbfile = options.get('databasefile', self.__databasefile)
+ # load the database file
+ colordb = None
+ if dbfile <> self.__databasefile:
+ colordb = ColorDB.get_colordb(dbfile)
+ if not self.__master:
+ from Tkinter import Tk
+ self.__master = Tk()
+ if not self.__pw:
+ self.__pw, self.__sb = \
+ Main.build(master = self.__master,
+ initfile = self.__initfile,
+ ignore = self.__ignore)
+ else:
+ self.__pw.deiconify()
+ # convert color
+ if colordb:
+ self.__sb.set_colordb(colordb)
+ else:
+ colordb = self.__sb.colordb()
+ if color:
+ r, g, b = Main.initial_color(color, colordb)
+ self.__sb.update_views(r, g, b)
+ # reset the canceled flag and run it
+ self.__sb.canceled(0)
+ Main.run(self.__pw, self.__sb)
+ rgbtuple = self.__sb.current_rgb()
+ self.__pw.withdraw()
+ # check to see if the cancel button was pushed
+ if self.__sb.canceled_p():
+ return None, None
+ # Try to return the color name from the database if there is an exact
+ # match, otherwise use the "#rrggbb" spec. BAW: Forget about color
+ # aliases for now, maybe later we should return these too.
+ name = None
+ if not self.__wantspec:
+ try:
+ name = colordb.find_byrgb(rgbtuple)[0]
+ except ColorDB.BadColor:
+ pass
+ if name is None:
+ name = ColorDB.triplet_to_rrggbb(rgbtuple)
+ return rgbtuple, name
+
+ def save(self):
+ if self.__sb:
+ self.__sb.save_views()
+
+
+# convenience stuff
+_chooser = None
+
+def askcolor(color = None, **options):
+ """Ask for a color"""
+ global _chooser
+ if not _chooser:
+ _chooser = apply(Chooser, (), options)
+ return _chooser.show(color, options)
+
+def save():
+ global _chooser
+ if _chooser:
+ _chooser.save()
+
+
+# test stuff
+if __name__ == '__main__':
+ from Tkinter import *
+
+ class Tester:
+ def __init__(self):
+ self.__root = tk = Tk()
+ b = Button(tk, text='Choose Color...', command=self.__choose)
+ b.pack()
+ self.__l = Label(tk)
+ self.__l.pack()
+ q = Button(tk, text='Quit', command=self.__quit)
+ q.pack()
+
+ def __choose(self, event=None):
+ rgb, name = askcolor(master=self.__root)
+ if rgb is None:
+ text = 'You hit CANCEL!'
+ else:
+ r, g, b = rgb
+ text = 'You picked %s (%3d/%3d/%3d)' % (name, r, g, b)
+ self.__l.configure(text=text)
+
+ def __quit(self, event=None):
+ self.__root.quit()
+
+ def run(self):
+ self.__root.mainloop()
+ t = Tester()
+ t.run()
+ # simpler
+## print 'color:', askcolor()
+## print 'color:', askcolor()
diff --git a/sys/src/cmd/python/Tools/pynche/pynche b/sys/src/cmd/python/Tools/pynche/pynche
new file mode 100755
index 000000000..64bf70335
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/pynche
@@ -0,0 +1,7 @@
+#! /usr/bin/env python
+
+"""Run this file under Unix, or when debugging under Windows.
+Run the file pynche.pyw under Windows to inhibit the console window.
+"""
+import Main
+Main.main()
diff --git a/sys/src/cmd/python/Tools/pynche/pynche.pyw b/sys/src/cmd/python/Tools/pynche/pynche.pyw
new file mode 100755
index 000000000..6dfc8fed7
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/pynche.pyw
@@ -0,0 +1,7 @@
+#! /usr/bin/env python
+
+"""Run this file under Windows to inhibit the console window.
+Run the file pynche.py under Unix or when debugging under Windows.
+"""
+import Main
+Main.main()
diff --git a/sys/src/cmd/python/Tools/pynche/webcolors.txt b/sys/src/cmd/python/Tools/pynche/webcolors.txt
new file mode 100644
index 000000000..f645c1e61
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/webcolors.txt
@@ -0,0 +1,141 @@
+# De-facto NS & MSIE recognized HTML color names
+AliceBlue #f0f8ff
+AntiqueWhite #faebd7
+Aqua #00ffff
+Aquamarine #7fffd4
+Azure #f0ffff
+Beige #f5f5dc
+Bisque #ffe4c4
+Black #000000
+BlanchedAlmond #ffebcd
+Blue #0000ff
+BlueViolet #8a2be2
+Brown #a52a2a
+BurlyWood #deb887
+CadetBlue #5f9ea0
+Chartreuse #7fff00
+Chocolate #d2691e
+Coral #ff7f50
+CornflowerBlue #6495ed
+Cornsilk #fff8dc
+Crimson #dc143c
+Cyan #00ffff
+DarkBlue #00008b
+DarkCyan #008b8b
+DarkGoldenrod #b8860b
+DarkGray #a9a9a9
+DarkGreen #006400
+DarkKhaki #bdb76b
+DarkMagenta #8b008b
+DarkOliveGreen #556b2f
+DarkOrange #ff8c00
+DarkOrchid #9932cc
+DarkRed #8b0000
+DarkSalmon #e9967a
+DarkSeaGreen #8fbc8f
+DarkSlateBlue #483d8b
+DarkSlateGray #2f4f4f
+DarkTurquoise #00ced1
+DarkViolet #9400d3
+DeepPink #ff1493
+DeepSkyBlue #00bfff
+DimGray #696969
+DodgerBlue #1e90ff
+FireBrick #b22222
+FloralWhite #fffaf0
+ForestGreen #228b22
+Fuchsia #ff00ff
+Gainsboro #dcdcdc
+GhostWhite #f8f8ff
+Gold #ffd700
+Goldenrod #daa520
+Gray #808080
+Green #008000
+GreenYellow #adff2f
+Honeydew #f0fff0
+HotPink #ff69b4
+IndianRed #cd5c5c
+Indigo #4b0082
+Ivory #fffff0
+Khaki #f0e68c
+Lavender #e6e6fa
+LavenderBlush #fff0f5
+LawnGreen #7cfc00
+LemonChiffon #fffacd
+LightBlue #add8e6
+LightCoral #f08080
+LightCyan #e0ffff
+LightGoldenrodYellow #fafad2
+LightGreen #90ee90
+LightGrey #d3d3d3
+LightPink #ffb6c1
+LightSalmon #ffa07a
+LightSeaGreen #20b2aa
+LightSkyBlue #87cefa
+LightSlateGray #778899
+LightSteelBlue #b0c4de
+LightYellow #ffffe0
+Lime #00ff00
+LimeGreen #32cd32
+Linen #faf0e6
+Magenta #ff00ff
+Maroon #800000
+MediumAquamarine #66cdaa
+MediumBlue #0000cd
+MediumOrchid #ba55d3
+MediumPurple #9370db
+MediumSeaGreen #3cb371
+MediumSlateBlue #7b68ee
+MediumSpringGreen #00fa9a
+MediumTurquoise #48d1cc
+MediumVioletRed #c71585
+MidnightBlue #191970
+MintCream #f5fffa
+MistyRose #ffe4e1
+Moccasin #ffe4b5
+NavajoWhite #ffdead
+Navy #000080
+OldLace #fdf5e6
+Olive #808000
+OliveDrab #6b8e23
+Orange #ffa500
+OrangeRed #ff4500
+Orchid #da70d6
+PaleGoldenrod #eee8aa
+PaleGreen #98fb98
+PaleTurquoise #afeeee
+PaleVioletRed #db7093
+PapayaWhip #ffefd5
+PeachPuff #ffdab9
+Peru #cd853f
+Pink #ffc0cb
+Plum #dda0dd
+PowderBlue #b0e0e6
+Purple #800080
+Red #ff0000
+RosyBrown #bc8f8f
+RoyalBlue #4169e1
+SaddleBrown #8b4513
+Salmon #fa8072
+SandyBrown #f4a460
+SeaGreen #2e8b57
+Seashell #fff5ee
+Sienna #a0522d
+Silver #c0c0c0
+SkyBlue #87ceeb
+SlateBlue #6a5acd
+SlateGray #708090
+Snow #fffafa
+SpringGreen #00ff7f
+SteelBlue #4682b4
+Tan #d2b48c
+Teal #008080
+Thistle #d8bfd8
+Tomato #ff6347
+Turquoise #40e0d0
+Violet #ee82ee
+Wheat #f5deb3
+White #ffffff
+WhiteSmoke #f5f5f5
+Yellow #ffff00
+YellowGreen #9acd32
diff --git a/sys/src/cmd/python/Tools/pynche/websafe.txt b/sys/src/cmd/python/Tools/pynche/websafe.txt
new file mode 100644
index 000000000..70ed51e68
--- /dev/null
+++ b/sys/src/cmd/python/Tools/pynche/websafe.txt
@@ -0,0 +1,217 @@
+# Websafe RGB values
+#000000
+#000033
+#000066
+#000099
+#0000cc
+#0000ff
+#003300
+#003333
+#003366
+#003399
+#0033cc
+#0033ff
+#006600
+#006633
+#006666
+#006699
+#0066cc
+#0066ff
+#009900
+#009933
+#009966
+#009999
+#0099cc
+#0099ff
+#00cc00
+#00cc33
+#00cc66
+#00cc99
+#00cccc
+#00ccff
+#00ff00
+#00ff33
+#00ff66
+#00ff99
+#00ffcc
+#00ffff
+#330000
+#330033
+#330066
+#330099
+#3300cc
+#3300ff
+#333300
+#333333
+#333366
+#333399
+#3333cc
+#3333ff
+#336600
+#336633
+#336666
+#336699
+#3366cc
+#3366ff
+#339900
+#339933
+#339966
+#339999
+#3399cc
+#3399ff
+#33cc00
+#33cc33
+#33cc66
+#33cc99
+#33cccc
+#33ccff
+#33ff00
+#33ff33
+#33ff66
+#33ff99
+#33ffcc
+#33ffff
+#660000
+#660033
+#660066
+#660099
+#6600cc
+#6600ff
+#663300
+#663333
+#663366
+#663399
+#6633cc
+#6633ff
+#666600
+#666633
+#666666
+#666699
+#6666cc
+#6666ff
+#669900
+#669933
+#669966
+#669999
+#6699cc
+#6699ff
+#66cc00
+#66cc33
+#66cc66
+#66cc99
+#66cccc
+#66ccff
+#66ff00
+#66ff33
+#66ff66
+#66ff99
+#66ffcc
+#66ffff
+#990000
+#990033
+#990066
+#990099
+#9900cc
+#9900ff
+#993300
+#993333
+#993366
+#993399
+#9933cc
+#9933ff
+#996600
+#996633
+#996666
+#996699
+#9966cc
+#9966ff
+#999900
+#999933
+#999966
+#999999
+#9999cc
+#9999ff
+#99cc00
+#99cc33
+#99cc66
+#99cc99
+#99cccc
+#99ccff
+#99ff00
+#99ff33
+#99ff66
+#99ff99
+#99ffcc
+#99ffff
+#cc0000
+#cc0033
+#cc0066
+#cc0099
+#cc00cc
+#cc00ff
+#cc3300
+#cc3333
+#cc3366
+#cc3399
+#cc33cc
+#cc33ff
+#cc6600
+#cc6633
+#cc6666
+#cc6699
+#cc66cc
+#cc66ff
+#cc9900
+#cc9933
+#cc9966
+#cc9999
+#cc99cc
+#cc99ff
+#cccc00
+#cccc33
+#cccc66
+#cccc99
+#cccccc
+#ccccff
+#ccff00
+#ccff33
+#ccff66
+#ccff99
+#ccffcc
+#ccffff
+#ff0000
+#ff0033
+#ff0066
+#ff0099
+#ff00cc
+#ff00ff
+#ff3300
+#ff3333
+#ff3366
+#ff3399
+#ff33cc
+#ff33ff
+#ff6600
+#ff6633
+#ff6666
+#ff6699
+#ff66cc
+#ff66ff
+#ff9900
+#ff9933
+#ff9966
+#ff9999
+#ff99cc
+#ff99ff
+#ffcc00
+#ffcc33
+#ffcc66
+#ffcc99
+#ffcccc
+#ffccff
+#ffff00
+#ffff33
+#ffff66
+#ffff99
+#ffffcc
+#ffffff