mirror of https://github.com/python/cpython
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.
This commit is contained in:
parent
0ec1493d0b
commit
0604d72318
|
@ -49,9 +49,9 @@ class ChipWidget:
|
|||
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 @@ 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)
|
||||
|
|
|
@ -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<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):
|
|||
# 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
|
||||
|
|
|
@ -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('<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 @@ 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('<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 @@ class ListViewer:
|
|||
|
||||
def save_options(self, optiondb):
|
||||
optiondb['UPONCLICK'] = self.__uoc.get()
|
||||
|
||||
def flush(self):
|
||||
self.__canvas.delete('all')
|
||||
self.__populate()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue