| 1 | """DetailsViewer class. | 
|---|
| 2 |  | 
|---|
| 3 | This class implements a pure input window which allows you to meticulously | 
|---|
| 4 | edit the current color.  You have both mouse control of the color (via the | 
|---|
| 5 | buttons along the bottom row), and there are keyboard bindings for each of the | 
|---|
| 6 | increment/decrement buttons. | 
|---|
| 7 |  | 
|---|
| 8 | The top three check buttons allow you to specify which of the three color | 
|---|
| 9 | variations are tied together when incrementing and decrementing.  Red, green, | 
|---|
| 10 | and blue are self evident.  By tying together red and green, you can modify | 
|---|
| 11 | the yellow level of the color.  By tying together red and blue, you can modify | 
|---|
| 12 | the magenta level of the color.  By tying together green and blue, you can | 
|---|
| 13 | modify the cyan level, and by tying all three together, you can modify the | 
|---|
| 14 | grey level. | 
|---|
| 15 |  | 
|---|
| 16 | The behavior at the boundaries (0 and 255) are defined by the `At boundary' | 
|---|
| 17 | option menu: | 
|---|
| 18 |  | 
|---|
| 19 | Stop | 
|---|
| 20 | When the increment or decrement would send any of the tied variations | 
|---|
| 21 | out of bounds, the entire delta is discarded. | 
|---|
| 22 |  | 
|---|
| 23 | Wrap Around | 
|---|
| 24 | When the increment or decrement would send any of the tied variations | 
|---|
| 25 | out of bounds, the out of bounds variation is wrapped around to the | 
|---|
| 26 | other side.  Thus if red were at 238 and 25 were added to it, red | 
|---|
| 27 | would have the value 7. | 
|---|
| 28 |  | 
|---|
| 29 | Preserve Distance | 
|---|
| 30 | When the increment or decrement would send any of the tied variations | 
|---|
| 31 | out of bounds, all tied variations are wrapped as one, so as to | 
|---|
| 32 | preserve the distance between them.  Thus if green and blue were tied, | 
|---|
| 33 | and green was at 238 while blue was at 223, and an increment of 25 | 
|---|
| 34 | were applied, green would be at 15 and blue would be at 0. | 
|---|
| 35 |  | 
|---|
| 36 | Squash | 
|---|
| 37 | When the increment or decrement would send any of the tied variations | 
|---|
| 38 | out of bounds, the out of bounds variation is set to the ceiling of | 
|---|
| 39 | 255 or floor of 0, as appropriate.  In this way, all tied variations | 
|---|
| 40 | are squashed to one edge or the other. | 
|---|
| 41 |  | 
|---|
| 42 | The following key bindings can be used as accelerators.  Note that Pynche can | 
|---|
| 43 | fall behind if you hold the key down as a key repeat: | 
|---|
| 44 |  | 
|---|
| 45 | Left arrow == -1 | 
|---|
| 46 | Right arrow == +1 | 
|---|
| 47 |  | 
|---|
| 48 | Control + Left == -10 | 
|---|
| 49 | Control + Right == 10 | 
|---|
| 50 |  | 
|---|
| 51 | Shift + Left == -25 | 
|---|
| 52 | Shift + Right == +25 | 
|---|
| 53 | """ | 
|---|
| 54 |  | 
|---|
| 55 | from Tkinter import * | 
|---|
| 56 |  | 
|---|
| 57 | STOP = 'Stop' | 
|---|
| 58 | WRAP = 'Wrap Around' | 
|---|
| 59 | RATIO = 'Preserve Distance' | 
|---|
| 60 | GRAV = 'Squash' | 
|---|
| 61 |  | 
|---|
| 62 | ADDTOVIEW = 'Details Window...' | 
|---|
| 63 |  | 
|---|
| 64 |  | 
|---|
| 65 |  | 
|---|
| 66 | class DetailsViewer: | 
|---|
| 67 | def __init__(self, switchboard, master=None): | 
|---|
| 68 | self.__sb = switchboard | 
|---|
| 69 | optiondb = switchboard.optiondb() | 
|---|
| 70 | self.__red, self.__green, self.__blue = switchboard.current_rgb() | 
|---|
| 71 | # GUI | 
|---|
| 72 | root = self.__root = Toplevel(master, class_='Pynche') | 
|---|
| 73 | root.protocol('WM_DELETE_WINDOW', self.withdraw) | 
|---|
| 74 | root.title('Pynche Details Window') | 
|---|
| 75 | root.iconname('Pynche Details Window') | 
|---|
| 76 | root.bind('<Alt-q>', self.__quit) | 
|---|
| 77 | root.bind('<Alt-Q>', self.__quit) | 
|---|
| 78 | root.bind('<Alt-w>', self.withdraw) | 
|---|
| 79 | root.bind('<Alt-W>', self.withdraw) | 
|---|
| 80 | # accelerators | 
|---|
| 81 | root.bind('<KeyPress-Left>', self.__minus1) | 
|---|
| 82 | root.bind('<KeyPress-Right>', self.__plus1) | 
|---|
| 83 | root.bind('<Control-KeyPress-Left>', self.__minus10) | 
|---|
| 84 | root.bind('<Control-KeyPress-Right>', self.__plus10) | 
|---|
| 85 | root.bind('<Shift-KeyPress-Left>', self.__minus25) | 
|---|
| 86 | root.bind('<Shift-KeyPress-Right>', self.__plus25) | 
|---|
| 87 | # | 
|---|
| 88 | # color ties | 
|---|
| 89 | frame = self.__frame = Frame(root) | 
|---|
| 90 | frame.pack(expand=YES, fill=X) | 
|---|
| 91 | self.__l1 = Label(frame, text='Move Sliders:') | 
|---|
| 92 | self.__l1.grid(row=1, column=0, sticky=E) | 
|---|
| 93 | self.__rvar = IntVar() | 
|---|
| 94 | self.__rvar.set(optiondb.get('RSLIDER', 4)) | 
|---|
| 95 | self.__radio1 = Checkbutton(frame, text='Red', | 
|---|
| 96 | variable=self.__rvar, | 
|---|
| 97 | command=self.__effect, | 
|---|
| 98 | onvalue=4, offvalue=0) | 
|---|
| 99 | self.__radio1.grid(row=1, column=1, sticky=W) | 
|---|
| 100 | self.__gvar = IntVar() | 
|---|
| 101 | self.__gvar.set(optiondb.get('GSLIDER', 2)) | 
|---|
| 102 | self.__radio2 = Checkbutton(frame, text='Green', | 
|---|
| 103 | variable=self.__gvar, | 
|---|
| 104 | command=self.__effect, | 
|---|
| 105 | onvalue=2, offvalue=0) | 
|---|
| 106 | self.__radio2.grid(row=2, column=1, sticky=W) | 
|---|
| 107 | self.__bvar = IntVar() | 
|---|
| 108 | self.__bvar.set(optiondb.get('BSLIDER', 1)) | 
|---|
| 109 | self.__radio3 = Checkbutton(frame, text='Blue', | 
|---|
| 110 | variable=self.__bvar, | 
|---|
| 111 | command=self.__effect, | 
|---|
| 112 | onvalue=1, offvalue=0) | 
|---|
| 113 | self.__radio3.grid(row=3, column=1, sticky=W) | 
|---|
| 114 | self.__l2 = Label(frame) | 
|---|
| 115 | self.__l2.grid(row=4, column=1, sticky=W) | 
|---|
| 116 | self.__effect() | 
|---|
| 117 | # | 
|---|
| 118 | # Boundary behavior | 
|---|
| 119 | self.__l3 = Label(frame, text='At boundary:') | 
|---|
| 120 | self.__l3.grid(row=5, column=0, sticky=E) | 
|---|
| 121 | self.__boundvar = StringVar() | 
|---|
| 122 | self.__boundvar.set(optiondb.get('ATBOUND', STOP)) | 
|---|
| 123 | self.__omenu = OptionMenu(frame, self.__boundvar, | 
|---|
| 124 | STOP, WRAP, RATIO, GRAV) | 
|---|
| 125 | self.__omenu.grid(row=5, column=1, sticky=W) | 
|---|
| 126 | self.__omenu.configure(width=17) | 
|---|
| 127 | # | 
|---|
| 128 | # Buttons | 
|---|
| 129 | frame = self.__btnframe = Frame(frame) | 
|---|
| 130 | frame.grid(row=0, column=0, columnspan=2, sticky='EW') | 
|---|
| 131 | self.__down25 = Button(frame, text='-25', | 
|---|
| 132 | command=self.__minus25) | 
|---|
| 133 | self.__down10 = Button(frame, text='-10', | 
|---|
| 134 | command=self.__minus10) | 
|---|
| 135 | self.__down1 = Button(frame, text='-1', | 
|---|
| 136 | command=self.__minus1) | 
|---|
| 137 | self.__up1 = Button(frame, text='+1', | 
|---|
| 138 | command=self.__plus1) | 
|---|
| 139 | self.__up10 = Button(frame, text='+10', | 
|---|
| 140 | command=self.__plus10) | 
|---|
| 141 | self.__up25 = Button(frame, text='+25', | 
|---|
| 142 | command=self.__plus25) | 
|---|
| 143 | self.__down25.pack(expand=YES, fill=X, side=LEFT) | 
|---|
| 144 | self.__down10.pack(expand=YES, fill=X, side=LEFT) | 
|---|
| 145 | self.__down1.pack(expand=YES, fill=X, side=LEFT) | 
|---|
| 146 | self.__up1.pack(expand=YES, fill=X, side=LEFT) | 
|---|
| 147 | self.__up10.pack(expand=YES, fill=X, side=LEFT) | 
|---|
| 148 | self.__up25.pack(expand=YES, fill=X, side=LEFT) | 
|---|
| 149 |  | 
|---|
| 150 | def __effect(self, event=None): | 
|---|
| 151 | tie = self.__rvar.get() + self.__gvar.get() + self.__bvar.get() | 
|---|
| 152 | if tie in (0, 1, 2, 4): | 
|---|
| 153 | text = '' | 
|---|
| 154 | else: | 
|---|
| 155 | text = '(= %s Level)' % {3: 'Cyan', | 
|---|
| 156 | 5: 'Magenta', | 
|---|
| 157 | 6: 'Yellow', | 
|---|
| 158 | 7: 'Grey'}[tie] | 
|---|
| 159 | self.__l2.configure(text=text) | 
|---|
| 160 |  | 
|---|
| 161 | def __quit(self, event=None): | 
|---|
| 162 | self.__root.quit() | 
|---|
| 163 |  | 
|---|
| 164 | def withdraw(self, event=None): | 
|---|
| 165 | self.__root.withdraw() | 
|---|
| 166 |  | 
|---|
| 167 | def deiconify(self, event=None): | 
|---|
| 168 | self.__root.deiconify() | 
|---|
| 169 |  | 
|---|
| 170 | def __minus25(self, event=None): | 
|---|
| 171 | self.__delta(-25) | 
|---|
| 172 |  | 
|---|
| 173 | def __minus10(self, event=None): | 
|---|
| 174 | self.__delta(-10) | 
|---|
| 175 |  | 
|---|
| 176 | def __minus1(self, event=None): | 
|---|
| 177 | self.__delta(-1) | 
|---|
| 178 |  | 
|---|
| 179 | def __plus1(self, event=None): | 
|---|
| 180 | self.__delta(1) | 
|---|
| 181 |  | 
|---|
| 182 | def __plus10(self, event=None): | 
|---|
| 183 | self.__delta(10) | 
|---|
| 184 |  | 
|---|
| 185 | def __plus25(self, event=None): | 
|---|
| 186 | self.__delta(25) | 
|---|
| 187 |  | 
|---|
| 188 | def __delta(self, delta): | 
|---|
| 189 | tie = [] | 
|---|
| 190 | if self.__rvar.get(): | 
|---|
| 191 | red = self.__red + delta | 
|---|
| 192 | tie.append(red) | 
|---|
| 193 | else: | 
|---|
| 194 | red = self.__red | 
|---|
| 195 | if self.__gvar.get(): | 
|---|
| 196 | green = self.__green + delta | 
|---|
| 197 | tie.append(green) | 
|---|
| 198 | else: | 
|---|
| 199 | green = self.__green | 
|---|
| 200 | if self.__bvar.get(): | 
|---|
| 201 | blue = self.__blue + delta | 
|---|
| 202 | tie.append(blue) | 
|---|
| 203 | else: | 
|---|
| 204 | blue = self.__blue | 
|---|
| 205 | # now apply at boundary behavior | 
|---|
| 206 | atbound = self.__boundvar.get() | 
|---|
| 207 | if atbound == STOP: | 
|---|
| 208 | if red < 0 or green < 0 or blue < 0 or \ | 
|---|
| 209 | red > 255 or green > 255 or blue > 255: | 
|---|
| 210 | # then | 
|---|
| 211 | red, green, blue = self.__red, self.__green, self.__blue | 
|---|
| 212 | elif atbound == WRAP or (atbound == RATIO and len(tie) < 2): | 
|---|
| 213 | if red < 0: | 
|---|
| 214 | red += 256 | 
|---|
| 215 | if green < 0: | 
|---|
| 216 | green += 256 | 
|---|
| 217 | if blue < 0: | 
|---|
| 218 | blue += 256 | 
|---|
| 219 | if red > 255: | 
|---|
| 220 | red -= 256 | 
|---|
| 221 | if green > 255: | 
|---|
| 222 | green -= 256 | 
|---|
| 223 | if blue > 255: | 
|---|
| 224 | blue -= 256 | 
|---|
| 225 | elif atbound == RATIO: | 
|---|
| 226 | # for when 2 or 3 colors are tied together | 
|---|
| 227 | dir = 0 | 
|---|
| 228 | for c in tie: | 
|---|
| 229 | if c < 0: | 
|---|
| 230 | dir = -1 | 
|---|
| 231 | elif c > 255: | 
|---|
| 232 | dir = 1 | 
|---|
| 233 | if dir == -1: | 
|---|
| 234 | delta = max(tie) | 
|---|
| 235 | if self.__rvar.get(): | 
|---|
| 236 | red = red + 255 - delta | 
|---|
| 237 | if self.__gvar.get(): | 
|---|
| 238 | green = green + 255 - delta | 
|---|
| 239 | if self.__bvar.get(): | 
|---|
| 240 | blue = blue + 255 - delta | 
|---|
| 241 | elif dir == 1: | 
|---|
| 242 | delta = min(tie) | 
|---|
| 243 | if self.__rvar.get(): | 
|---|
| 244 | red = red - delta | 
|---|
| 245 | if self.__gvar.get(): | 
|---|
| 246 | green = green - delta | 
|---|
| 247 | if self.__bvar.get(): | 
|---|
| 248 | blue = blue - delta | 
|---|
| 249 | elif atbound == GRAV: | 
|---|
| 250 | if red < 0: | 
|---|
| 251 | red = 0 | 
|---|
| 252 | if green < 0: | 
|---|
| 253 | green = 0 | 
|---|
| 254 | if blue < 0: | 
|---|
| 255 | blue = 0 | 
|---|
| 256 | if red > 255: | 
|---|
| 257 | red = 255 | 
|---|
| 258 | if green > 255: | 
|---|
| 259 | green = 255 | 
|---|
| 260 | if blue > 255: | 
|---|
| 261 | blue = 255 | 
|---|
| 262 | self.__sb.update_views(red, green, blue) | 
|---|
| 263 | self.__root.update_idletasks() | 
|---|
| 264 |  | 
|---|
| 265 | def update_yourself(self, red, green, blue): | 
|---|
| 266 | self.__red = red | 
|---|
| 267 | self.__green = green | 
|---|
| 268 | self.__blue = blue | 
|---|
| 269 |  | 
|---|
| 270 | def save_options(self, optiondb): | 
|---|
| 271 | optiondb['RSLIDER'] = self.__rvar.get() | 
|---|
| 272 | optiondb['GSLIDER'] = self.__gvar.get() | 
|---|
| 273 | optiondb['BSLIDER'] = self.__bvar.get() | 
|---|
| 274 | optiondb['ATBOUND'] = self.__boundvar.get() | 
|---|