blob: e181db514d3b48ecefbac3f4dcc0a83ea28794a6 [file] [log] [blame]
Barry Warsaw4638c5b1998-10-02 16:20:14 +00001"""Strip viewer and related widgets.
2
3The classes in this file implement the StripViewer shown in the top two thirds
4of the main Pynche window. It consists of three StripWidgets which display
5the variations in red, green, and blue respectively of the currently selected
6r/g/b color value.
7
8Each StripWidget shows the color variations that are reachable by varying an
9axis of the currently selected color. So for example, if the color is
10
11 (R,G,B)=(127,163,196)
12
13then the Red variations show colors from (0,163,196) to (255,163,196), the
14Green variations show colors from (127,0,196) to (127,255,196), and the Blue
15variations show colors from (127,163,0) to (127,163,255).
16
17The selected color is always visible in all three StripWidgets, and in fact
18each StripWidget highlights the selected color, and has an arrow pointing to
19the selected chip, which includes the value along that particular axis.
20
21Clicking on any chip in any StripWidget selects that color, and updates all
22arrows and other windows. By toggling on Update while dragging, Pynche will
23select the color under the cursor while you drag it, but be forewarned that
24this can be slow.
25"""
26
Barry Warsawf67a50c1998-02-18 00:05:59 +000027import string
Barry Warsaw9d882bc1998-02-12 19:51:57 +000028from Tkinter import *
Barry Warsaw9d882bc1998-02-12 19:51:57 +000029import ColorDB
30
Barry Warsaw4c2fab51998-02-18 16:22:22 +000031# Load this script into the Tcl interpreter and call it in
32# StripWidget.set_color(). This is about as fast as it can be with the
33# current _tkinter.c interface, which doesn't support Tcl Objects.
34TCLPROC = '''\
35proc setcolor {canv colors} {
36 set i 1
37 foreach c $colors {
38 $canv itemconfigure $i -fill $c -outline $c
39 incr i
40 }
41}
42'''
43
Barry Warsawee6d8a51998-03-17 15:59:26 +000044# Tcl event types
45BTNDOWN = 4
46BTNUP = 5
47BTNDRAG = 6
48
Barry Warsaw4c2fab51998-02-18 16:22:22 +000049
Barry Warsaw0dc9c921998-09-28 22:42:44 +000050def constant(numchips):
51 step = 255.0 / (numchips - 1)
52 start = 0.0
53 seq = []
54 while numchips > 0:
55 seq.append(int(start))
56 start = start + step
57 numchips = numchips - 1
58 return seq
59
60# red variations, green+blue = cyan constant
Barry Warsawef300921998-12-15 01:04:38 +000061def constant_red_generator(numchips, red, green, blue):
62 seq = constant(numchips)
63 return map(None, [red] * numchips, seq, seq)
64
65# green variations, red+blue = magenta constant
66def constant_green_generator(numchips, red, green, blue):
67 seq = constant(numchips)
68 return map(None, seq, [green] * numchips, seq)
69
70# blue variations, red+green = yellow constant
71def constant_blue_generator(numchips, red, green, blue):
72 seq = constant(numchips)
73 return map(None, seq, seq, [blue] * numchips)
74
75# red variations, green+blue = cyan constant
Barry Warsaw0dc9c921998-09-28 22:42:44 +000076def constant_cyan_generator(numchips, red, green, blue):
77 seq = constant(numchips)
78 return map(None, seq, [green] * numchips, [blue] * numchips)
79
80# green variations, red+blue = magenta constant
81def constant_magenta_generator(numchips, red, green, blue):
82 seq = constant(numchips)
83 return map(None, [red] * numchips, seq, [blue] * numchips)
84
85# blue variations, red+green = yellow constant
86def constant_yellow_generator(numchips, red, green, blue):
87 seq = constant(numchips)
88 return map(None, [red] * numchips, [green] * numchips, seq)
89
90
91
Barry Warsaw9d882bc1998-02-12 19:51:57 +000092class LeftArrow:
93 _ARROWWIDTH = 30
94 _ARROWHEIGHT = 15
95 _YOFFSET = 13
96 _TEXTYOFFSET = 1
97 _TAG = ('leftarrow',)
98
99 def __init__(self, canvas, x):
100 self._canvas = canvas
101 self.__arrow, self.__text = self._create(x)
102 self.move_to(x)
103
104 def _create(self, x):
105 arrow = self._canvas.create_line(
106 x, self._ARROWHEIGHT + self._YOFFSET,
107 x, self._YOFFSET,
108 x + self._ARROWWIDTH, self._YOFFSET,
109 arrow='first',
110 width=3.0,
111 tags=self._TAG)
112 text = self._canvas.create_text(
113 x + self._ARROWWIDTH + 13,
114 self._ARROWHEIGHT - self._TEXTYOFFSET,
Barry Warsaw35ae8641998-02-13 21:28:47 +0000115 tags=self._TAG,
Barry Warsaw9d882bc1998-02-12 19:51:57 +0000116 text='128')
117 return arrow, text
118
119 def _x(self):
120 coords = self._canvas.coords(self._TAG)
121 assert coords
122 return coords[0]
123
124 def move_to(self, x):
125 deltax = x - self._x()
126 self._canvas.move(self._TAG, deltax, 0)
127
Barry Warsaw35ae8641998-02-13 21:28:47 +0000128 def set_text(self, text):
129 self._canvas.itemconfigure(self.__text, text=text)
130
Barry Warsaw9d882bc1998-02-12 19:51:57 +0000131
132class RightArrow(LeftArrow):
133 _TAG = ('rightarrow',)
134
135 def _create(self, x):
136 arrow = self._canvas.create_line(
137 x, self._YOFFSET,
138 x + self._ARROWWIDTH, self._YOFFSET,
139 x + self._ARROWWIDTH, self._ARROWHEIGHT + self._YOFFSET,
140 arrow='last',
141 width=3.0,
142 tags=self._TAG)
143 text = self._canvas.create_text(
Barry Warsaw35ae8641998-02-13 21:28:47 +0000144 x - self._ARROWWIDTH + 15, # TBD: kludge
Barry Warsaw9d882bc1998-02-12 19:51:57 +0000145 self._ARROWHEIGHT - self._TEXTYOFFSET,
Barry Warsaw84f52e01998-10-06 23:04:55 +0000146 justify=RIGHT,
Barry Warsaw35ae8641998-02-13 21:28:47 +0000147 text='128',
148 tags=self._TAG)
Barry Warsaw9d882bc1998-02-12 19:51:57 +0000149 return arrow, text
150
151 def _x(self):
152 coords = self._canvas.bbox(self._TAG)
153 assert coords
154 return coords[2] - 6 # TBD: kludge
155
156
157
Barry Warsaw6a3ea741998-09-28 20:58:06 +0000158class StripWidget:
Barry Warsaw9d882bc1998-02-12 19:51:57 +0000159 _CHIPHEIGHT = 50
160 _CHIPWIDTH = 10
161 _NUMCHIPS = 40
162
Barry Warsaw6a3ea741998-09-28 20:58:06 +0000163 def __init__(self, switchboard,
Barry Warsawca07ba01998-10-22 03:25:59 +0000164 master = None,
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000165 chipwidth = _CHIPWIDTH,
166 chipheight = _CHIPHEIGHT,
167 numchips = _NUMCHIPS,
Barry Warsaw6a3ea741998-09-28 20:58:06 +0000168 generator = None,
169 axis = None,
Barry Warsawd3441651998-10-01 03:08:07 +0000170 label = '',
Barry Warsaw5b678391998-10-06 16:13:35 +0000171 uwdvar = None,
172 hexvar = None):
Barry Warsaw5177c481998-09-28 21:01:55 +0000173 # instance variables
174 self.__generator = generator
175 self.__axis = axis
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000176 self.__numchips = numchips
Barry Warsaw5177c481998-09-28 21:01:55 +0000177 assert self.__axis in (0, 1, 2)
Barry Warsawd3441651998-10-01 03:08:07 +0000178 self.__uwd = uwdvar
Barry Warsaw5b678391998-10-06 16:13:35 +0000179 self.__hexp = hexvar
Barry Warsaw6a3ea741998-09-28 20:58:06 +0000180 # the last chip selected
Barry Warsawee6d8a51998-03-17 15:59:26 +0000181 self.__lastchip = None
Barry Warsaw6a3ea741998-09-28 20:58:06 +0000182 self.__sb = switchboard
Barry Warsaw5177c481998-09-28 21:01:55 +0000183
Barry Warsaw9d882bc1998-02-12 19:51:57 +0000184 canvaswidth = numchips * (chipwidth + 1)
Barry Warsawbc689301998-02-17 03:09:40 +0000185 canvasheight = chipheight + 43 # TBD: Kludge
Barry Warsaw9d882bc1998-02-12 19:51:57 +0000186
Barry Warsawbc689301998-02-17 03:09:40 +0000187 # create the canvas and pack it
Barry Warsawca07ba01998-10-22 03:25:59 +0000188 canvas = self.__canvas = Canvas(master,
189 width=canvaswidth,
190 height=canvasheight,
191## borderwidth=2,
192## relief=GROOVE
193 )
Barry Warsawbc689301998-02-17 03:09:40 +0000194
Barry Warsaw4435d5a1998-02-18 17:00:24 +0000195 canvas.pack()
Barry Warsawee6d8a51998-03-17 15:59:26 +0000196 canvas.bind('<ButtonPress-1>', self.__select_chip)
Barry Warsaw4435d5a1998-02-18 17:00:24 +0000197 canvas.bind('<ButtonRelease-1>', self.__select_chip)
Barry Warsawee6d8a51998-03-17 15:59:26 +0000198 canvas.bind('<B1-Motion>', self.__select_chip)
Barry Warsaw9d882bc1998-02-12 19:51:57 +0000199
Barry Warsaw4c2fab51998-02-18 16:22:22 +0000200 # Load a proc into the Tcl interpreter. This is used in the
201 # set_color() method to speed up setting the chip colors.
Barry Warsaw4435d5a1998-02-18 17:00:24 +0000202 canvas.tk.eval(TCLPROC)
Barry Warsaw4c2fab51998-02-18 16:22:22 +0000203
Barry Warsaw9d882bc1998-02-12 19:51:57 +0000204 # create the color strip
205 chips = self.__chips = []
206 x = 1
207 y = 30
Barry Warsaw4435d5a1998-02-18 17:00:24 +0000208 tags = ('chip',)
Barry Warsaw9d882bc1998-02-12 19:51:57 +0000209 for c in range(self.__numchips):
Barry Warsawf67a50c1998-02-18 00:05:59 +0000210 color = 'grey'
Barry Warsaw4435d5a1998-02-18 17:00:24 +0000211 rect = canvas.create_rectangle(
Barry Warsaw9d882bc1998-02-12 19:51:57 +0000212 x, y, x+chipwidth, y+chipheight,
Barry Warsaw4435d5a1998-02-18 17:00:24 +0000213 fill=color, outline=color,
214 tags=tags)
Barry Warsaw9d882bc1998-02-12 19:51:57 +0000215 x = x + chipwidth + 1 # for outline
Barry Warsawf67a50c1998-02-18 00:05:59 +0000216 chips.append(color)
Barry Warsaw9d882bc1998-02-12 19:51:57 +0000217
Barry Warsaw6a3ea741998-09-28 20:58:06 +0000218 # create the strip label
Barry Warsaw4435d5a1998-02-18 17:00:24 +0000219 self.__label = canvas.create_text(
Barry Warsawbc689301998-02-17 03:09:40 +0000220 3, y + chipheight + 8,
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000221 text=label,
Barry Warsawbc689301998-02-17 03:09:40 +0000222 anchor=W)
223
Barry Warsaw9d882bc1998-02-12 19:51:57 +0000224 # create the arrow and text item
225 chipx = self.__arrow_x(0)
Barry Warsaw4435d5a1998-02-18 17:00:24 +0000226 self.__leftarrow = LeftArrow(canvas, chipx)
Barry Warsaw9d882bc1998-02-12 19:51:57 +0000227
228 chipx = self.__arrow_x(len(chips) - 1)
Barry Warsaw4435d5a1998-02-18 17:00:24 +0000229 self.__rightarrow = RightArrow(canvas, chipx)
Barry Warsaw9d882bc1998-02-12 19:51:57 +0000230
Barry Warsawbc689301998-02-17 03:09:40 +0000231 def __arrow_x(self, chipnum):
Barry Warsawf67a50c1998-02-18 00:05:59 +0000232 coords = self.__canvas.coords(chipnum+1)
Barry Warsawbc689301998-02-17 03:09:40 +0000233 assert coords
234 x0, y0, x1, y1 = coords
235 return (x1 + x0) / 2.0
236
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000237 # Invoked when one of the chips is clicked. This should just tell the
238 # switchboard to set the color on all the output components
Barry Warsawbc689301998-02-17 03:09:40 +0000239 def __select_chip(self, event=None):
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000240 x = event.x
241 y = event.y
242 canvas = self.__canvas
243 chip = canvas.find_overlapping(x, y, x, y)
244 if chip and (1 <= chip[0] <= self.__numchips):
245 color = self.__chips[chip[0]-1]
246 red, green, blue = ColorDB.rrggbb_to_triplet(color)
247 etype = int(event.type)
Barry Warsawd3441651998-10-01 03:08:07 +0000248 if (etype == BTNUP or self.__uwd.get()):
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000249 # update everyone
250 self.__sb.update_views(red, green, blue)
251 else:
252 # just track the arrows
253 self.__trackarrow(chip[0], (red, green, blue))
Barry Warsawbc689301998-02-17 03:09:40 +0000254
Barry Warsawee6d8a51998-03-17 15:59:26 +0000255 def __trackarrow(self, chip, rgbtuple):
256 # invert the last chip
257 if self.__lastchip is not None:
258 color = self.__canvas.itemcget(self.__lastchip, 'fill')
259 self.__canvas.itemconfigure(self.__lastchip, outline=color)
260 self.__lastchip = chip
Barry Warsawf67a50c1998-02-18 00:05:59 +0000261 # get the arrow's text
262 coloraxis = rgbtuple[self.__axis]
Barry Warsaw5b678391998-10-06 16:13:35 +0000263 if self.__hexp.get():
264 # hex
265 text = hex(coloraxis)
266 else:
267 # decimal
268 text = repr(coloraxis)
Barry Warsawf67a50c1998-02-18 00:05:59 +0000269 # move the arrow, and set it's text
270 if coloraxis <= 128:
Barry Warsaw6a3ea741998-09-28 20:58:06 +0000271 # use the left arrow
Barry Warsawf67a50c1998-02-18 00:05:59 +0000272 self.__leftarrow.set_text(text)
273 self.__leftarrow.move_to(self.__arrow_x(chip-1))
274 self.__rightarrow.move_to(-100)
275 else:
Barry Warsaw6a3ea741998-09-28 20:58:06 +0000276 # use the right arrow
Barry Warsawf67a50c1998-02-18 00:05:59 +0000277 self.__rightarrow.set_text(text)
278 self.__rightarrow.move_to(self.__arrow_x(chip-1))
279 self.__leftarrow.move_to(-100)
280 # and set the chip's outline
Barry Warsaw6a3ea741998-09-28 20:58:06 +0000281 brightness = ColorDB.triplet_to_brightness(rgbtuple)
Barry Warsaw26f4b5d1998-09-28 22:52:02 +0000282 if brightness <= 128:
Barry Warsawf67a50c1998-02-18 00:05:59 +0000283 outline = 'white'
284 else:
285 outline = 'black'
286 self.__canvas.itemconfigure(chip, outline=outline)
Barry Warsaw70787ed1998-03-16 23:08:53 +0000287
Barry Warsawee6d8a51998-03-17 15:59:26 +0000288
Barry Warsaw6a3ea741998-09-28 20:58:06 +0000289 def update_yourself(self, red, green, blue):
Barry Warsawee6d8a51998-03-17 15:59:26 +0000290 assert self.__generator
291 i = 1
292 chip = 0
293 chips = self.__chips = []
294 tclcmd = []
295 tk = self.__canvas.tk
Barry Warsaw6a3ea741998-09-28 20:58:06 +0000296 # get the red, green, and blue components for all chips
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000297 for t in self.__generator(self.__numchips, red, green, blue):
Barry Warsawee6d8a51998-03-17 15:59:26 +0000298 rrggbb = ColorDB.triplet_to_rrggbb(t)
299 chips.append(rrggbb)
300 tred, tgreen, tblue = t
301 if tred <= red and tgreen <= green and tblue <= blue:
302 chip = i
303 i = i + 1
304 # call the raw tcl script
305 colors = string.join(chips)
306 tk.eval('setcolor %s {%s}' % (self.__canvas._w, colors))
Barry Warsawee6d8a51998-03-17 15:59:26 +0000307 # move the arrows around
Barry Warsaw6a3ea741998-09-28 20:58:06 +0000308 self.__trackarrow(chip, (red, green, blue))
Barry Warsawee6d8a51998-03-17 15:59:26 +0000309
Barry Warsawef300921998-12-15 01:04:38 +0000310 def set(self, label, generator):
311 self.__canvas.itemconfigure(self.__label, text=label)
312 self.__generator = generator
313
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000314
315class StripViewer:
Barry Warsawca07ba01998-10-22 03:25:59 +0000316 def __init__(self, switchboard, master=None):
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000317 self.__sb = switchboard
Barry Warsaw8a09e1c1998-10-20 20:45:46 +0000318 optiondb = switchboard.optiondb()
Barry Warsawef300921998-12-15 01:04:38 +0000319 # create a frame inside the master.
320 frame = Frame(master, relief=RAISED, borderwidth=1)
321 frame.grid(row=1, column=0, columnspan=2, sticky='NSEW')
322 # create the options to be used later
Barry Warsaw8a09e1c1998-10-20 20:45:46 +0000323 uwd = self.__uwdvar = BooleanVar()
324 uwd.set(optiondb.get('UPWHILEDRAG', 0))
325 hexp = self.__hexpvar = BooleanVar()
326 hexp.set(optiondb.get('HEXSTRIP', 0))
Barry Warsawef300921998-12-15 01:04:38 +0000327 # create the red, green, blue strips inside their own frame
328 frame1 = Frame(frame)
329 frame1.pack(expand=YES, fill=BOTH)
330 self.__reds = StripWidget(switchboard, frame1,
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000331 generator=constant_cyan_generator,
332 axis=0,
Barry Warsawd3441651998-10-01 03:08:07 +0000333 label='Red Variations',
Barry Warsaw5b678391998-10-06 16:13:35 +0000334 uwdvar=uwd, hexvar=hexp)
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000335
Barry Warsawef300921998-12-15 01:04:38 +0000336 self.__greens = StripWidget(switchboard, frame1,
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000337 generator=constant_magenta_generator,
338 axis=1,
Barry Warsawd3441651998-10-01 03:08:07 +0000339 label='Green Variations',
Barry Warsaw5b678391998-10-06 16:13:35 +0000340 uwdvar=uwd, hexvar=hexp)
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000341
Barry Warsawef300921998-12-15 01:04:38 +0000342 self.__blues = StripWidget(switchboard, frame1,
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000343 generator=constant_yellow_generator,
344 axis=2,
Barry Warsawd3441651998-10-01 03:08:07 +0000345 label='Blue Variations',
Barry Warsaw5b678391998-10-06 16:13:35 +0000346 uwdvar=uwd, hexvar=hexp)
347
Barry Warsawef300921998-12-15 01:04:38 +0000348 # create a frame to contain the controls
349 frame2 = Frame(frame)
350 frame2.pack(expand=YES, fill=BOTH)
351 frame2.columnconfigure(0, weight=20)
352 frame2.columnconfigure(2, weight=20)
Barry Warsaw5b678391998-10-06 16:13:35 +0000353
Barry Warsawef300921998-12-15 01:04:38 +0000354 padx = 8
Barry Warsaw5b678391998-10-06 16:13:35 +0000355
Barry Warsawef300921998-12-15 01:04:38 +0000356 # create the black button
357 blackbtn = Button(frame2,
Barry Warsawf5e98571999-04-27 15:56:02 +0000358 text='Black',
Barry Warsawef300921998-12-15 01:04:38 +0000359 command=self.__toblack)
360 blackbtn.grid(row=0, column=0, rowspan=2, sticky=W, padx=padx)
361
362 # create the controls
363 uwdbtn = Checkbutton(frame2,
364 text='Update while dragging',
365 variable=uwd)
366 uwdbtn.grid(row=0, column=1, sticky=W)
367 hexbtn = Checkbutton(frame2,
368 text='Hexadecimal',
369 variable=hexp,
370 command=self.__togglehex)
371 hexbtn.grid(row=1, column=1, sticky=W)
372
373 # XXX: ignore this feature for now; it doesn't work quite right yet
374
375## gentypevar = self.__gentypevar = IntVar()
376## self.__variations = Radiobutton(frame,
377## text='Variations',
378## variable=gentypevar,
379## value=0,
380## command=self.__togglegentype)
381## self.__variations.grid(row=0, column=1, sticky=W)
382## self.__constants = Radiobutton(frame,
383## text='Constants',
384## variable=gentypevar,
385## value=1,
386## command=self.__togglegentype)
387## self.__constants.grid(row=1, column=1, sticky=W)
388
389 # create the white button
390 whitebtn = Button(frame2,
Barry Warsawf5e98571999-04-27 15:56:02 +0000391 text='White',
Barry Warsawef300921998-12-15 01:04:38 +0000392 command=self.__towhite)
393 whitebtn.grid(row=0, column=2, rowspan=2, sticky=E, padx=padx)
Barry Warsaw5b678391998-10-06 16:13:35 +0000394
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000395 def update_yourself(self, red, green, blue):
396 self.__reds.update_yourself(red, green, blue)
397 self.__greens.update_yourself(red, green, blue)
398 self.__blues.update_yourself(red, green, blue)
Barry Warsaw5b678391998-10-06 16:13:35 +0000399
400 def __togglehex(self, event=None):
401 red, green, blue = self.__sb.current_rgb()
402 self.update_yourself(red, green, blue)
Barry Warsaw8a09e1c1998-10-20 20:45:46 +0000403
Barry Warsawef300921998-12-15 01:04:38 +0000404 def __togglegentype(self, event=None):
405 which = self.__gentypevar.get()
406 if which == 0:
407 self.__reds.set(label='Red Variations',
408 generator=constant_cyan_generator)
409 self.__greens.set(label='Green Variations',
410 generator=constant_magenta_generator)
411 self.__blues.set(label='Blue Variations',
412 generator=constant_yellow_generator)
413 elif which == 1:
414 self.__reds.set(label='Red Constant',
415 generator=constant_red_generator)
416 self.__greens.set(label='Green Constant',
417 generator=constant_green_generator)
418 self.__blues.set(label='Blue Constant',
419 generator=constant_blue_generator)
420 else:
421 assert 0
422 self.__sb.update_views_current()
423
424 def __toblack(self, event=None):
425 self.__sb.update_views(0, 0, 0)
426
427 def __towhite(self, event=None):
428 self.__sb.update_views(255, 255, 255)
429
Barry Warsaw8a09e1c1998-10-20 20:45:46 +0000430 def save_options(self, optiondb):
431 optiondb['UPWHILEDRAG'] = self.__uwdvar.get()
432 optiondb['HEXSTRIP'] = self.__hexpvar.get()