From 0604d723183bee9ca18920708ec7fd44fbc4c63b Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Mon, 26 Apr 1999 23:17:16 +0000 Subject: [PATCH] Lots of changes to support loading alternative color name database. You can switch database by just loading the new one; the list window and nearest colors adapt to the new database. Some reorganizing of code. Also, the name of the database file is stored in the ~/.pynche pickle. If it can't be loaded, fallbacks are used. --- Tools/pynche/ChipViewer.py | 34 ++++++------ Tools/pynche/ColorDB.py | 101 +++++++++++++++++++++++++---------- Tools/pynche/ListViewer.py | 66 +++++++++++++---------- Tools/pynche/Main.py | 34 +++++++----- Tools/pynche/PyncheWidget.py | 35 +++++++++++- Tools/pynche/Switchboard.py | 10 +++- 6 files changed, 191 insertions(+), 89 deletions(-) diff --git a/Tools/pynche/ChipViewer.py b/Tools/pynche/ChipViewer.py index 3556f6d8e44..33d12dcd4ca 100644 --- a/Tools/pynche/ChipViewer.py +++ b/Tools/pynche/ChipViewer.py @@ -49,9 +49,9 @@ class ChipWidget: if releasecmd: self.__chip.bind('', releasecmd) - def set_color(self, color): + def set_color(self, color, colorname=None): self.__chip.config(background=color) - self.__name.config(text=color) + self.__name.config(text=colorname or color) def get_color(self): return self.__chip['background'] @@ -83,25 +83,27 @@ class ChipViewer: releasecmd = self.__buttonrelease) def update_yourself(self, red, green, blue): - # TBD: should exactname default to X11 color name if their is an exact - # match for the rgb triplet? Part of me says it's nice to see both - # names for the color, the other part says that it's better to - # feedback the exact match. + # Selected always shows the #rrggbb name of the color, nearest always + # shows the name of the nearest color in the database. TBD: 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) - try: - allcolors = self.__sb.colordb().find_byrgb(rgbtuple) - exactname = allcolors[0] - except ColorDB.BadColor: - exactname = ColorDB.triplet_to_rrggbb(rgbtuple) - nearest = self.__sb.colordb().nearest(red, green, blue) - self.__selected.set_color(exactname) - self.__nearest.set_color(nearest) + 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, nearest) def __buttonpress(self, event=None): self.__nearest.press() def __buttonrelease(self, event=None): self.__nearest.release() - colorname = self.__nearest.get_color() - red, green, blue = self.__sb.colordb().find_byname(colorname) + rrggbb = self.__nearest.get_color() + red, green, blue = ColorDB.rrggbb_to_triplet(rrggbb) self.__sb.update_views(red, green, blue) diff --git a/Tools/pynche/ColorDB.py b/Tools/pynche/ColorDB.py index 863688a5c73..87c44060860 100644 --- a/Tools/pynche/ColorDB.py +++ b/Tools/pynche/ColorDB.py @@ -35,7 +35,9 @@ DEFAULT_DB = None # generic class class ColorDB: - def __init__(self, fp, lineno): + 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, @@ -54,6 +56,7 @@ class ColorDB: if not line: break # get this compiled regular expression from derived class +## print '%3d: %s' % (lineno, line[:-1]) mo = self._re.match(line) if not mo: sys.stderr.write('Error in %s, line %d\n' % (fp.name, lineno)) @@ -62,9 +65,10 @@ class ColorDB: # # extract the red, green, blue, and name # - red, green, blue = map(int, mo.group('red', 'green', 'blue')) - name = mo.group('name') + red, green, blue = self._extractrgb(mo) + name = self._extractname(mo) keyname = string.lower(name) +## print keyname, '(%d, %d, %d)' % (red, green, blue) # # TBD: 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 @@ -81,13 +85,25 @@ class ColorDB: self.__byname[keyname] = key lineno = lineno + 1 + # override in derived classes + def _extractrgb(self, mo): + return map(int, 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 = string.lower(name) try: return self.__byname[name] @@ -95,9 +111,10 @@ class ColorDB: raise BadColor(name) def nearest(self, red, green, blue): - # TBD: use Voronoi diagrams, Delaunay triangulation, or octree for - # speeding up the locating of nearest point. Exhaustive search is - # inefficient, but may be fast enough. + """Return the name of color nearest (red, green, blue)""" + # TBD: 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(): @@ -133,7 +150,29 @@ class ColorDB: class RGBColorDB(ColorDB): _re = re.compile( - '\s*(?P\d+)\s+(?P\d+)\s+(?P\d+)\s+(?P.*)') + '\s*(?P\d+)\s+(?P\d+)\s+(?P\d+)\s+(?P.*)') + + +class HTML40DB(ColorDB): + _re = re.compile('(?P\S+)\s+(?P#[0-9a-fA-F]{6})') + + def _extractrgb(self, mo): + return rrggbb_to_triplet(mo.group('hexrgb')) + +class LightlinkDB(HTML40DB): + _re = re.compile('(?P(.+))\s+(?P#[0-9a-fA-F]{6})') + + def _extractname(self, mo): + return string.strip(mo.group('name')) + +class WebsafeDB(ColorDB): + _re = re.compile('(?P#[0-9a-fA-F]{6})') + + def _extractrgb(self, mo): + return rrggbb_to_triplet(mo.group('hexrgb')) + + def _extractname(self, mo): + return string.upper(mo.group('hexrgb')) @@ -141,30 +180,36 @@ class RGBColorDB(ColorDB): # expression, SCANLINES is the number of header lines to scan, and CLASS is # the class to instantiate if a match is found -X_RGB_TXT = re.compile('XConsortium'), 1, RGBColorDB +FILETYPES = [ + (re.compile('XConsortium'), RGBColorDB), + (re.compile('HTML'), HTML40DB), + (re.compile('lightlink'), LightlinkDB), + (re.compile('Websafe'), WebsafeDB), + ] -def get_colordb(file, filetype=X_RGB_TXT): +def get_colordb(file, filetype=None): colordb = None - fp = None - typere, scanlines, class_ = filetype + fp = open(file) try: - try: - lineno = 0 - fp = open(file) - while lineno < scanlines: - line = fp.readline() - if not line: - break - mo = typere.search(line) - if mo: - colordb = class_(fp, lineno) - break - lineno = lineno + 1 - except IOError: - pass + 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: - if fp: - fp.close() + fp.close() # save a global copy global DEFAULT_DB DEFAULT_DB = colordb @@ -175,6 +220,7 @@ def get_colordb(file, filetype=X_RGB_TXT): _namedict = {} def rrggbb_to_triplet(color, atoi=string.atoi): """Converts a #rrggbb color to the tuple (red, green, blue).""" + global _namedict rgbtuple = _namedict.get(color) if rgbtuple is None: if color[0] <> '#': @@ -190,6 +236,7 @@ def rrggbb_to_triplet(color, atoi=string.atoi): _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 diff --git a/Tools/pynche/ListViewer.py b/Tools/pynche/ListViewer.py index eb43a928607..424e462ce38 100644 --- a/Tools/pynche/ListViewer.py +++ b/Tools/pynche/ListViewer.py @@ -45,35 +45,7 @@ class ListViewer: canvas.pack(fill=BOTH, expand=1) canvas.configure(yscrollcommand=(self.__scrollbar, 'set')) self.__scrollbar.configure(command=(canvas, 'yview')) - # - # create all the buttons - colordb = switchboard.colordb() - 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,)) - canvas.bind('', self.__onrelease) - bboxes.append(boxid) - if textend+3 > widest: - widest = textend+3 - row = 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) + self.__populate() # # Update on click self.__uoc = BooleanVar() @@ -91,6 +63,38 @@ class ListViewer: 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('', self.__onrelease) + bboxes.append(boxid) + if textend+3 > widest: + widest = textend+3 + row = 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 @@ -164,3 +168,7 @@ class ListViewer: def save_options(self, optiondb): optiondb['UPONCLICK'] = self.__uoc.get() + + def flush(self): + self.__canvas.delete('all') + self.__populate() diff --git a/Tools/pynche/Main.py b/Tools/pynche/Main.py index 1ec738b111f..459aaa570bc 100644 --- a/Tools/pynche/Main.py +++ b/Tools/pynche/Main.py @@ -49,7 +49,7 @@ Where: """ -__version__ = '0.1' +__version__ = '0.2' import sys import os @@ -120,19 +120,27 @@ def initial_color(s, colordb): def build(master=None, initialcolor=None, initfile=None, ignore=None): - # create the windows and go - for f in RGB_TXT: - try: - colordb = ColorDB.get_colordb(f) - if colordb: - break - except IOError: - pass - else: - usage(1, 'No color database file found, see the -d option.') - # create all output widgets - s = Switchboard(colordb, not ignore and initfile) + s = Switchboard(not ignore and initfile) + + # load the color database + colordb = None + try: + dbfile = s.optiondb()['DBFILE'] + colordb = ColorDB.get_colordb(dbfile) + except (KeyError, IOError): + # scoot through the files listed above to try to find a usable color + # database file + for f in RGB_TXT: + try: + colordb = ColorDB.get_colordb(f) + if colordb: + break + except IOError: + pass + 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) diff --git a/Tools/pynche/PyncheWidget.py b/Tools/pynche/PyncheWidget.py index 5e691ece29a..810b7ab6651 100644 --- a/Tools/pynche/PyncheWidget.py +++ b/Tools/pynche/PyncheWidget.py @@ -23,6 +23,7 @@ class PyncheWidget: 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 @@ -51,8 +52,11 @@ class PyncheWidget: # # File menu # + filemenu = self.__filemenu = Menu(menubar, tearoff=0) + filemenu.add_command(label='Load palette...', + command=self.__load, + underline=0) if not modal: - filemenu = self.__filemenu = Menu(menubar, tearoff=0) filemenu.add_command(label='Quit', command=self.__quit, accelerator='Alt-Q', @@ -66,7 +70,7 @@ class PyncheWidget: underline=0) viewmenu.add_command(label='Color List Window...', command=self.__popup_listwin, - underline=0) + underline=6) viewmenu.add_command(label='Details Window...', command=self.__popup_details, underline=0) @@ -186,6 +190,33 @@ email: bwarsaw@python.org''' % __version__) self.__sb.add_view(self.__detailswin) self.__detailswin.deiconify() + def __load(self, event=None): + import FileDialog + import ColorDB + while 1: + d = FileDialog.FileDialog(self.__root) + file = d.go(pattern='*.txt', key=self.__dialogstate) + if file is None: + # 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) + if self.__listwin: + self.__listwin.flush() + self.__sb.update_views_current() + def withdraw(self): self.__root.withdraw() diff --git a/Tools/pynche/Switchboard.py b/Tools/pynche/Switchboard.py index 3b06f1118de..9f2c64c2706 100644 --- a/Tools/pynche/Switchboard.py +++ b/Tools/pynche/Switchboard.py @@ -17,9 +17,9 @@ from types import DictType import marshal class Switchboard: - def __init__(self, colordb, initfile): + def __init__(self, initfile): self.__initfile = initfile - self.__colordb = colordb + self.__colordb = None self.__optiondb = {} self.__views = [] self.__red = 0 @@ -63,6 +63,9 @@ class Switchboard: def colordb(self): return self.__colordb + def set_colordb(self, colordb): + self.__colordb = colordb + def optiondb(self): return self.__optiondb @@ -74,6 +77,9 @@ class Switchboard: 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: