StripViewer.py 14.3 KB
Newer Older
Barry Warsaw's avatar
Barry Warsaw committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
"""Strip viewer and related widgets.

The classes in this file implement the StripViewer shown in the top two thirds 
of the main Pynche window.  It consists of three StripWidgets which display
the variations in red, green, and blue respectively of the currently selected
r/g/b color value.

Each StripWidget shows the color variations that are reachable by varying an
axis of the currently selected color.  So for example, if the color is

  (R,G,B)=(127,163,196)

then the Red variations show colors from (0,163,196) to (255,163,196), the
Green variations show colors from (127,0,196) to (127,255,196), and the Blue
variations show colors from (127,163,0) to (127,163,255).

The selected color is always visible in all three StripWidgets, and in fact
each StripWidget highlights the selected color, and has an arrow pointing to
the selected chip, which includes the value along that particular axis.

Clicking on any chip in any StripWidget selects that color, and updates all
arrows and other windows.  By toggling on Update while dragging, Pynche will
select the color under the cursor while you drag it, but be forewarned that
this can be slow.
"""

Barry Warsaw's avatar
Barry Warsaw committed
27 28 29
from Tkinter import *
import ColorDB

30 31 32 33 34 35 36 37 38 39 40 41 42
# Load this script into the Tcl interpreter and call it in
# StripWidget.set_color().  This is about as fast as it can be with the
# current _tkinter.c interface, which doesn't support Tcl Objects.
TCLPROC = '''\
proc setcolor {canv colors} {
    set i 1
    foreach c $colors {
        $canv itemconfigure $i -fill $c -outline $c
	incr i
    }
}
'''

43 44 45 46 47
# Tcl event types
BTNDOWN = 4
BTNUP = 5
BTNDRAG = 6

48
SPACE = ' '
49

50 51


52 53 54 55 56 57 58 59 60 61
def constant(numchips):
    step = 255.0 / (numchips - 1)
    start = 0.0
    seq = []
    while numchips > 0:
	seq.append(int(start))
	start = start + step
	numchips = numchips - 1
    return seq

62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
# red variations, green+blue = cyan constant
def constant_red_generator(numchips, red, green, blue):
    seq = constant(numchips)
    return map(None, [red] * numchips, seq, seq)

# green variations, red+blue = magenta constant
def constant_green_generator(numchips, red, green, blue):
    seq = constant(numchips)
    return map(None, seq, [green] * numchips, seq)

# blue variations, red+green = yellow constant
def constant_blue_generator(numchips, red, green, blue):
    seq = constant(numchips)
    return map(None, seq, seq, [blue] * numchips)

77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
# red variations, green+blue = cyan constant
def constant_cyan_generator(numchips, red, green, blue):
    seq = constant(numchips)
    return map(None, seq, [green] * numchips, [blue] * numchips)

# green variations, red+blue = magenta constant
def constant_magenta_generator(numchips, red, green, blue):
    seq = constant(numchips)
    return map(None, [red] * numchips, seq, [blue] * numchips)

# blue variations, red+green = yellow constant
def constant_yellow_generator(numchips, red, green, blue):
    seq = constant(numchips)
    return map(None, [red] * numchips, [green] * numchips, seq)



Barry Warsaw's avatar
Barry Warsaw committed
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
class LeftArrow:
    _ARROWWIDTH = 30
    _ARROWHEIGHT = 15
    _YOFFSET = 13
    _TEXTYOFFSET = 1
    _TAG = ('leftarrow',)

    def __init__(self, canvas, x):
	self._canvas = canvas
	self.__arrow, self.__text = self._create(x)
	self.move_to(x)

    def _create(self, x):
	arrow = self._canvas.create_line(
	    x, self._ARROWHEIGHT + self._YOFFSET,
	    x, self._YOFFSET,
	    x + self._ARROWWIDTH, self._YOFFSET,
	    arrow='first',
	    width=3.0,
	    tags=self._TAG)
	text = self._canvas.create_text(
	    x + self._ARROWWIDTH + 13,
	    self._ARROWHEIGHT - self._TEXTYOFFSET,
117
	    tags=self._TAG,
Barry Warsaw's avatar
Barry Warsaw committed
118 119 120 121 122 123 124 125 126 127 128 129
	    text='128')
	return arrow, text

    def _x(self):
	coords = self._canvas.coords(self._TAG)
	assert coords
	return coords[0]

    def move_to(self, x):
	deltax = x - self._x()
	self._canvas.move(self._TAG, deltax, 0)

130 131 132
    def set_text(self, text):
	self._canvas.itemconfigure(self.__text, text=text)

Barry Warsaw's avatar
Barry Warsaw committed
133 134 135 136 137 138 139 140 141 142 143 144 145

