|
1 """Strip viewer and related widgets. |
|
2 |
|
3 The classes in this file implement the StripViewer shown in the top two thirds |
|
4 of the main Pynche window. It consists of three StripWidgets which display |
|
5 the variations in red, green, and blue respectively of the currently selected |
|
6 r/g/b color value. |
|
7 |
|
8 Each StripWidget shows the color variations that are reachable by varying an |
|
9 axis of the currently selected color. So for example, if the color is |
|
10 |
|
11 (R,G,B)=(127,163,196) |
|
12 |
|
13 then the Red variations show colors from (0,163,196) to (255,163,196), the |
|
14 Green variations show colors from (127,0,196) to (127,255,196), and the Blue |
|
15 variations show colors from (127,163,0) to (127,163,255). |
|
16 |
|
17 The selected color is always visible in all three StripWidgets, and in fact |
|
18 each StripWidget highlights the selected color, and has an arrow pointing to |
|
19 the selected chip, which includes the value along that particular axis. |
|
20 |
|
21 Clicking on any chip in any StripWidget selects that color, and updates all |
|
22 arrows and other windows. By toggling on Update while dragging, Pynche will |
|
23 select the color under the cursor while you drag it, but be forewarned that |
|
24 this can be slow. |
|
25 """ |
|
26 |
|
27 from Tkinter import * |
|
28 import ColorDB |
|
29 |
|
30 # Load this script into the Tcl interpreter and call it in |
|
31 # StripWidget.set_color(). This is about as fast as it can be with the |
|
32 # current _tkinter.c interface, which doesn't support Tcl Objects. |
|
33 TCLPROC = '''\ |
|
34 proc setcolor {canv colors} { |
|
35 set i 1 |
|
36 foreach c $colors { |
|
37 $canv itemconfigure $i -fill $c -outline $c |
|
38 incr i |
|
39 } |
|
40 } |
|
41 ''' |
|
42 |
|
43 # Tcl event types |
|
44 BTNDOWN = 4 |
|
45 BTNUP = 5 |
|
46 BTNDRAG = 6 |
|
47 |
|
48 SPACE = ' ' |
|
49 |
|
50 |
|
51 |
|
52 def constant(numchips): |
|
53 step = 255.0 / (numchips - 1) |
|
54 start = 0.0 |
|
55 seq = [] |
|
56 while numchips > 0: |
|
57 seq.append(int(start)) |
|
58 start = start + step |
|
59 numchips = numchips - 1 |
|
60 return seq |
|
61 |
|
62 # red variations, green+blue = cyan constant |
|
63 def constant_red_generator(numchips, red, green, blue): |
|
64 seq = constant(numchips) |
|
65 return map(None, [red] * numchips, seq, seq) |
|
66 |
|
67 # green variations, red+blue = magenta constant |
|
68 def constant_green_generator(numchips, red, green, blue): |
|
69 seq = constant(numchips) |
|
70 return map(None, seq, [green] * numchips, seq) |
|
71 |
|
72 # blue variations, red+green = yellow constant |
|
73 def constant_blue_generator(numchips, red, green, blue): |
|
74 seq = constant(numchips) |
|
75 return map(None, seq, seq, [blue] * numchips) |
|
76 |
|
77 # red variations, green+blue = cyan constant |
|
78 def constant_cyan_generator(numchips, red, green, blue): |
|
79 seq = constant(numchips) |
|
80 return map(None, seq, [green] * numchips, [blue] * numchips) |
|
81 |
|
82 # green variations, red+blue = magenta constant |
|
83 def constant_magenta_generator(numchips, red, green, blue): |
|
84 seq = constant(numchips) |
|
85 return map(None, [red] * numchips, seq, [blue] * numchips) |
|
86 |
|
87 # blue variations, red+green = yellow constant |
|
88 def constant_yellow_generator(numchips, red, green, blue): |
|
89 seq = constant(numchips) |
|
90 return map(None, [red] * numchips, [green] * numchips, seq) |
|
91 |
|
92 |
|
93 |
|
94 class LeftArrow: |
|
95 _ARROWWIDTH = 30 |
|
96 _ARROWHEIGHT = 15 |
|
97 _YOFFSET = 13 |
|
98 _TEXTYOFFSET = 1 |
|
99 _TAG = ('leftarrow',) |
|
100 |
|
101 def __init__(self, canvas, x): |
|
102 self._canvas = canvas |
|
103 self.__arrow, self.__text = self._create(x) |
|
104 self.move_to(x) |
|
105 |
|
106 def _create(self, x): |
|
107 arrow = self._canvas.create_line( |
|
108 x, self._ARROWHEIGHT + self._YOFFSET, |
|
109 x, self._YOFFSET, |
|
110 x + self._ARROWWIDTH, self._YOFFSET, |
|
111 arrow='first', |
|
112 width=3.0, |
|
113 tags=self._TAG) |
|
114 text = self._canvas.create_text( |
|
115 x + self._ARROWWIDTH + 13, |
|
116 self._ARROWHEIGHT - self._TEXTYOFFSET, |
|
117 tags=self._TAG, |
|
118 text='128') |
|
119 return arrow, text |
|
120 |
|
121 def _x(self): |
|
122 coords = self._canvas.coords(self._TAG) |
|
123 assert coords |
|
124 return coords[0] |
|
125 |
|
126 def move_to(self, x): |
|
127 deltax = x - self._x() |
|
128 self._canvas.move(self._TAG, deltax, 0) |
|
129 |
|
130 def set_text(self, text): |
|
131 self._canvas.itemconfigure(self.__text, text=text) |
|
132 |
|
133 |
|
134 class RightArrow(LeftArrow): |
|
135 _TAG = ('rightarrow',) |
|
136 |
|
137 def _create(self, x): |
|
138 arrow = self._canvas.create_line( |
|
139 x, self._YOFFSET, |
|
140 x + self._ARROWWIDTH, self._YOFFSET, |
|
141 x + self._ARROWWIDTH, self._ARROWHEIGHT + self._YOFFSET, |
|
142 arrow='last', |
|
143 width=3.0, |
|
144 tags=self._TAG) |
|
145 text = self._canvas.create_text( |
|
146 x - self._ARROWWIDTH + 15, # BAW: kludge |
|
147 self._ARROWHEIGHT - self._TEXTYOFFSET, |
|
148 justify=RIGHT, |
|
149 text='128', |
|
150 tags=self._TAG) |
|
151 return arrow, text |
|
152 |
|
153 def _x(self): |
|
154 coords = self._canvas.coords(self._TAG) |
|
155 assert coords |
|
156 return coords[0] + self._ARROWWIDTH |
|
157 |
|
158 |
|
159 |
|
160 class StripWidget: |
|
161 _CHIPHEIGHT = 50 |
|
162 _CHIPWIDTH = 10 |
|
163 _NUMCHIPS = 40 |
|
164 |
|
165 def __init__(self, switchboard, |
|
166 master = None, |
|
167 chipwidth = _CHIPWIDTH, |
|
168 chipheight = _CHIPHEIGHT, |
|
169 numchips = _NUMCHIPS, |
|
170 generator = None, |
|
171 axis = None, |
|
172 label = '', |
|
173 uwdvar = None, |
|
174 hexvar = None): |
|
175 # instance variables |
|
176 self.__generator = generator |
|
177 self.__axis = axis |
|
178 self.__numchips = numchips |
|
179 assert self.__axis in (0, 1, 2) |
|
180 self.__uwd = uwdvar |
|
181 self.__hexp = hexvar |
|
182 # the last chip selected |
|
183 self.__lastchip = None |
|
184 self.__sb = switchboard |
|
185 |
|
186 canvaswidth = numchips * (chipwidth + 1) |
|
187 canvasheight = chipheight + 43 # BAW: Kludge |
|
188 |
|
189 # create the canvas and pack it |
|
190 canvas = self.__canvas = Canvas(master, |
|
191 width=canvaswidth, |
|
192 height=canvasheight, |
|
193 ## borderwidth=2, |
|
194 ## relief=GROOVE |
|
195 ) |
|
196 |
|
197 canvas.pack() |
|
198 canvas.bind('<ButtonPress-1>', self.__select_chip) |
|
199 canvas.bind('<ButtonRelease-1>', self.__select_chip) |
|
200 canvas.bind('<B1-Motion>', self.__select_chip) |
|
201 |
|
202 # Load a proc into the Tcl interpreter. This is used in the |
|
203 # set_color() method to speed up setting the chip colors. |
|
204 canvas.tk.eval(TCLPROC) |
|
205 |
|
206 # create the color strip |
|
207 chips = self.__chips = [] |
|
208 x = 1 |
|
209 y = 30 |
|
210 tags = ('chip',) |
|
211 for c in range(self.__numchips): |
|
212 color = 'grey' |
|
213 canvas.create_rectangle( |
|
214 x, y, x+chipwidth, y+chipheight, |
|
215 fill=color, outline=color, |
|
216 tags=tags) |
|
217 x = x + chipwidth + 1 # for outline |
|
218 chips.append(color) |
|
219 |
|
220 # create the strip label |
|
221 self.__label = canvas.create_text( |
|
222 3, y + chipheight + 8, |
|
223 text=label, |
|
224 anchor=W) |
|
225 |
|
226 # create the arrow and text item |
|
227 chipx = self.__arrow_x(0) |
|
228 self.__leftarrow = LeftArrow(canvas, chipx) |
|
229 |
|
230 chipx = self.__arrow_x(len(chips) - 1) |
|
231 self.__rightarrow = RightArrow(canvas, chipx) |
|
232 |
|
233 def __arrow_x(self, chipnum): |
|
234 coords = self.__canvas.coords(chipnum+1) |
|
235 assert coords |
|
236 x0, y0, x1, y1 = coords |
|
237 return (x1 + x0) / 2.0 |
|
238 |
|
239 # Invoked when one of the chips is clicked. This should just tell the |
|
240 # switchboard to set the color on all the output components |
|
241 def __select_chip(self, event=None): |
|
242 x = event.x |
|
243 y = event.y |
|
244 canvas = self.__canvas |
|
245 chip = canvas.find_overlapping(x, y, x, y) |
|
246 if chip and (1 <= chip[0] <= self.__numchips): |
|
247 color = self.__chips[chip[0]-1] |
|
248 red, green, blue = ColorDB.rrggbb_to_triplet(color) |
|
249 etype = int(event.type) |
|
250 if (etype == BTNUP or self.__uwd.get()): |
|
251 # update everyone |
|
252 self.__sb.update_views(red, green, blue) |
|
253 else: |
|
254 # just track the arrows |
|
255 self.__trackarrow(chip[0], (red, green, blue)) |
|
256 |
|
257 def __trackarrow(self, chip, rgbtuple): |
|
258 # invert the last chip |
|
259 if self.__lastchip is not None: |
|
260 color = self.__canvas.itemcget(self.__lastchip, 'fill') |
|
261 self.__canvas.itemconfigure(self.__lastchip, outline=color) |
|
262 self.__lastchip = chip |
|
263 # get the arrow's text |
|
264 coloraxis = rgbtuple[self.__axis] |
|
265 if self.__hexp.get(): |
|
266 # hex |
|
267 text = hex(coloraxis) |
|
268 else: |
|
269 # decimal |
|
270 text = repr(coloraxis) |
|
271 # move the arrow, and set its text |
|
272 if coloraxis <= 128: |
|
273 # use the left arrow |
|
274 self.__leftarrow.set_text(text) |
|
275 self.__leftarrow.move_to(self.__arrow_x(chip-1)) |
|
276 self.__rightarrow.move_to(-100) |
|
277 else: |
|
278 # use the right arrow |
|
279 self.__rightarrow.set_text(text) |
|
280 self.__rightarrow.move_to(self.__arrow_x(chip-1)) |
|
281 self.__leftarrow.move_to(-100) |
|
282 # and set the chip's outline |
|
283 brightness = ColorDB.triplet_to_brightness(rgbtuple) |
|
284 if brightness <= 128: |
|
285 outline = 'white' |
|
286 else: |
|
287 outline = 'black' |
|
288 self.__canvas.itemconfigure(chip, outline=outline) |
|
289 |
|
290 |
|
291 def update_yourself(self, red, green, blue): |
|
292 assert self.__generator |
|
293 i = 1 |
|
294 chip = 0 |
|
295 chips = self.__chips = [] |
|
296 tk = self.__canvas.tk |
|
297 # get the red, green, and blue components for all chips |
|
298 for t in self.__generator(self.__numchips, red, green, blue): |
|
299 rrggbb = ColorDB.triplet_to_rrggbb(t) |
|
300 chips.append(rrggbb) |
|
301 tred, tgreen, tblue = t |
|
302 if tred <= red and tgreen <= green and tblue <= blue: |
|
303 chip = i |
|
304 i = i + 1 |
|
305 # call the raw tcl script |
|
306 colors = SPACE.join(chips) |
|
307 tk.eval('setcolor %s {%s}' % (self.__canvas._w, colors)) |
|
308 # move the arrows around |
|
309 self.__trackarrow(chip, (red, green, blue)) |
|
310 |
|
311 def set(self, label, generator): |
|
312 self.__canvas.itemconfigure(self.__label, text=label) |
|
313 self.__generator = generator |
|
314 |
|
315 |
|
316 class StripViewer: |
|
317 def __init__(self, switchboard, master=None): |
|
318 self.__sb = switchboard |
|
319 optiondb = switchboard.optiondb() |
|
320 # create a frame inside the master. |
|
321 frame = Frame(master, relief=RAISED, borderwidth=1) |
|
322 frame.grid(row=1, column=0, columnspan=2, sticky='NSEW') |
|
323 # create the options to be used later |
|
324 uwd = self.__uwdvar = BooleanVar() |
|
325 uwd.set(optiondb.get('UPWHILEDRAG', 0)) |
|
326 hexp = self.__hexpvar = BooleanVar() |
|
327 hexp.set(optiondb.get('HEXSTRIP', 0)) |
|
328 # create the red, green, blue strips inside their own frame |
|
329 frame1 = Frame(frame) |
|
330 frame1.pack(expand=YES, fill=BOTH) |
|
331 self.__reds = StripWidget(switchboard, frame1, |
|
332 generator=constant_cyan_generator, |
|
333 axis=0, |
|
334 label='Red Variations', |
|
335 uwdvar=uwd, hexvar=hexp) |
|
336 |
|
337 self.__greens = StripWidget(switchboard, frame1, |
|
338 generator=constant_magenta_generator, |
|
339 axis=1, |
|
340 label='Green Variations', |
|
341 uwdvar=uwd, hexvar=hexp) |
|
342 |
|
343 self.__blues = StripWidget(switchboard, frame1, |
|
344 generator=constant_yellow_generator, |
|
345 axis=2, |
|
346 label='Blue Variations', |
|
347 uwdvar=uwd, hexvar=hexp) |
|
348 |
|
349 # create a frame to contain the controls |
|
350 frame2 = Frame(frame) |
|
351 frame2.pack(expand=YES, fill=BOTH) |
|
352 frame2.columnconfigure(0, weight=20) |
|
353 frame2.columnconfigure(2, weight=20) |
|
354 |
|
355 padx = 8 |
|
356 |
|
357 # create the black button |
|
358 blackbtn = Button(frame2, |
|
359 text='Black', |
|
360 command=self.__toblack) |
|
361 blackbtn.grid(row=0, column=0, rowspan=2, sticky=W, padx=padx) |
|
362 |
|
363 # create the controls |
|
364 uwdbtn = Checkbutton(frame2, |
|
365 text='Update while dragging', |
|
366 variable=uwd) |
|
367 uwdbtn.grid(row=0, column=1, sticky=W) |
|
368 hexbtn = Checkbutton(frame2, |
|
369 text='Hexadecimal', |
|
370 variable=hexp, |
|
371 command=self.__togglehex) |
|
372 hexbtn.grid(row=1, column=1, sticky=W) |
|
373 |
|
374 # XXX: ignore this feature for now; it doesn't work quite right yet |
|
375 |
|
376 ## gentypevar = self.__gentypevar = IntVar() |
|
377 ## self.__variations = Radiobutton(frame, |
|
378 ## text='Variations', |
|
379 ## variable=gentypevar, |
|
380 ## value=0, |
|
381 ## command=self.__togglegentype) |
|
382 ## self.__variations.grid(row=0, column=1, sticky=W) |
|
383 ## self.__constants = Radiobutton(frame, |
|
384 ## text='Constants', |
|
385 ## variable=gentypevar, |
|
386 ## value=1, |
|
387 ## command=self.__togglegentype) |
|
388 ## self.__constants.grid(row=1, column=1, sticky=W) |
|
389 |
|
390 # create the white button |
|
391 whitebtn = Button(frame2, |
|
392 text='White', |
|
393 command=self.__towhite) |
|
394 whitebtn.grid(row=0, column=2, rowspan=2, sticky=E, padx=padx) |
|
395 |
|
396 def update_yourself(self, red, green, blue): |
|
397 self.__reds.update_yourself(red, green, blue) |
|
398 self.__greens.update_yourself(red, green, blue) |
|
399 self.__blues.update_yourself(red, green, blue) |
|
400 |
|
401 def __togglehex(self, event=None): |
|
402 red, green, blue = self.__sb.current_rgb() |
|
403 self.update_yourself(red, green, blue) |
|
404 |
|
405 ## def __togglegentype(self, event=None): |
|
406 ## which = self.__gentypevar.get() |
|
407 ## if which == 0: |
|
408 ## self.__reds.set(label='Red Variations', |
|
409 ## generator=constant_cyan_generator) |
|
410 ## self.__greens.set(label='Green Variations', |
|
411 ## generator=constant_magenta_generator) |
|
412 ## self.__blues.set(label='Blue Variations', |
|
413 ## generator=constant_yellow_generator) |
|
414 ## elif which == 1: |
|
415 ## self.__reds.set(label='Red Constant', |
|
416 ## generator=constant_red_generator) |
|
417 ## self.__greens.set(label='Green Constant', |
|
418 ## generator=constant_green_generator) |
|
419 ## self.__blues.set(label='Blue Constant', |
|
420 ## generator=constant_blue_generator) |
|
421 ## else: |
|
422 ## assert 0 |
|
423 ## self.__sb.update_views_current() |
|
424 |
|
425 def __toblack(self, event=None): |
|
426 self.__sb.update_views(0, 0, 0) |
|
427 |
|
428 def __towhite(self, event=None): |
|
429 self.__sb.update_views(255, 255, 255) |
|
430 |
|
431 def save_options(self, optiondb): |
|
432 optiondb['UPWHILEDRAG'] = self.__uwdvar.get() |
|
433 optiondb['HEXSTRIP'] = self.__hexpvar.get() |