Issue #7042: Use a better mechanism for testing timers in test_signal.
[python.git] / Tools / pynche / StripViewer.py
blob01bcbf6d0507dd5b2d3b53d99e6d3376cd3a1b41
1 """Strip viewer and related widgets.
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.
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
11 (R,G,B)=(127,163,196)
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).
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.
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 """
27 from Tkinter import *
28 import ColorDB
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
41 '''
43 # Tcl event types
44 BTNDOWN = 4
45 BTNUP = 5
46 BTNDRAG = 6
48 SPACE = ' '
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
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)
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)
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)
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)
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)
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)
94 class LeftArrow:
95 _ARROWWIDTH = 30
96 _ARROWHEIGHT = 15
97 _YOFFSET = 13
98 _TEXTYOFFSET = 1
99 _TAG = ('leftarrow',)
101 def __init__(self, canvas, x):
102 self._canvas = canvas
103 self.__arrow, self.__text = self._create(x)
104 self.move_to(x)
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
121 def _x(self):
122 coords = self._canvas.coords(self._TAG)
123 assert coords
124 return coords[0]
126 def move_to(self, x):
127 deltax = x - self._x()
128 self._canvas.move(self._TAG, deltax, 0)
130 def set_text(self, text):
131 self._canvas.itemconfigure(self.__text, text=text)
134 class RightArrow(LeftArrow):
135 _TAG = ('rightarrow',)
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
153 def _x(self):
154 coords = self._canvas.coords(self._TAG)
155 assert coords
156 return coords[0] + self._ARROWWIDTH
160 class StripWidget:
161 _CHIPHEIGHT = 50
162 _CHIPWIDTH = 10
163 _NUMCHIPS = 40
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
186 canvaswidth = numchips * (chipwidth + 1)
187 canvasheight = chipheight + 43 # BAW: Kludge
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
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)
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)
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)
220 # create the strip label
221 self.__label = canvas.create_text(
222 3, y + chipheight + 8,
223 text=label,
224 anchor=W)
226 # create the arrow and text item
227 chipx = self.__arrow_x(0)
228 self.__leftarrow = LeftArrow(canvas, chipx)
230 chipx = self.__arrow_x(len(chips) - 1)
231 self.__rightarrow = RightArrow(canvas, chipx)
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
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))
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)
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))
311 def set(self, label, generator):
312 self.__canvas.itemconfigure(self.__label, text=label)
313 self.__generator = generator
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)
337 self.__greens = StripWidget(switchboard, frame1,
338 generator=constant_magenta_generator,
339 axis=1,
340 label='Green Variations',
341 uwdvar=uwd, hexvar=hexp)
343 self.__blues = StripWidget(switchboard, frame1,
344 generator=constant_yellow_generator,
345 axis=2,
346 label='Blue Variations',
347 uwdvar=uwd, hexvar=hexp)
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)
355 padx = 8
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)
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)
374 # XXX: ignore this feature for now; it doesn't work quite right yet
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)
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)
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)
401 def __togglehex(self, event=None):
402 red, green, blue = self.__sb.current_rgb()
403 self.update_yourself(red, green, blue)
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()
425 def __toblack(self, event=None):
426 self.__sb.update_views(0, 0, 0)
428 def __towhite(self, event=None):
429 self.__sb.update_views(255, 255, 255)
431 def save_options(self, optiondb):
432 optiondb['UPWHILEDRAG'] = self.__uwdvar.get()
433 optiondb['HEXSTRIP'] = self.__hexpvar.get()