Back to index

python3.2  3.2.2
StripViewer.py
Go to the documentation of this file.
00001 """Strip viewer and related widgets.
00002 
00003 The classes in this file implement the StripViewer shown in the top two thirds
00004 of the main Pynche window.  It consists of three StripWidgets which display
00005 the variations in red, green, and blue respectively of the currently selected
00006 r/g/b color value.
00007 
00008 Each StripWidget shows the color variations that are reachable by varying an
00009 axis of the currently selected color.  So for example, if the color is
00010 
00011   (R,G,B)=(127,163,196)
00012 
00013 then the Red variations show colors from (0,163,196) to (255,163,196), the
00014 Green variations show colors from (127,0,196) to (127,255,196), and the Blue
00015 variations show colors from (127,163,0) to (127,163,255).
00016 
00017 The selected color is always visible in all three StripWidgets, and in fact
00018 each StripWidget highlights the selected color, and has an arrow pointing to
00019 the selected chip, which includes the value along that particular axis.
00020 
00021 Clicking on any chip in any StripWidget selects that color, and updates all
00022 arrows and other windows.  By toggling on Update while dragging, Pynche will
00023 select the color under the cursor while you drag it, but be forewarned that
00024 this can be slow.
00025 """
00026 
00027 from tkinter import *
00028 import ColorDB
00029 
00030 # Load this script into the Tcl interpreter and call it in
00031 # StripWidget.set_color().  This is about as fast as it can be with the
00032 # current _tkinter.c interface, which doesn't support Tcl Objects.
00033 TCLPROC = '''\
00034 proc setcolor {canv colors} {
00035     set i 1
00036     foreach c $colors {
00037         $canv itemconfigure $i -fill $c -outline $c
00038         incr i
00039     }
00040 }
00041 '''
00042 
00043 # Tcl event types
00044 BTNDOWN = 4
00045 BTNUP = 5
00046 BTNDRAG = 6
00047 
00048 SPACE = ' '
00049 
00050 
00051 
00052 def constant(numchips):
00053     step = 255.0 / (numchips - 1)
00054     start = 0.0
00055     seq = []
00056     while numchips > 0:
00057         seq.append(int(start))
00058         start = start + step
00059         numchips = numchips - 1
00060     return seq
00061 
00062 # red variations, green+blue = cyan constant
00063 def constant_red_generator(numchips, red, green, blue):
00064     seq = constant(numchips)
00065     return list(zip([red] * numchips, seq, seq))
00066 
00067 # green variations, red+blue = magenta constant
00068 def constant_green_generator(numchips, red, green, blue):
00069     seq = constant(numchips)
00070     return list(zip(seq, [green] * numchips, seq))
00071 
00072 # blue variations, red+green = yellow constant
00073 def constant_blue_generator(numchips, red, green, blue):
00074     seq = constant(numchips)
00075     return list(zip(seq, seq, [blue] * numchips))
00076 
00077 # red variations, green+blue = cyan constant
00078 def constant_cyan_generator(numchips, red, green, blue):
00079     seq = constant(numchips)
00080     return list(zip(seq, [green] * numchips, [blue] * numchips))
00081 
00082 # green variations, red+blue = magenta constant
00083 def constant_magenta_generator(numchips, red, green, blue):
00084     seq = constant(numchips)
00085     return list(zip([red] * numchips, seq, [blue] * numchips))
00086 
00087 # blue variations, red+green = yellow constant
00088 def constant_yellow_generator(numchips, red, green, blue):
00089     seq = constant(numchips)
00090     return list(zip([red] * numchips, [green] * numchips, seq))
00091 
00092 
00093 
00094 class LeftArrow:
00095     _ARROWWIDTH = 30
00096     _ARROWHEIGHT = 15
00097     _YOFFSET = 13
00098     _TEXTYOFFSET = 1
00099     _TAG = ('leftarrow',)
00100 
00101     def __init__(self, canvas, x):
00102         self._canvas = canvas
00103         self.__arrow, self.__text = self._create(x)
00104         self.move_to(x)
00105 
00106     def _create(self, x):
00107         arrow = self._canvas.create_line(
00108             x, self._ARROWHEIGHT + self._YOFFSET,
00109             x, self._YOFFSET,
00110             x + self._ARROWWIDTH, self._YOFFSET,
00111             arrow='first',
00112             width=3.0,
00113             tags=self._TAG)
00114         text = self._canvas.create_text(
00115             x + self._ARROWWIDTH + 13,
00116             self._ARROWHEIGHT - self._TEXTYOFFSET,
00117             tags=self._TAG,
00118             text='128')
00119         return arrow, text
00120 
00121     def _x(self):
00122         coords = list(self._canvas.coords(self._TAG))
00123         assert coords
00124         return coords[0]
00125 
00126     def move_to(self, x):
00127         deltax = x - self._x()
00128         self._canvas.move(self._TAG, deltax, 0)
00129 
00130     def set_text(self, text):
00131         self._canvas.itemconfigure(self.__text, text=text)
00132 
00133 
00134 class RightArrow(LeftArrow):
00135     _TAG = ('rightarrow',)
00136 
00137     def _create(self, x):
00138         arrow = self._canvas.create_line(
00139             x, self._YOFFSET,
00140             x + self._ARROWWIDTH, self._YOFFSET,
00141             x + self._ARROWWIDTH, self._ARROWHEIGHT + self._YOFFSET,
00142             arrow='last',
00143             width=3.0,
00144             tags=self._TAG)
00145         text = self._canvas.create_text(
00146             x - self._ARROWWIDTH + 15,            # BAW: kludge
00147             self._ARROWHEIGHT - self._TEXTYOFFSET,
00148             justify=RIGHT,
00149             text='128',
00150             tags=self._TAG)
00151         return arrow, text
00152 
00153     def _x(self):
00154         coords = list(self._canvas.coords(self._TAG))
00155         assert coords
00156         return coords[0] + self._ARROWWIDTH
00157 
00158 
00159 
00160 class StripWidget:
00161     _CHIPHEIGHT = 50
00162     _CHIPWIDTH = 10
00163     _NUMCHIPS = 40
00164 
00165     def __init__(self, switchboard,
00166                  master     = None,
00167                  chipwidth  = _CHIPWIDTH,
00168                  chipheight = _CHIPHEIGHT,
00169                  numchips   = _NUMCHIPS,
00170                  generator  = None,
00171                  axis       = None,
00172                  label      = '',
00173                  uwdvar     = None,
00174                  hexvar     = None):
00175         # instance variables
00176         self.__generator = generator
00177         self.__axis = axis
00178         self.__numchips = numchips
00179         assert self.__axis in (0, 1, 2)
00180         self.__uwd = uwdvar
00181         self.__hexp = hexvar
00182         # the last chip selected
00183         self.__lastchip = None
00184         self.__sb = switchboard
00185 
00186         canvaswidth = numchips * (chipwidth + 1)
00187         canvasheight = chipheight + 43            # BAW: Kludge
00188 
00189         # create the canvas and pack it
00190         canvas = self.__canvas = Canvas(master,
00191                                         width=canvaswidth,
00192                                         height=canvasheight,
00193 ##                                        borderwidth=2,
00194 ##                                        relief=GROOVE
00195                                         )
00196 
00197         canvas.pack()
00198         canvas.bind('<ButtonPress-1>', self.__select_chip)
00199         canvas.bind('<ButtonRelease-1>', self.__select_chip)
00200         canvas.bind('<B1-Motion>', self.__select_chip)
00201 
00202         # Load a proc into the Tcl interpreter.  This is used in the
00203         # set_color() method to speed up setting the chip colors.
00204         canvas.tk.eval(TCLPROC)
00205 
00206         # create the color strip
00207         chips = self.__chips = []
00208         x = 1
00209         y = 30
00210         tags = ('chip',)
00211         for c in range(self.__numchips):
00212             color = 'grey'
00213             canvas.create_rectangle(
00214                 x, y, x+chipwidth, y+chipheight,
00215                 fill=color, outline=color,
00216                 tags=tags)
00217             x = x + chipwidth + 1                 # for outline
00218             chips.append(color)
00219 
00220         # create the strip label
00221         self.__label = canvas.create_text(
00222             3, y + chipheight + 8,
00223             text=label,
00224             anchor=W)
00225 
00226         # create the arrow and text item
00227         chipx = self.__arrow_x(0)
00228         self.__leftarrow = LeftArrow(canvas, chipx)
00229 
00230         chipx = self.__arrow_x(len(chips) - 1)
00231         self.__rightarrow = RightArrow(canvas, chipx)
00232 
00233     def __arrow_x(self, chipnum):
00234         coords = self.__canvas.coords(chipnum+1)
00235         assert coords
00236         x0, y0, x1, y1 = coords
00237         return (x1 + x0) / 2.0
00238 
00239     # Invoked when one of the chips is clicked.  This should just tell the
00240     # switchboard to set the color on all the output components
00241     def __select_chip(self, event=None):
00242         x = event.x
00243         y = event.y
00244         canvas = self.__canvas
00245         chip = canvas.find_overlapping(x, y, x, y)
00246         if chip and (1 <= chip[0] <= self.__numchips):
00247             color = self.__chips[chip[0]-1]
00248             red, green, blue = ColorDB.rrggbb_to_triplet(color)
00249             etype = int(event.type)
00250             if (etype == BTNUP or self.__uwd.get()):
00251                 # update everyone
00252                 self.__sb.update_views(red, green, blue)
00253             else:
00254                 # just track the arrows
00255                 self.__trackarrow(chip[0], (red, green, blue))
00256 
00257     def __trackarrow(self, chip, rgbtuple):
00258         # invert the last chip
00259         if self.__lastchip is not None:
00260             color = self.__canvas.itemcget(self.__lastchip, 'fill')
00261             self.__canvas.itemconfigure(self.__lastchip, outline=color)
00262         self.__lastchip = chip
00263         # get the arrow's text
00264         coloraxis = rgbtuple[self.__axis]
00265         if self.__hexp.get():
00266             # hex
00267             text = hex(coloraxis)
00268         else:
00269             # decimal
00270             text = repr(coloraxis)
00271         # move the arrow, and set its text
00272         if coloraxis <= 128:
00273             # use the left arrow
00274             self.__leftarrow.set_text(text)
00275             self.__leftarrow.move_to(self.__arrow_x(chip-1))
00276             self.__rightarrow.move_to(-100)
00277         else:
00278             # use the right arrow
00279             self.__rightarrow.set_text(text)
00280             self.__rightarrow.move_to(self.__arrow_x(chip-1))
00281             self.__leftarrow.move_to(-100)
00282         # and set the chip's outline
00283         brightness = ColorDB.triplet_to_brightness(rgbtuple)
00284         if brightness <= 128:
00285             outline = 'white'
00286         else:
00287             outline = 'black'
00288         self.__canvas.itemconfigure(chip, outline=outline)
00289 
00290 
00291     def update_yourself(self, red, green, blue):
00292         assert self.__generator
00293         i = 1
00294         chip = 0
00295         chips = self.__chips = []
00296         tk = self.__canvas.tk
00297         # get the red, green, and blue components for all chips
00298         for t in self.__generator(self.__numchips, red, green, blue):
00299             rrggbb = ColorDB.triplet_to_rrggbb(t)
00300             chips.append(rrggbb)
00301             tred, tgreen, tblue = t
00302             if tred <= red and tgreen <= green and tblue <= blue:
00303                 chip = i
00304             i = i + 1
00305         # call the raw tcl script
00306         colors = SPACE.join(chips)
00307         tk.eval('setcolor %s {%s}' % (self.__canvas._w, colors))
00308         # move the arrows around
00309         self.__trackarrow(chip, (red, green, blue))
00310 
00311     def set(self, label, generator):
00312         self.__canvas.itemconfigure(self.__label, text=label)
00313         self.__generator = generator
00314 
00315 
00316 class StripViewer:
00317     def __init__(self, switchboard, master=None):
00318         self.__sb = switchboard
00319         optiondb = switchboard.optiondb()
00320         # create a frame inside the master.
00321         frame = Frame(master, relief=RAISED, borderwidth=1)
00322         frame.grid(row=1, column=0, columnspan=2, sticky='NSEW')
00323         # create the options to be used later
00324         uwd = self.__uwdvar = BooleanVar()
00325         uwd.set(optiondb.get('UPWHILEDRAG', 0))
00326         hexp = self.__hexpvar = BooleanVar()
00327         hexp.set(optiondb.get('HEXSTRIP', 0))
00328         # create the red, green, blue strips inside their own frame
00329         frame1 = Frame(frame)
00330         frame1.pack(expand=YES, fill=BOTH)
00331         self.__reds = StripWidget(switchboard, frame1,
00332                                   generator=constant_cyan_generator,
00333                                   axis=0,
00334                                   label='Red Variations',
00335                                   uwdvar=uwd, hexvar=hexp)
00336 
00337         self.__greens = StripWidget(switchboard, frame1,
00338                                     generator=constant_magenta_generator,
00339                                     axis=1,
00340                                     label='Green Variations',
00341                                     uwdvar=uwd, hexvar=hexp)
00342 
00343         self.__blues = StripWidget(switchboard, frame1,
00344                                    generator=constant_yellow_generator,
00345                                    axis=2,
00346                                    label='Blue Variations',
00347                                    uwdvar=uwd, hexvar=hexp)
00348 
00349         # create a frame to contain the controls
00350         frame2 = Frame(frame)
00351         frame2.pack(expand=YES, fill=BOTH)
00352         frame2.columnconfigure(0, weight=20)
00353         frame2.columnconfigure(2, weight=20)
00354 
00355         padx = 8
00356 
00357         # create the black button
00358         blackbtn = Button(frame2,
00359                           text='Black',
00360                           command=self.__toblack)
00361         blackbtn.grid(row=0, column=0, rowspan=2, sticky=W, padx=padx)
00362 
00363         # create the controls
00364         uwdbtn = Checkbutton(frame2,
00365                              text='Update while dragging',
00366                              variable=uwd)
00367         uwdbtn.grid(row=0, column=1, sticky=W)
00368         hexbtn = Checkbutton(frame2,
00369                              text='Hexadecimal',
00370                              variable=hexp,
00371                              command=self.__togglehex)
00372         hexbtn.grid(row=1, column=1, sticky=W)
00373 
00374         # XXX: ignore this feature for now; it doesn't work quite right yet
00375 
00376 ##        gentypevar = self.__gentypevar = IntVar()
00377 ##        self.__variations = Radiobutton(frame,
00378 ##                                        text='Variations',
00379 ##                                        variable=gentypevar,
00380 ##                                        value=0,
00381 ##                                        command=self.__togglegentype)
00382 ##        self.__variations.grid(row=0, column=1, sticky=W)
00383 ##        self.__constants = Radiobutton(frame,
00384 ##                                       text='Constants',
00385 ##                                       variable=gentypevar,
00386 ##                                       value=1,
00387 ##                                       command=self.__togglegentype)
00388 ##        self.__constants.grid(row=1, column=1, sticky=W)
00389 
00390         # create the white button
00391         whitebtn = Button(frame2,
00392                           text='White',
00393                           command=self.__towhite)
00394         whitebtn.grid(row=0, column=2, rowspan=2, sticky=E, padx=padx)
00395 
00396     def update_yourself(self, red, green, blue):
00397         self.__reds.update_yourself(red, green, blue)
00398         self.__greens.update_yourself(red, green, blue)
00399         self.__blues.update_yourself(red, green, blue)
00400 
00401     def __togglehex(self, event=None):
00402         red, green, blue = self.__sb.current_rgb()
00403         self.update_yourself(red, green, blue)
00404 
00405 ##    def __togglegentype(self, event=None):
00406 ##        which = self.__gentypevar.get()
00407 ##        if which == 0:
00408 ##            self.__reds.set(label='Red Variations',
00409 ##                            generator=constant_cyan_generator)
00410 ##            self.__greens.set(label='Green Variations',
00411 ##                              generator=constant_magenta_generator)
00412 ##            self.__blues.set(label='Blue Variations',
00413 ##                             generator=constant_yellow_generator)
00414 ##        elif which == 1:
00415 ##            self.__reds.set(label='Red Constant',
00416 ##                            generator=constant_red_generator)
00417 ##            self.__greens.set(label='Green Constant',
00418 ##                              generator=constant_green_generator)
00419 ##            self.__blues.set(label='Blue Constant',
00420 ##                             generator=constant_blue_generator)
00421 ##        else:
00422 ##            assert 0
00423 ##        self.__sb.update_views_current()
00424 
00425     def __toblack(self, event=None):
00426         self.__sb.update_views(0, 0, 0)
00427 
00428     def __towhite(self, event=None):
00429         self.__sb.update_views(255, 255, 255)
00430 
00431     def save_options(self, optiondb):
00432         optiondb['UPWHILEDRAG'] = self.__uwdvar.get()
00433         optiondb['HEXSTRIP'] = self.__hexpvar.get()