diff options
author | cinap_lenrek <cinap_lenrek@localhost> | 2011-05-03 11:25:13 +0000 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@localhost> | 2011-05-03 11:25:13 +0000 |
commit | 458120dd40db6b4df55a4e96b650e16798ef06a0 (patch) | |
tree | 8f82685be24fef97e715c6f5ca4c68d34d5074ee /sys/src/cmd/python/Tools/pynche | |
parent | 3a742c699f6806c1145aea5149bf15de15a0afd7 (diff) |
add hg and python
Diffstat (limited to 'sys/src/cmd/python/Tools/pynche')
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 |