StripViewer.py 15.1 KB
Newer Older
Barry Warsaw's avatar
Barry Warsaw committed
1 2
"""Strip viewer and related widgets.

3
The classes in this file implement the StripViewer shown in the top two thirds
Barry Warsaw's avatar
Barry Warsaw committed
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
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.
"""

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

30 31 32 33 34 35 36 37
# 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
38
        incr i
39 40 41 42
    }
}
'''

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

48
SPACE = ' '
49

50 51

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

62 63 64
# red variations, green+blue = cyan constant
def constant_red_generator(numchips, red, green, blue):
    seq = constant(numchips)
65
    return list(zip([red] * numchips, seq, seq))
66 67 68 69

# green variations, red+blue = magenta constant
def constant_green_generator(numchips, red, green, blue):
    seq = constant(numchips)
70
    return list(zip(seq, [green] * numchips, seq))
71 72 73 74

# blue variations, red+green = yellow constant
def constant_blue_generator(numchips, red, green, blue):
    seq = constant(numchips)
75
    return list(zip(seq, seq, [blue] * numchips))
76

77 78 79
# red variations, green+blue = cyan constant
def constant_cyan_generator(numchips, red, green, blue):
    seq = constant(numchips)
80
    return list(zip(seq, [green] * numchips, [blue] * numchips))
81 82 83 84

# green variations, red+blue = magenta constant
def constant_magenta_generator(numchips, red, green, blue):
    seq = constant(numchips)
85
    return list(zip([red] * numchips, seq, [blue] * numchips))
86 87 88 89

# blue variations, red+green = yellow constant
def constant_yellow_generator(numchips, red, green, blue):
    seq = constant(numchips)
90
    return list(zip([red] * numchips, [green] * numchips, seq))
91 92 93



Barry Warsaw's avatar
Barry Warsaw committed
94 95 96 97 98 99 100 101
class LeftArrow:
    _ARROWWIDTH = 30
    _ARROWHEIGHT = 15
    _YOFFSET = 13
    _TEXTYOFFSET = 1
    _TAG = ('leftarrow',)

    def __init__(self, canvas, x):
102 103 104
        self._canvas = canvas
        self.__arrow, self.__text = self._create(x)
        self.move_to(x)
Barry Warsaw's avatar
Barry Warsaw committed
105 106

    def _create(self, x):
107 108 109 110 111 112 113 114 115 116 117 118 119
        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,
            tags=self._TAG,
            text='128')
        return arrow, text
Barry Warsaw's avatar
Barry Warsaw committed
120 121

    def _x(self):
122
        coords = list(self._canvas.coords(self._TAG))
123 124
        assert coords
        return coords[0]
Barry Warsaw's avatar
Barry Warsaw committed
125 126

    def move_to(self, x):
127 128
        deltax = x - self._x()
        self._canvas.move(self._TAG, deltax, 0)
Barry Warsaw's avatar
Barry Warsaw committed
129

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

Barry Warsaw's avatar
Barry Warsaw committed
133 134 135 136 137

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

    def _create(self, x):
138 139 140 141 142 143 144 145 146 147
        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(
            x - self._ARROWWIDTH + 15,            # BAW: kludge
            self._ARROWHEIGHT - self._TEXTYOFFSET,
148
            justify=RIGHT,
149 150 151
            text='128',
            tags=self._TAG)
        return arrow, text
Barry Warsaw's avatar
Barry Warsaw committed
152 153

    def _x(self):
154
        coords = list(self._canvas.coords(self._TAG))
155
        assert coords
Barry Warsaw's avatar
Barry Warsaw committed
156
        return coords[0] + self._ARROWWIDTH
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
        # instance variables
176 177
        self.__generator = generator
        self.__axis = axis
178
        self.__numchips = numchips
179 180
        assert self.__axis in (0, 1, 2)
        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
185

186 187
        canvaswidth = numchips * (chipwidth + 1)
        canvasheight = chipheight + 43            # BAW: Kludge
Barry Warsaw's avatar
Barry Warsaw committed
188

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

197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
        canvas.pack()
        canvas.bind('<ButtonPress-1>', self.__select_chip)
        canvas.bind('<ButtonRelease-1>', self.__select_chip)
        canvas.bind('<B1-Motion>', self.__select_chip)

        # Load a proc into the Tcl interpreter.  This is used in the
        # set_color() method to speed up setting the chip colors.
        canvas.tk.eval(TCLPROC)

        # create the color strip
        chips = self.__chips = []
        x = 1
        y = 30
        tags = ('chip',)
        for c in range(self.__numchips):
            color = 'grey'
            canvas.create_rectangle(
                x, y, x+chipwidth, y+chipheight,
                fill=color, outline=color,
                tags=tags)
            x = x + chipwidth + 1                 # for outline
            chips.append(color)

        # create the strip label
        self.__label = canvas.create_text(
            3, y + chipheight + 8,
            text=label,
            anchor=W)

        # create the arrow and text item
        chipx = self.__arrow_x(0)
        self.__leftarrow = LeftArrow(canvas, chipx)

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

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

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
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)
271
        # move the arrow, and set its text
272 273 274 275 276 277 278 279 280 281 282
        if coloraxis <= 128:
            # use the left arrow
            self.__leftarrow.set_text(text)
            self.__leftarrow.move_to(self.__arrow_x(chip-1))
            self.__rightarrow.move_to(-100)
        else:
            # use the right arrow
            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 285 286 287 288
        if brightness <= 128:
            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
                          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
375

376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
##        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()