class RightArrow(LeftArrow):
    _TAG = ('rightarrow',)

    def _create(self, x):
	arrow = self._canvas.create_line(
	    x, self._YOFFSET,
	    x + self._ARROWWIDTH, self._YOFFSET,
	    x + self._ARROWWIDTH, self._ARROWHEIGHT + self._YOFFSET,
	    arrow='last',
	    width=3.0,
	    tags=self._TAG)
	text = self._canvas.create_text(
146
	    x - self._ARROWWIDTH + 15,		  # BAW: kludge
Barry Warsaw's avatar
Barry Warsaw committed
147
	    self._ARROWHEIGHT - self._TEXTYOFFSET,
148
            justify=RIGHT,
149 150
	    text='128',
	    tags=self._TAG)
Barry Warsaw's avatar
Barry Warsaw committed
151 152 153 154 155
	return arrow, text

    def _x(self):
	coords = self._canvas.bbox(self._TAG)
	assert coords
156
	return coords[2] - 6			  # BAW: kludge
Barry Warsaw's avatar
Barry Warsaw committed
157 158 159



Barry Warsaw's avatar
Barry Warsaw committed
160
class StripWidget:
Barry Warsaw's avatar
Barry Warsaw committed
161 162 163 164
    _CHIPHEIGHT = 50
    _CHIPWIDTH = 10
    _NUMCHIPS = 40

Barry Warsaw's avatar
Barry Warsaw committed
165
    def __init__(self, switchboard,
166
                 master     = None,
167 168 169
                 chipwidth  = _CHIPWIDTH,
                 chipheight = _CHIPHEIGHT,
                 numchips   = _NUMCHIPS,
Barry Warsaw's avatar
Barry Warsaw committed
170 171
                 generator  = None,
                 axis       = None,
172
                 label      = '',
173 174
                 uwdvar     = None,
                 hexvar     = None):
Barry Warsaw's avatar
Barry Warsaw committed
175 176 177
        # instance variables
	self.__generator = generator
	self.__axis = axis
178
        self.__numchips = numchips
Barry Warsaw's avatar
Barry Warsaw committed
179
	assert self.__axis in (0, 1, 2)
180
	self.__uwd = uwdvar
181
        self.__hexp = hexvar
Barry Warsaw's avatar
Barry Warsaw committed
182
        # the last chip selected
183
        self.__lastchip = None
Barry Warsaw's avatar
Barry Warsaw committed
184
        self.__sb = switchboard
Barry Warsaw's avatar
Barry Warsaw committed
185
        
Barry Warsaw's avatar
Barry Warsaw committed
186
	canvaswidth = numchips * (chipwidth + 1)
187
	canvasheight = chipheight + 43		  # BAW: Kludge
Barry Warsaw's avatar
Barry Warsaw committed
188

189
	# create the canvas and pack it
190 191 192 193 194 195
	canvas = self.__canvas = Canvas(master,
                                        width=canvaswidth,
                                        height=canvasheight,
##                                        borderwidth=2,
##                                        relief=GROOVE
                                        )
196

Barry Warsaw's avatar
Barry Warsaw committed
197
	canvas.pack()
198
	canvas.bind('<ButtonPress-1>', self.__select_chip)
Barry Warsaw's avatar
Barry Warsaw committed
199
	canvas.bind('<ButtonRelease-1>', self.__select_chip)
200
	canvas.bind('<B1-Motion>', self.__select_chip)
Barry Warsaw's avatar
Barry Warsaw committed
201

202 203
	# Load a proc into the Tcl interpreter.  This is used in the
	# set_color() method to speed up setting the chip colors.
Barry Warsaw's avatar
Barry Warsaw committed
204
	canvas.tk.eval(TCLPROC)
205

Barry Warsaw's avatar
Barry Warsaw committed
206 207 208 209
	# create the color strip
	chips = self.__chips = []
	x = 1
	y = 30
Barry Warsaw's avatar
Barry Warsaw committed
210
	tags = ('chip',)
Barry Warsaw's avatar
Barry Warsaw committed
211
	for c in range(self.__numchips):
Barry Warsaw's avatar
Barry Warsaw committed
212
	    color = 'grey'
213
	    canvas.create_rectangle(
Barry Warsaw's avatar
Barry Warsaw committed
214
		x, y, x+chipwidth, y+chipheight,
Barry Warsaw's avatar
Barry Warsaw committed
215 216
		fill=color, outline=color,
		tags=tags)
Barry Warsaw's avatar
Barry Warsaw committed
217
	    x = x + chipwidth + 1		  # for outline
Barry Warsaw's avatar
Barry Warsaw committed
218
	    chips.append(color)
Barry Warsaw's avatar
Barry Warsaw committed
219

Barry Warsaw's avatar
Barry Warsaw committed
220
	# create the strip label
Barry Warsaw's avatar
Barry Warsaw committed
221
	self.__label = canvas.create_text(
222
	    3, y + chipheight + 8,
223
	    text=label,
224 225
	    anchor=W)

Barry Warsaw's avatar
Barry Warsaw committed
226 227
	# create the arrow and text item
	chipx = self.__arrow_x(0)
Barry Warsaw's avatar
Barry Warsaw committed
228
	self.__leftarrow = LeftArrow(canvas, chipx)
Barry Warsaw's avatar
Barry Warsaw committed
229 230

	chipx = self.__arrow_x(len(chips) - 1)
Barry Warsaw's avatar
Barry Warsaw committed
231
	self.__rightarrow = RightArrow(canvas, chipx)
Barry Warsaw's avatar
Barry Warsaw committed
232

233
    def __arrow_x(self, chipnum):
Barry Warsaw's avatar
Barry Warsaw committed
234
	coords = self.__canvas.coords(chipnum+1)
235 236 237 238
	assert coords
	x0, y0, x1, y1 = coords
	return (x1 + x0) / 2.0

239 240
    # Invoked when one of the chips is clicked.  This should just tell the
    # switchboard to set the color on all the output components
241
    def __select_chip(self, event=None):
242 243 244 245 246 247 248 249
        x = event.x
        y = event.y
        canvas = self.__canvas
        chip = canvas.find_overlapping(x, y, x, y)
        if chip and (1 <= chip[0] <= self.__numchips):
            color = self.__chips[chip[0]-1]
            red, green, blue = ColorDB.rrggbb_to_triplet(color)
            etype = int(event.type)
250
            if (etype == BTNUP or self.__uwd.get()):
251 252 253 254 255
                # update everyone
                self.__sb.update_views(red, green, blue)
            else:
                # just track the arrows
                self.__trackarrow(chip[0], (red, green, blue))
256

257 258 259 260 261 262
    def __trackarrow(self, chip, rgbtuple):
        # invert the last chip
        if self.__lastchip is not None:
            color = self.__canvas.itemcget(self.__lastchip, 'fill')
            self.__canvas.itemconfigure(self.__lastchip, outline=color)
        self.__lastchip = chip
Barry Warsaw's avatar
Barry Warsaw committed
263 264
	# get the arrow's text
	coloraxis = rgbtuple[self.__axis]
265 266 267 268 269 270
        if self.__hexp.get():
            # hex
            text = hex(coloraxis)
        else:
            # decimal
            text = repr(coloraxis)
Barry Warsaw's avatar
Barry Warsaw committed
271 272
	# move the arrow, and set it's text
	if coloraxis <= 128:
Barry Warsaw's avatar
Barry Warsaw committed
273
	    # use the left arrow
Barry Warsaw's avatar
Barry Warsaw committed
274 275 276 277
	    self.__leftarrow.set_text(text)
	    self.__leftarrow.move_to(self.__arrow_x(chip-1))
	    self.__rightarrow.move_to(-100)
	else:
Barry Warsaw's avatar
Barry Warsaw committed
278
	    # use the right arrow
Barry Warsaw's avatar
Barry Warsaw committed
279 280 281 282
	    self.__rightarrow.set_text(text)
	    self.__rightarrow.move_to(self.__arrow_x(chip-1))
	    self.__leftarrow.move_to(-100)
	# and set the chip's outline
Barry Warsaw's avatar
Barry Warsaw committed
283
        brightness = ColorDB.triplet_to_brightness(rgbtuple)
284
	if brightness <= 128:
Barry Warsaw's avatar
Barry Warsaw committed
285 286 287 288
	    outline = 'white'
	else:
	    outline = 'black'
	self.__canvas.itemconfigure(chip, outline=outline)
289

290

Barry Warsaw's avatar
Barry Warsaw committed
291
    def update_yourself(self, red, green, blue):
292 293 294 295 296
	assert self.__generator
	i = 1
	chip = 0
	chips = self.__chips = []
	tk = self.__canvas.tk
Barry Warsaw's avatar
Barry Warsaw committed
297
        # get the red, green, and blue components for all chips
298
        for t in self.__generator(self.__numchips, red, green, blue):
299 300 301 302 303 304 305
            rrggbb = ColorDB.triplet_to_rrggbb(t)
            chips.append(rrggbb)
            tred, tgreen, tblue = t
            if tred <= red and tgreen <= green and tblue <= blue:
                chip = i
            i = i + 1
        # call the raw tcl script
306
        colors = SPACE.join(chips)
307 308
        tk.eval('setcolor %s {%s}' % (self.__canvas._w, colors))
        # move the arrows around
Barry Warsaw's avatar
Barry Warsaw committed
309
        self.__trackarrow(chip, (red, green, blue))
310

311 312 313 314
    def set(self, label, generator):
        self.__canvas.itemconfigure(self.__label, text=label)
        self.__generator = generator

315 316

class StripViewer:
317
    def __init__(self, switchboard, master=None):
318
        self.__sb = switchboard
319
        optiondb = switchboard.optiondb()
320 321 322 323
        # create a frame inside the master.
        frame = Frame(master, relief=RAISED, borderwidth=1)
        frame.grid(row=1, column=0, columnspan=2, sticky='NSEW')
        # create the options to be used later
324 325 326 327
        uwd = self.__uwdvar = BooleanVar()
        uwd.set(optiondb.get('UPWHILEDRAG', 0))
        hexp = self.__hexpvar = BooleanVar()
        hexp.set(optiondb.get('HEXSTRIP', 0))
328 329 330 331
        # create the red, green, blue strips inside their own frame
        frame1 = Frame(frame)
        frame1.pack(expand=YES, fill=BOTH)
        self.__reds = StripWidget(switchboard, frame1,
332 333
                                  generator=constant_cyan_generator,
                                  axis=0,
334
                                  label='Red Variations',
335
                                  uwdvar=uwd, hexvar=hexp)
336

337
        self.__greens = StripWidget(switchboard, frame1,
338 339
                                    generator=constant_magenta_generator,
                                    axis=1,
340
                                    label='Green Variations',
341
                                    uwdvar=uwd, hexvar=hexp)
342

343
        self.__blues = StripWidget(switchboard, frame1,
344 345
                                   generator=constant_yellow_generator,
                                   axis=2,
346
                                   label='Blue Variations',
347 348
                                   uwdvar=uwd, hexvar=hexp)

349 350 351 352 353 354 355 356 357 358
        # create a frame to contain the controls
        frame2 = Frame(frame)
        frame2.pack(expand=YES, fill=BOTH)
        frame2.columnconfigure(0, weight=20)
        frame2.columnconfigure(2, weight=20)

        padx = 8

        # create the black button
        blackbtn = Button(frame2,
359
                          text='Black',
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
                          command=self.__toblack)
        blackbtn.grid(row=0, column=0, rowspan=2, sticky=W, padx=padx)

        # create the controls
        uwdbtn = Checkbutton(frame2,
                             text='Update while dragging',
                             variable=uwd)
        uwdbtn.grid(row=0, column=1, sticky=W)
        hexbtn = Checkbutton(frame2,
                             text='Hexadecimal',
                             variable=hexp,
                             command=self.__togglehex)
        hexbtn.grid(row=1, column=1, sticky=W)

        # XXX: ignore this feature for now; it doesn't work quite right yet
        
##        gentypevar = self.__gentypevar = IntVar()
##        self.__variations = Radiobutton(frame,
##                                        text='Variations',
##                                        variable=gentypevar,
##                                        value=0,
##                                        command=self.__togglegentype)
##        self.__variations.grid(row=0, column=1, sticky=W)
##        self.__constants = Radiobutton(frame,
##                                       text='Constants',
##                                       variable=gentypevar,
##                                       value=1,
##                                       command=self.__togglegentype)
##        self.__constants.grid(row=1, column=1, sticky=W)

        # create the white button
        whitebtn = Button(frame2,
392
                          text='White',
393 394
                          command=self.__towhite)
        whitebtn.grid(row=0, column=2, rowspan=2, sticky=E, padx=padx)
395

396 397 398 399
    def update_yourself(self, red, green, blue):
        self.__reds.update_yourself(red, green, blue)
        self.__greens.update_yourself(red, green, blue)
        self.__blues.update_yourself(red, green, blue)
400 401 402 403

    def __togglehex(self, event=None):
        red, green, blue = self.__sb.current_rgb()
        self.update_yourself(red, green, blue)
404

405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
##    def __togglegentype(self, event=None):
##        which = self.__gentypevar.get()
##        if which == 0:
##            self.__reds.set(label='Red Variations',
##                            generator=constant_cyan_generator)
##            self.__greens.set(label='Green Variations',
##                              generator=constant_magenta_generator)
##            self.__blues.set(label='Blue Variations',
##                             generator=constant_yellow_generator)
##        elif which == 1:
##            self.__reds.set(label='Red Constant',
##                            generator=constant_red_generator)
##            self.__greens.set(label='Green Constant',
##                              generator=constant_green_generator)
##            self.__blues.set(label='Blue Constant',
##                             generator=constant_blue_generator)
##        else:
##            assert 0
##        self.__sb.update_views_current()
424 425 426 427 428 429 430

    def __toblack(self, event=None):
        self.__sb.update_views(0, 0, 0)

    def __towhite(self, event=None):
        self.__sb.update_views(255, 255, 255)

431 432 433
    def save_options(self, optiondb):
        optiondb['UPWHILEDRAG'] = self.__uwdvar.get()
        optiondb['HEXSTRIP'] = self.__hexpvar.get()