1"""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 27from Tkinter import * 28import 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. 33TCLPROC = '''\ 34proc 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 44BTNDOWN = 4 45BTNUP = 5 46BTNDRAG = 6 47 48SPACE = ' ' 49 50 51 52def 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 63def 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 68def 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 73def 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 78def 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 83def 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 88def constant_yellow_generator(numchips, red, green, blue): 89 seq = constant(numchips) 90 return map(None, [red] * numchips, [green] * numchips, seq) 91 92 93 94class 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 134class 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 160class 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 316class 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() 434