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