Commit 0604d723 authored by Barry Warsaw's avatar Barry Warsaw

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.
parent 0ec1493d
...@@ -49,9 +49,9 @@ class ChipWidget: ...@@ -49,9 +49,9 @@ class ChipWidget:
if releasecmd: if releasecmd:
self.__chip.bind('<ButtonRelease-1>', 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.__chip.config(background=color)
self.__name.config(text=color) self.__name.config(text=colorname or color)
def get_color(self): def get_color(self):
return self.__chip['background'] return self.__chip['background']
...@@ -83,25 +83,27 @@ class ChipViewer: ...@@ -83,25 +83,27 @@ class ChipViewer:
releasecmd = self.__buttonrelease) releasecmd = self.__buttonrelease)
def update_yourself(self, red, green, blue): def update_yourself(self, red, green, blue):
# TBD: should exactname default to X11 color name if their is an exact # Selected always shows the #rrggbb name of the color, nearest always
# match for the rgb triplet? Part of me says it's nice to see both # shows the name of the nearest color in the database. TBD: should
# names for the color, the other part says that it's better to # an exact match be indicated in some way?
# feedback the exact match. #
# 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) rgbtuple = (red, green, blue)
try: rrggbb = ColorDB.triplet_to_rrggbb(rgbtuple)
allcolors = self.__sb.colordb().find_byrgb(rgbtuple) # find the nearest
exactname = allcolors[0] nearest = colordb.nearest(red, green, blue)
except ColorDB.BadColor: nearest_tuple = colordb.find_byname(nearest)
exactname = ColorDB.triplet_to_rrggbb(rgbtuple) nearest_rrggbb = ColorDB.triplet_to_rrggbb(nearest_tuple)
nearest = self.__sb.colordb().nearest(red, green, blue) self.__selected.set_color(rrggbb)
self.__selected.set_color(exactname) self.__nearest.set_color(nearest_rrggbb, nearest)
self.__nearest.set_color(nearest)
def __buttonpress(self, event=None): def __buttonpress(self, event=None):
self.__nearest.press() self.__nearest.press()
def __buttonrelease(self, event=None): def __buttonrelease(self, event=None):
self.__nearest.release() self.__nearest.release()
colorname = self.__nearest.get_color() rrggbb = self.__nearest.get_color()
red, green, blue = self.__sb.colordb().find_byname(colorname) red, green, blue = ColorDB.rrggbb_to_triplet(rrggbb)
self.__sb.update_views(red, green, blue) self.__sb.update_views(red, green, blue)
...@@ -35,7 +35,9 @@ DEFAULT_DB = None ...@@ -35,7 +35,9 @@ DEFAULT_DB = None
# generic class # generic class
class ColorDB: 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. # Maintain several dictionaries for indexing into the color database.
# Note that while Tk supports RGB intensities of 4, 8, 12, or 16 bits, # 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, # for now we only support 8 bit intensities. At least on OpenWindows,
...@@ -54,6 +56,7 @@ class ColorDB: ...@@ -54,6 +56,7 @@ class ColorDB:
if not line: if not line:
break break
# get this compiled regular expression from derived class # get this compiled regular expression from derived class
## print '%3d: %s' % (lineno, line[:-1])
mo = self._re.match(line) mo = self._re.match(line)
if not mo: if not mo:
sys.stderr.write('Error in %s, line %d\n' % (fp.name, lineno)) sys.stderr.write('Error in %s, line %d\n' % (fp.name, lineno))
...@@ -62,9 +65,10 @@ class ColorDB: ...@@ -62,9 +65,10 @@ class ColorDB:
# #
# extract the red, green, blue, and name # extract the red, green, blue, and name
# #
red, green, blue = map(int, mo.group('red', 'green', 'blue')) red, green, blue = self._extractrgb(mo)
name = mo.group('name') name = self._extractname(mo)
keyname = string.lower(name) 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 # 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 # rgb values we find. Later, we might want to make the two word
...@@ -81,13 +85,25 @@ class ColorDB: ...@@ -81,13 +85,25 @@ class ColorDB:
self.__byname[keyname] = key self.__byname[keyname] = key
lineno = lineno + 1 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): def find_byrgb(self, rgbtuple):
"""Return name for rgbtuple"""
try: try:
return self.__byrgb[rgbtuple] return self.__byrgb[rgbtuple]
except KeyError: except KeyError:
raise BadColor(rgbtuple) raise BadColor(rgbtuple)
def find_byname(self, name): def find_byname(self, name):
"""Return (red, green, blue) for name"""
name = string.lower(name) name = string.lower(name)
try: try:
return self.__byname[name] return self.__byname[name]
...@@ -95,9 +111,10 @@ class ColorDB: ...@@ -95,9 +111,10 @@ class ColorDB:
raise BadColor(name) raise BadColor(name)
def nearest(self, red, green, blue): def nearest(self, red, green, blue):
# TBD: use Voronoi diagrams, Delaunay triangulation, or octree for """Return the name of color nearest (red, green, blue)"""
# speeding up the locating of nearest point. Exhaustive search is # TBD: should we use Voronoi diagrams, Delaunay triangulation, or
# inefficient, but may be fast enough. # octree for speeding up the locating of nearest point? Exhaustive
# search is inefficient, but seems fast enough.
nearest = -1 nearest = -1
nearest_name = '' nearest_name = ''
for name, aliases in self.__byrgb.values(): for name, aliases in self.__byrgb.values():
...@@ -133,7 +150,29 @@ class ColorDB: ...@@ -133,7 +150,29 @@ class ColorDB:
class RGBColorDB(ColorDB): class RGBColorDB(ColorDB):
_re = re.compile( _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 @@ class RGBColorDB(ColorDB): ...@@ -141,30 +180,36 @@ class RGBColorDB(ColorDB):
# expression, SCANLINES is the number of header lines to scan, and CLASS is # expression, SCANLINES is the number of header lines to scan, and CLASS is
# the class to instantiate if a match is found # 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 colordb = None
fp = None fp = open(file)
typere, scanlines, class_ = filetype
try: try:
try: line = fp.readline()
lineno = 0 if not line:
fp = open(file) return None
while lineno < scanlines: # try to determine the type of RGB file it is
line = fp.readline() if filetype is None:
if not line: filetypes = FILETYPES
break else:
mo = typere.search(line) filetypes = [filetype]
if mo: for typere, class_ in filetypes:
colordb = class_(fp, lineno) mo = typere.search(line)
break if mo:
lineno = lineno + 1 break
except IOError: else:
pass # no matching type
return None
# we know the type and the class to grok the type, so suck it in
colordb = class_(fp)
finally: finally:
if fp: fp.close()
fp.close()
# save a global copy # save a global copy
global DEFAULT_DB global DEFAULT_DB
DEFAULT_DB = colordb DEFAULT_DB = colordb
...@@ -175,6 +220,7 @@ def get_colordb(file, filetype=X_RGB_TXT): ...@@ -175,6 +220,7 @@ def get_colordb(file, filetype=X_RGB_TXT):
_namedict = {} _namedict = {}
def rrggbb_to_triplet(color, atoi=string.atoi): def rrggbb_to_triplet(color, atoi=string.atoi):
"""Converts a #rrggbb color to the tuple (red, green, blue).""" """Converts a #rrggbb color to the tuple (red, green, blue)."""
global _namedict
rgbtuple = _namedict.get(color) rgbtuple = _namedict.get(color)
if rgbtuple is None: if rgbtuple is None:
if color[0] <> '#': if color[0] <> '#':
...@@ -190,6 +236,7 @@ def rrggbb_to_triplet(color, atoi=string.atoi): ...@@ -190,6 +236,7 @@ def rrggbb_to_triplet(color, atoi=string.atoi):
_tripdict = {} _tripdict = {}
def triplet_to_rrggbb(rgbtuple): def triplet_to_rrggbb(rgbtuple):
"""Converts a (red, green, blue) tuple to #rrggbb.""" """Converts a (red, green, blue) tuple to #rrggbb."""
global _tripdict
hexname = _tripdict.get(rgbtuple) hexname = _tripdict.get(rgbtuple)
if hexname is None: if hexname is None:
hexname = '#%02x%02x%02x' % rgbtuple hexname = '#%02x%02x%02x' % rgbtuple
......
...@@ -45,9 +45,29 @@ class ListViewer: ...@@ -45,9 +45,29 @@ class ListViewer:
canvas.pack(fill=BOTH, expand=1) canvas.pack(fill=BOTH, expand=1)
canvas.configure(yscrollcommand=(self.__scrollbar, 'set')) canvas.configure(yscrollcommand=(self.__scrollbar, 'set'))
self.__scrollbar.configure(command=(canvas, 'yview')) 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 # create all the buttons
colordb = switchboard.colordb() colordb = self.__sb.colordb()
canvas = self.__canvas
row = 0 row = 0
widest = 0 widest = 0
bboxes = self.__bboxes = [] bboxes = self.__bboxes = []
...@@ -63,7 +83,7 @@ class ListViewer: ...@@ -63,7 +83,7 @@ class ListViewer:
boxid = canvas.create_rectangle(3, row*20+3, boxid = canvas.create_rectangle(3, row*20+3,
textend+3, row*20 + 23, textend+3, row*20 + 23,
outline='', outline='',
tags=(exactcolor,)) tags=(exactcolor, 'all'))
canvas.bind('<ButtonRelease>', self.__onrelease) canvas.bind('<ButtonRelease>', self.__onrelease)
bboxes.append(boxid) bboxes.append(boxid)
if textend+3 > widest: if textend+3 > widest:
...@@ -74,22 +94,6 @@ class ListViewer: ...@@ -74,22 +94,6 @@ class ListViewer:
for box in bboxes: for box in bboxes:
x1, y1, x2, y2 = canvas.coords(box) x1, y1, x2, y2 = canvas.coords(box)
canvas.coords(box, x1, y1, widest, y2) 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): def __onrelease(self, event=None):
canvas = self.__canvas canvas = self.__canvas
...@@ -164,3 +168,7 @@ class ListViewer: ...@@ -164,3 +168,7 @@ class ListViewer:
def save_options(self, optiondb): def save_options(self, optiondb):
optiondb['UPONCLICK'] = self.__uoc.get() optiondb['UPONCLICK'] = self.__uoc.get()
def flush(self):
self.__canvas.delete('all')
self.__populate()
...@@ -49,7 +49,7 @@ Where: ...@@ -49,7 +49,7 @@ Where:
""" """
__version__ = '0.1' __version__ = '0.2'
import sys import sys
import os import os
...@@ -120,19 +120,27 @@ def initial_color(s, colordb): ...@@ -120,19 +120,27 @@ def initial_color(s, colordb):
def build(master=None, initialcolor=None, initfile=None, ignore=None): 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 # 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 # create the application window decorations
app = PyncheWidget(__version__, s, master=master) app = PyncheWidget(__version__, s, master=master)
......
...@@ -23,6 +23,7 @@ class PyncheWidget: ...@@ -23,6 +23,7 @@ class PyncheWidget:
self.__listwin = None self.__listwin = None
self.__detailswin = None self.__detailswin = None
self.__helpwin = None self.__helpwin = None
self.__dialogstate = {}
modal = self.__modal = not not master modal = self.__modal = not not master
# If a master was given, we are running as a modal dialog servant to # 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 # some other application. We rearrange our UI in this case (there's
...@@ -51,8 +52,11 @@ class PyncheWidget: ...@@ -51,8 +52,11 @@ class PyncheWidget:
# #
# File menu # File menu
# #
filemenu = self.__filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label='Load palette...',
command=self.__load,
underline=0)
if not modal: if not modal:
filemenu = self.__filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label='Quit', filemenu.add_command(label='Quit',
command=self.__quit, command=self.__quit,
accelerator='Alt-Q', accelerator='Alt-Q',
...@@ -66,7 +70,7 @@ class PyncheWidget: ...@@ -66,7 +70,7 @@ class PyncheWidget:
underline=0) underline=0)
viewmenu.add_command(label='Color List Window...', viewmenu.add_command(label='Color List Window...',
command=self.__popup_listwin, command=self.__popup_listwin,
underline=0) underline=6)
viewmenu.add_command(label='Details Window...', viewmenu.add_command(label='Details Window...',
command=self.__popup_details, command=self.__popup_details,
underline=0) underline=0)
...@@ -186,6 +190,33 @@ email: bwarsaw@python.org''' % __version__) ...@@ -186,6 +190,33 @@ email: bwarsaw@python.org''' % __version__)
self.__sb.add_view(self.__detailswin) self.__sb.add_view(self.__detailswin)
self.__detailswin.deiconify() 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): def withdraw(self):
self.__root.withdraw() self.__root.withdraw()
......
...@@ -17,9 +17,9 @@ from types import DictType ...@@ -17,9 +17,9 @@ from types import DictType
import marshal import marshal
class Switchboard: class Switchboard:
def __init__(self, colordb, initfile): def __init__(self, initfile):
self.__initfile = initfile self.__initfile = initfile
self.__colordb = colordb self.__colordb = None
self.__optiondb = {} self.__optiondb = {}
self.__views = [] self.__views = []
self.__red = 0 self.__red = 0
...@@ -63,6 +63,9 @@ class Switchboard: ...@@ -63,6 +63,9 @@ class Switchboard:
def colordb(self): def colordb(self):
return self.__colordb return self.__colordb
def set_colordb(self, colordb):
self.__colordb = colordb
def optiondb(self): def optiondb(self):
return self.__optiondb return self.__optiondb
...@@ -74,6 +77,9 @@ class Switchboard: ...@@ -74,6 +77,9 @@ class Switchboard:
for v in self.__views: for v in self.__views:
if hasattr(v, 'save_options'): if hasattr(v, 'save_options'):
v.save_options(self.__optiondb) 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 fp = None
try: try:
try: try:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment