From 0604d723183bee9ca18920708ec7fd44fbc4c63b Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Mon, 26 Apr 1999 23:17:16 +0000 Subject: 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 | 44 +++++++++++-------- Tools/pynche/Main.py | 34 +++++++++------ Tools/pynche/PyncheWidget.py | 35 ++++++++++++++- Tools/pynche/Switchboard.py | 10 ++++- 6 files changed, 180 insertions(+), 78 deletions(-) diff --git a/Tools/pynche/ChipViewer.py b/Tools/pynche/ChipViewer.py index 3556f6d..33d12dc 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 863688a..87c4406 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 eb43a92..424e462 100644 --- a/Tools/pynche/ListViewer.py +++ b/Tools/pynche/ListViewer.py @@ -45,9 +45,29 @@ class ListViewer: 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 = switchboard.colordb() + colordb = self.__sb.colordb() + canvas = self.__canvas row = 0 widest = 0 bboxes = self.__bboxes = [] @@ -63,7 +83,7 @@ class ListViewer: boxid = canvas.create_rectangle(3, row*20+3, textend+3, row*20 + 23, outline='', - tags=(exactcolor,)) + tags=(exactcolor, 'all')) canvas.bind('', self.__onrelease) bboxes.append(boxid) if textend+3 > widest: @@ -74,22 +94,6 @@ class ListViewer: for box in bboxes: x1, y1, x2, y2 = canvas.coords(box) canvas.coords(box, x1, y1, widest, y2) - # - # 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 __onrelease(self, event=None): canvas = self.__canvas @@ -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 1ec738b..459aaa5 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 5e691ec..810b7ab 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 3b06f11..9f2c64c 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: -- cgit v0.12