blob: 8205a9a877e30ab8613010449ef205fd9c87bad6 [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 Warsaw741eae02001-04-18 03:51:55 +0000211 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 = []
Barry Warsawee6d8a51998-03-17 15:59:26 +0000294 tk = self.__canvas.tk
Barry Warsaw6a3ea741998-09-28 20:58:06 +0000295 # get the red, green, and blue components for all chips
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000296 for t in self.__generator(self.__numchips, red, green, blue):
Barry Warsawee6d8a51998-03-17 15:59:26 +0000297 rrggbb = ColorDB.triplet_to_rrggbb(t)
298 chips.append(rrggbb)
299 tred, tgreen, tblue = t
300 if tred <= red and tgreen <= green and tblue <= blue:
301 chip = i
302 i = i + 1
303 # call the raw tcl script
304 colors = string.join(chips)
305 tk.eval('setcolor %s {%s}' % (self.__canvas._w, colors))
Barry Warsawee6d8a51998-03-17 15:59:26 +0000306 # move the arrows around
Barry Warsaw6a3ea741998-09-28 20:58:06 +0000307 self.__trackarrow(chip, (red, green, blue))
Barry Warsawee6d8a51998-03-17 15:59:26 +0000308
Barry Warsawef300921998-12-15 01:04:38 +0000309 def set(self, label, generator):
310 self.__canvas.itemconfigure(self.__label, text=label)
311 self.__generator = generator
312
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000313
314class StripViewer:
Barry Warsawca07ba01998-10-22 03:25:59 +0000315 def __init__(self, switchboard, master=None):
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000316 self.__sb = switchboard
Barry Warsaw8a09e1c1998-10-20 20:45:46 +0000317 optiondb = switchboard.optiondb()
Barry Warsawef300921998-12-15 01:04:38 +0000318 # create a frame inside the master.
319 frame = Frame(master, relief=RAISED, borderwidth=1)
320 frame.grid(row=1, column=0, columnspan=2, sticky='NSEW')
321 # create the options to be used later
Barry Warsaw8a09e1c1998-10-20 20:45:46 +0000322 uwd = self.__uwdvar = BooleanVar()
323 uwd.set(optiondb.get('UPWHILEDRAG', 0))
324 hexp = self.__hexpvar = BooleanVar()
325 hexp.set(optiondb.get('HEXSTRIP', 0))
Barry Warsawef300921998-12-15 01:04:38 +0000326 # create the red, green, blue strips inside their own frame
327 frame1 = Frame(frame)
328 frame1.pack(expand=YES, fill=BOTH)
329 self.__reds = StripWidget(switchboard, frame1,
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000330 generator=constant_cyan_generator,
331 axis=0,
Barry Warsawd3441651998-10-01 03:08:07 +0000332 label='Red Variations',
Barry Warsaw5b678391998-10-06 16:13:35 +0000333 uwdvar=uwd, hexvar=hexp)
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000334
Barry Warsawef300921998-12-15 01:04:38 +0000335 self.__greens = StripWidget(switchboard, frame1,
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000336 generator=constant_magenta_generator,
337 axis=1,
Barry Warsawd3441651998-10-01 03:08:07 +0000338 label='Green Variations',
Barry Warsaw5b678391998-10-06 16:13:35 +0000339 uwdvar=uwd, hexvar=hexp)
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000340
Barry Warsawef300921998-12-15 01:04:38 +0000341 self.__blues = StripWidget(switchboard, frame1,
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000342 generator=constant_yellow_generator,
343 axis=2,
Barry Warsawd3441651998-10-01 03:08:07 +0000344 label='Blue Variations',
Barry Warsaw5b678391998-10-06 16:13:35 +0000345 uwdvar=uwd, hexvar=hexp)
346
Barry Warsawef300921998-12-15 01:04:38 +0000347 # create a frame to contain the controls
348 frame2 = Frame(frame)
349 frame2.pack(expand=YES, fill=BOTH)
350 frame2.columnconfigure(0, weight=20)
351 frame2.columnconfigure(2, weight=20)
Barry Warsaw5b678391998-10-06 16:13:35 +0000352
Barry Warsawef300921998-12-15 01:04:38 +0000353 padx = 8
Barry Warsaw5b678391998-10-06 16:13:35 +0000354
Barry Warsawef300921998-12-15 01:04:38 +0000355 # create the black button
356 blackbtn = Button(frame2,
Barry Warsawf5e98571999-04-27 15:56:02 +0000357 text='Black',
Barry Warsawef300921998-12-15 01:04:38 +0000358 command=self.__toblack)
359 blackbtn.grid(row=0, column=0, rowspan=2, sticky=W, padx=padx)
360
361 # create the controls
362 uwdbtn = Checkbutton(frame2,
363 text='Update while dragging',
364 variable=uwd)
365 uwdbtn.grid(row=0, column=1, sticky=W)
366 hexbtn = Checkbutton(frame2,
367 text='Hexadecimal',
368 variable=hexp,
369 command=self.__togglehex)
370 hexbtn.grid(row=1, column=1, sticky=W)
371
372 # XXX: ignore this feature for now; it doesn't work quite right yet
373
374## gentypevar = self.__gentypevar = IntVar()
375## self.__variations = Radiobutton(frame,
376## text='Variations',
377## variable=gentypevar,
378## value=0,
379## command=self.__togglegentype)
380## self.__variations.grid(row=0, column=1, sticky=W)
381## self.__constants = Radiobutton(frame,
382## text='Constants',
383## variable=gentypevar,
384## value=1,
385## command=self.__togglegentype)
386## self.__constants.grid(row=1, column=1, sticky=W)
387
388 # create the white button
389 whitebtn = Button(frame2,
Barry Warsawf5e98571999-04-27 15:56:02 +0000390 text='White',
Barry Warsawef300921998-12-15 01:04:38 +0000391 command=self.__towhite)
392 whitebtn.grid(row=0, column=2, rowspan=2, sticky=E, padx=padx)
Barry Warsaw5b678391998-10-06 16:13:35 +0000393
Barry Warsaw0dc9c921998-09-28 22:42:44 +0000394 def update_yourself(self, red, green, blue):
395 self.__reds.update_yourself(red, green, blue)
396 self.__greens.update_yourself(red, green, blue)
397 self.__blues.update_yourself(red, green, blue)
Barry Warsaw5b678391998-10-06 16:13:35 +0000398
399 def __togglehex(self, event=None):
400 red, green, blue = self.__sb.current_rgb()
401 self.update_yourself(red, green, blue)
Barry Warsaw8a09e1c1998-10-20 20:45:46 +0000402
Barry Warsaw741eae02001-04-18 03:51:55 +0000403## def __togglegentype(self, event=None):
404## which = self.__gentypevar.get()
405## if which == 0:
406## self.__reds.set(label='Red Variations',
407## generator=constant_cyan_generator)
408## self.__greens.set(label='Green Variations',
409## generator=constant_magenta_generator)
410## self.__blues.set(label='Blue Variations',
411## generator=constant_yellow_generator)
412## elif which == 1:
413## self.__reds.set(label='Red Constant',
414## generator=constant_red_generator)
415## self.__greens.set(label='Green Constant',
416## generator=constant_green_generator)
417## self.__blues.set(label='Blue Constant',
418## generator=constant_blue_generator)
419## else:
420## assert 0
421## self.__sb.update_views_current()
Barry Warsawef300921998-12-15 01:04:38 +0000422
423 def __toblack(self, event=None):
424 self.__sb.update_views(0, 0, 0)
425
426 def __towhite(self, event=None):
427 self.__sb.update_views(255, 255, 255)
428
Barry Warsaw8a09e1c1998-10-20 20:45:46 +0000429 def save_options(self, optiondb):
430 optiondb['UPWHILEDRAG'] = self.__uwdvar.get()
431 optiondb['HEXSTRIP'] = self.__hexpvar.get()