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: