Skip to content

Commit 0604d72

Browse files
committed
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.
1 parent 0ec1493 commit 0604d72

6 files changed

Lines changed: 180 additions & 78 deletions

File tree

Tools/pynche/ChipViewer.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ def __init__(self,
4949
if releasecmd:
5050
self.__chip.bind('<ButtonRelease-1>', releasecmd)
5151

52-
def set_color(self, color):
52+
def set_color(self, color, colorname=None):
5353
self.__chip.config(background=color)
54-
self.__name.config(text=color)
54+
self.__name.config(text=colorname or color)
5555

5656
def get_color(self):
5757
return self.__chip['background']
@@ -83,25 +83,27 @@ def __init__(self, switchboard, master=None):
8383
releasecmd = self.__buttonrelease)
8484

8585
def update_yourself(self, red, green, blue):
86-
# TBD: should exactname default to X11 color name if their is an exact
87-
# match for the rgb triplet? Part of me says it's nice to see both
88-
# names for the color, the other part says that it's better to
89-
# feedback the exact match.
86+
# Selected always shows the #rrggbb name of the color, nearest always
87+
# shows the name of the nearest color in the database. TBD: should
88+
# an exact match be indicated in some way?
89+
#
90+
# Always use the #rrggbb style to actually set the color, since we may
91+
# not be using X color names (e.g. "web-safe" names)
92+
colordb = self.__sb.colordb()
9093
rgbtuple = (red, green, blue)
91-
try:
92-
allcolors = self.__sb.colordb().find_byrgb(rgbtuple)
93-
exactname = allcolors[0]
94-
except ColorDB.BadColor:
95-
exactname = ColorDB.triplet_to_rrggbb(rgbtuple)
96-
nearest = self.__sb.colordb().nearest(red, green, blue)
97-
self.__selected.set_color(exactname)
98-
self.__nearest.set_color(nearest)
94+
rrggbb = ColorDB.triplet_to_rrggbb(rgbtuple)
95+
# find the nearest
96+
nearest = colordb.nearest(red, green, blue)
97+
nearest_tuple = colordb.find_byname(nearest)
98+
nearest_rrggbb = ColorDB.triplet_to_rrggbb(nearest_tuple)
99+
self.__selected.set_color(rrggbb)
100+
self.__nearest.set_color(nearest_rrggbb, nearest)
99101

100102
def __buttonpress(self, event=None):
101103
self.__nearest.press()
102104

103105
def __buttonrelease(self, event=None):
104106
self.__nearest.release()
105-
colorname = self.__nearest.get_color()
106-
red, green, blue = self.__sb.colordb().find_byname(colorname)
107+
rrggbb = self.__nearest.get_color()
108+
red, green, blue = ColorDB.rrggbb_to_triplet(rrggbb)
107109
self.__sb.update_views(red, green, blue)

Tools/pynche/ColorDB.py

Lines changed: 74 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ class BadColor(Exception):
3535

3636
# generic class
3737
class ColorDB:
38-
def __init__(self, fp, lineno):
38+
def __init__(self, fp):
39+
lineno = 2
40+
self.__name = fp.name
3941
# Maintain several dictionaries for indexing into the color database.
4042
# Note that while Tk supports RGB intensities of 4, 8, 12, or 16 bits,
4143
# for now we only support 8 bit intensities. At least on OpenWindows,
@@ -54,6 +56,7 @@ def __init__(self, fp, lineno):
5456
if not line:
5557
break
5658
# get this compiled regular expression from derived class
59+
## print '%3d: %s' % (lineno, line[:-1])
5760
mo = self._re.match(line)
5861
if not mo:
5962
sys.stderr.write('Error in %s, line %d\n' % (fp.name, lineno))
@@ -62,9 +65,10 @@ def __init__(self, fp, lineno):
6265
#
6366
# extract the red, green, blue, and name
6467
#
65-
red, green, blue = map(int, mo.group('red', 'green', 'blue'))
66-
name = mo.group('name')
68+
red, green, blue = self._extractrgb(mo)
69+
name = self._extractname(mo)
6770
keyname = string.lower(name)
71+
## print keyname, '(%d, %d, %d)' % (red, green, blue)
6872
#
6973
# TBD: for now the `name' is just the first named color with the
7074
# rgb values we find. Later, we might want to make the two word
@@ -81,23 +85,36 @@ def __init__(self, fp, lineno):
8185
self.__byname[keyname] = key
8286
lineno = lineno + 1
8387

88+
# override in derived classes
89+
def _extractrgb(self, mo):
90+
return map(int, mo.group('red', 'green', 'blue'))
91+
92+
def _extractname(self, mo):
93+
return mo.group('name')
94+
95+
def filename(self):
96+
return self.__name
97+
8498
def find_byrgb(self, rgbtuple):
99+
"""Return name for rgbtuple"""
85100
try:
86101
return self.__byrgb[rgbtuple]
87102
except KeyError:
88103
raise BadColor(rgbtuple)
89104

90105
def find_byname(self, name):
106+
"""Return (red, green, blue) for name"""
91107
name = string.lower(name)
92108
try:
93109
return self.__byname[name]
94110
except KeyError:
95111
raise BadColor(name)
96112

97113
def nearest(self, red, green, blue):
98-
# TBD: use Voronoi diagrams, Delaunay triangulation, or octree for
99-
# speeding up the locating of nearest point. Exhaustive search is
100-
# inefficient, but may be fast enough.
114+
"""Return the name of color nearest (red, green, blue)"""
115+
# TBD: should we use Voronoi diagrams, Delaunay triangulation, or
116+
# octree for speeding up the locating of nearest point? Exhaustive
117+
# search is inefficient, but seems fast enough.
101118
nearest = -1
102119
nearest_name = ''
103120
for name, aliases in self.__byrgb.values():
@@ -133,38 +150,66 @@ def aliases_of(self, red, green, blue):
133150

134151
class RGBColorDB(ColorDB):
135152
_re = re.compile(
136-
'\s*(?P<red>\d+)\s+(?P<green>\d+)\s+(?P<blue>\d+)\s+(?P<name>.*)')
153+
'\s*(?P<red>\d+)\s+(?P<green>\d+)\s+(?P<blue>\d+)\s+(?P<name>.*)')
154+
155+
156+
class HTML40DB(ColorDB):
157+
_re = re.compile('(?P<name>\S+)\s+(?P<hexrgb>#[0-9a-fA-F]{6})')
158+
159+
def _extractrgb(self, mo):
160+
return rrggbb_to_triplet(mo.group('hexrgb'))
161+
162+
class LightlinkDB(HTML40DB):
163+
_re = re.compile('(?P<name>(.+))\s+(?P<hexrgb>#[0-9a-fA-F]{6})')
164+
165+
def _extractname(self, mo):
166+
return string.strip(mo.group('name'))
167+
168+
class WebsafeDB(ColorDB):
169+
_re = re.compile('(?P<hexrgb>#[0-9a-fA-F]{6})')
170+
171+
def _extractrgb(self, mo):
172+
return rrggbb_to_triplet(mo.group('hexrgb'))
173+
174+
def _extractname(self, mo):
175+
return string.upper(mo.group('hexrgb'))
137176

138177

139178

140179
# format is a tuple (RE, SCANLINES, CLASS) where RE is a compiled regular
141180
# expression, SCANLINES is the number of header lines to scan, and CLASS is
142181
# the class to instantiate if a match is found
143182

144-
X_RGB_TXT = re.compile('XConsortium'), 1, RGBColorDB
183+
FILETYPES = [
184+
(re.compile('XConsortium'), RGBColorDB),
185+
(re.compile('HTML'), HTML40DB),
186+
(re.compile('lightlink'), LightlinkDB),
187+
(re.compile('Websafe'), WebsafeDB),
188+
]
145189

146-
def get_colordb(file, filetype=X_RGB_TXT):
190+
def get_colordb(file, filetype=None):
147191
colordb = None
148-
fp = None
149-
typere, scanlines, class_ = filetype
192+
fp = open(file)
150193
try:
151-
try:
152-
lineno = 0
153-
fp = open(file)
154-
while lineno < scanlines:
155-
line = fp.readline()
156-
if not line:
157-
break
158-
mo = typere.search(line)
159-
if mo:
160-
colordb = class_(fp, lineno)
161-
break
162-
lineno = lineno + 1
163-
except IOError:
164-
pass
194+
line = fp.readline()
195+
if not line:
196+
return None
197+
# try to determine the type of RGB file it is
198+
if filetype is None:
199+
filetypes = FILETYPES
200+
else:
201+
filetypes = [filetype]
202+
for typere, class_ in filetypes:
203+
mo = typere.search(line)
204+
if mo:
205+
break
206+
else:
207+
# no matching type
208+
return None
209+
# we know the type and the class to grok the type, so suck it in
210+
colordb = class_(fp)
165211
finally:
166-
if fp:
167-
fp.close()
212+
fp.close()
168213
# save a global copy
169214
global DEFAULT_DB
170215
DEFAULT_DB = colordb
@@ -175,6 +220,7 @@ def get_colordb(file, filetype=X_RGB_TXT):
175220
_namedict = {}
176221
def rrggbb_to_triplet(color, atoi=string.atoi):
177222
"""Converts a #rrggbb color to the tuple (red, green, blue)."""
223+
global _namedict
178224
rgbtuple = _namedict.get(color)
179225
if rgbtuple is None:
180226
if color[0] <> '#':
@@ -190,6 +236,7 @@ def rrggbb_to_triplet(color, atoi=string.atoi):
190236
_tripdict = {}
191237
def triplet_to_rrggbb(rgbtuple):
192238
"""Converts a (red, green, blue) tuple to #rrggbb."""
239+
global _tripdict
193240
hexname = _tripdict.get(rgbtuple)
194241
if hexname is None:
195242
hexname = '#%02x%02x%02x' % rgbtuple

Tools/pynche/ListViewer.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,29 @@ def __init__(self, switchboard, master=None):
4545
canvas.pack(fill=BOTH, expand=1)
4646
canvas.configure(yscrollcommand=(self.__scrollbar, 'set'))
4747
self.__scrollbar.configure(command=(canvas, 'yview'))
48+
self.__populate()
49+
#
50+
# Update on click
51+
self.__uoc = BooleanVar()
52+
self.__uoc.set(optiondb.get('UPONCLICK', 1))
53+
self.__uocbtn = Checkbutton(root,
54+
text='Update on Click',
55+
variable=self.__uoc,
56+
command=self.__toggleupdate)
57+
self.__uocbtn.pack(expand=1, fill=BOTH)
58+
#
59+
# alias list
60+
self.__alabel = Label(root, text='Aliases:')
61+
self.__alabel.pack()
62+
self.__aliases = Listbox(root, height=5,
63+
selectmode=BROWSE)
64+
self.__aliases.pack(expand=1, fill=BOTH)
65+
66+
def __populate(self):
4867
#
4968
# create all the buttons
50-
colordb = switchboard.colordb()
69+
colordb = self.__sb.colordb()
70+
canvas = self.__canvas
5171
row = 0
5272
widest = 0
5373
bboxes = self.__bboxes = []
@@ -63,7 +83,7 @@ def __init__(self, switchboard, master=None):
6383
boxid = canvas.create_rectangle(3, row*20+3,
6484
textend+3, row*20 + 23,
6585
outline='',
66-
tags=(exactcolor,))
86+
tags=(exactcolor, 'all'))
6787
canvas.bind('<ButtonRelease>', self.__onrelease)
6888
bboxes.append(boxid)
6989
if textend+3 > widest:
@@ -74,22 +94,6 @@ def __init__(self, switchboard, master=None):
7494
for box in bboxes:
7595
x1, y1, x2, y2 = canvas.coords(box)
7696
canvas.coords(box, x1, y1, widest, y2)
77-
#
78-
# Update on click
79-
self.__uoc = BooleanVar()
80-
self.__uoc.set(optiondb.get('UPONCLICK', 1))
81-
self.__uocbtn = Checkbutton(root,
82-
text='Update on Click',
83-
variable=self.__uoc,
84-
command=self.__toggleupdate)
85-
self.__uocbtn.pack(expand=1, fill=BOTH)
86-
#
87-
# alias list
88-
self.__alabel = Label(root, text='Aliases:')
89-
self.__alabel.pack()
90-
self.__aliases = Listbox(root, height=5,
91-
selectmode=BROWSE)
92-
self.__aliases.pack(expand=1, fill=BOTH)
9397

9498
def __onrelease(self, event=None):
9599
canvas = self.__canvas
@@ -164,3 +168,7 @@ def update_yourself(self, red, green, blue):
164168

165169
def save_options(self, optiondb):
166170
optiondb['UPONCLICK'] = self.__uoc.get()
171+
172+
def flush(self):
173+
self.__canvas.delete('all')
174+
self.__populate()

Tools/pynche/Main.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
5050
"""
5151

52-
__version__ = '0.1'
52+
__version__ = '0.2'
5353

5454
import sys
5555
import os
@@ -120,19 +120,27 @@ def scan_color(s, colordb=colordb):
120120

121121

122122
def build(master=None, initialcolor=None, initfile=None, ignore=None):
123-
# create the windows and go
124-
for f in RGB_TXT:
125-
try:
126-
colordb = ColorDB.get_colordb(f)
127-
if colordb:
128-
break
129-
except IOError:
130-
pass
131-
else:
132-
usage(1, 'No color database file found, see the -d option.')
133-
134123
# create all output widgets
135-
s = Switchboard(colordb, not ignore and initfile)
124+
s = Switchboard(not ignore and initfile)
125+
126+
# load the color database
127+
colordb = None
128+
try:
129+
dbfile = s.optiondb()['DBFILE']
130+
colordb = ColorDB.get_colordb(dbfile)
131+
except (KeyError, IOError):
132+
# scoot through the files listed above to try to find a usable color
133+
# database file
134+
for f in RGB_TXT:
135+
try:
136+
colordb = ColorDB.get_colordb(f)
137+
if colordb:
138+
break
139+
except IOError:
140+
pass
141+
if not colordb:
142+
usage(1, 'No color database file found, see the -d option.')
143+
s.set_colordb(colordb)
136144

137145
# create the application window decorations
138146
app = PyncheWidget(__version__, s, master=master)

0 commit comments

Comments
 (0)