| """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. | |
| """ | |
| from Tkinter import * | |
| import ColorDB | |
| # 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 | |
| } | |
| } | |
| ''' | |
| # Tcl event types | |
| BTNDOWN = 4 | |
| BTNUP = 5 | |
| BTNDRAG = 6 | |
| SPACE = ' ' | |
| 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 | |
| # 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) | |
| # 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) | |
| 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, | |
| tags=self._TAG, | |
| 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) | |
| def set_text(self, text): | |
| self._canvas.itemconfigure(self.__text, text=text) | |
| 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( | |
| x - self._ARROWWIDTH + 15, # BAW: kludge | |
| self._ARROWHEIGHT - self._TEXTYOFFSET, | |
| justify=RIGHT, | |
| text='128', | |
| tags=self._TAG) | |
| return arrow, text | |
| def _x(self): | |
| coords = self._canvas.coords(self._TAG) | |
| assert coords | |
| return coords[0] + self._ARROWWIDTH | |
| class StripWidget: | |
| _CHIPHEIGHT = 50 | |
| _CHIPWIDTH = 10 | |
| _NUMCHIPS = 40 | |
| def __init__(self, switchboard, | |
| master = None, | |
| chipwidth = _CHIPWIDTH, | |
| chipheight = _CHIPHEIGHT, | |
| numchips = _NUMCHIPS, | |
| generator = None, | |
| axis = None, | |
| label = '', | |
| uwdvar = None, | |
| hexvar = None): | |
| # instance variables | |
| self.__generator = generator | |
| self.__axis = axis | |
| self.__numchips = numchips | |
| assert self.__axis in (0, 1, 2) | |
| self.__uwd = uwdvar | |
| self.__hexp = hexvar | |
| # the last chip selected | |
| self.__lastchip = None | |
| self.__sb = switchboard | |
| canvaswidth = numchips * (chipwidth + 1) | |
| canvasheight = chipheight + 43 # BAW: Kludge | |
| # create the canvas and pack it | |
| canvas = self.__canvas = Canvas(master, | |
| width=canvaswidth, | |
| height=canvasheight, | |
| ## borderwidth=2, | |
| ## relief=GROOVE | |
| ) | |
| 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) | |
| def __arrow_x(self, chipnum): | |
| coords = self.__canvas.coords(chipnum+1) | |
| assert coords | |
| x0, y0, x1, y1 = coords | |
| return (x1 + x0) / 2.0 | |
| # Invoked when one of the chips is clicked. This should just tell the | |
| # switchboard to set the color on all the output components | |
| def __select_chip(self, event=None): | |
| 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) | |
| if (etype == BTNUP or self.__uwd.get()): | |
| # update everyone | |
| self.__sb.update_views(red, green, blue) | |
| else: | |
| # just track the arrows | |
| self.__trackarrow(chip[0], (red, green, blue)) | |
| 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 | |
| # get the arrow's text | |
| coloraxis = rgbtuple[self.__axis] | |
| if self.__hexp.get(): | |
| # hex | |
| text = hex(coloraxis) | |
| else: | |
| # decimal | |
| text = repr(coloraxis) | |
| # move the arrow, and set its text | |
| 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 | |
| brightness = ColorDB.triplet_to_brightness(rgbtuple) | |
| if brightness <= 128: | |
| outline = 'white' | |
| else: | |
| outline = 'black' | |
| self.__canvas.itemconfigure(chip, outline=outline) | |
| def update_yourself(self, red, green, blue): | |
| assert self.__generator | |
| i = 1 | |
| chip = 0 | |
| chips = self.__chips = [] | |
| tk = self.__canvas.tk | |
| # get the red, green, and blue components for all chips | |
| for t in self.__generator(self.__numchips, red, green, blue): | |
| 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 | |
| colors = SPACE.join(chips) | |
| tk.eval('setcolor %s {%s}' % (self.__canvas._w, colors)) | |
| # move the arrows around | |
| self.__trackarrow(chip, (red, green, blue)) | |
| def set(self, label, generator): | |
| self.__canvas.itemconfigure(self.__label, text=label) | |
| self.__generator = generator | |
| class StripViewer: | |
| def __init__(self, switchboard, master=None): | |
| self.__sb = switchboard | |
| optiondb = switchboard.optiondb() | |
| # 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 | |
| uwd = self.__uwdvar = BooleanVar() | |
| uwd.set(optiondb.get('UPWHILEDRAG', 0)) | |
| hexp = self.__hexpvar = BooleanVar() | |
| hexp.set(optiondb.get('HEXSTRIP', 0)) | |
| # create the red, green, blue strips inside their own frame | |
| frame1 = Frame(frame) | |
| frame1.pack(expand=YES, fill=BOTH) | |
| self.__reds = StripWidget(switchboard, frame1, | |
| generator=constant_cyan_generator, | |
| axis=0, | |
| label='Red Variations', | |
| uwdvar=uwd, hexvar=hexp) | |
| self.__greens = StripWidget(switchboard, frame1, | |
| generator=constant_magenta_generator, | |
| axis=1, | |
| label='Green Variations', | |
| uwdvar=uwd, hexvar=hexp) | |
| self.__blues = StripWidget(switchboard, frame1, | |
| generator=constant_yellow_generator, | |
| axis=2, | |
| label='Blue Variations', | |
| uwdvar=uwd, hexvar=hexp) | |
| # 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, | |
| text='Black', | |
| 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, | |
| text='White', | |
| command=self.__towhite) | |
| whitebtn.grid(row=0, column=2, rowspan=2, sticky=E, padx=padx) | |
| 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) | |
| def __togglehex(self, event=None): | |
| red, green, blue = self.__sb.current_rgb() | |
| self.update_yourself(red, green, blue) | |
| ## 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() | |
| def __toblack(self, event=None): | |
| self.__sb.update_views(0, 0, 0) | |
| def __towhite(self, event=None): | |
| self.__sb.update_views(255, 255, 255) | |
| def save_options(self, optiondb): | |
| optiondb['UPWHILEDRAG'] = self.__uwdvar.get() | |
| optiondb['HEXSTRIP'] = self.__hexpvar.get() |