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.
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 @@
         if releasecmd:
             self.__chip.bind('<ButtonRelease-1>', 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 @@
                                     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 @@
 
 # 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 @@
 	    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 @@
 	    #
 	    # 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 @@
 	    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 @@
 	    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 RGBColorDB(ColorDB):
     _re = re.compile(
-	'\s*(?P<red>\d+)\s+(?P<green>\d+)\s+(?P<blue>\d+)\s+(?P<name>.*)')
+        '\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 string.strip(mo.group('name'))
+
+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 string.upper(mo.group('hexrgb'))
 
 
 
@@ -141,30 +180,36 @@
 # 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 @@
 _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 @@
 _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,35 +45,7 @@
         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('<ButtonRelease>', 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 @@
                                  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 = 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 @@
 
     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 @@
 
 """
 
-__version__ = '0.1'
+__version__ = '0.2'
 
 import sys
 import os
@@ -120,19 +120,27 @@
 
 
 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 @@
         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 @@
         #
         # 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 @@
                              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 @@
             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 @@
 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 @@
     def colordb(self):
         return self.__colordb
 
+    def set_colordb(self, colordb):
+        self.__colordb = colordb
+
     def optiondb(self):
         return self.__optiondb
 
@@ -74,6 +77,9 @@
         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: