]>
Commit | Line | Data |
---|---|---|
1 | """ | |
2 | wxPyColourChooser | |
3 | Copyright (C) 2002 Michael Gilfix <mgilfix@eecs.tufts.edu> | |
4 | ||
5 | This file is part of wxPyColourChooser. | |
6 | ||
7 | This version of wxPyColourChooser is open source; you can redistribute it | |
8 | and/or modify it under the licensed terms. | |
9 | ||
10 | This program is distributed in the hope that it will be useful, | |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
13 | """ | |
14 | ||
15 | # 12/14/2003 - Jeff Grimmett (grimmtooth@softhome.net) | |
16 | # | |
17 | # o 2.5 compatability update. | |
18 | # | |
19 | ||
20 | import wx | |
21 | ||
22 | import pycolourbox | |
23 | import pypalette | |
24 | import pycolourslider | |
25 | import colorsys | |
26 | import intl | |
27 | ||
28 | from intl import _ # _ | |
29 | ||
30 | class wxPyColourChooser(wx.Panel): | |
31 | """A Pure-Python implementation of the colour chooser dialog. | |
32 | ||
33 | The PyColourChooser is a pure python implementation of the colour | |
34 | chooser dialog. It's useful for embedding the colour choosing functionality | |
35 | inside other widgets, when the pop-up dialog is undesirable. It can also | |
36 | be used as a drop-in replacement on the GTK platform, as the native | |
37 | dialog is kind of ugly. | |
38 | """ | |
39 | ||
40 | colour_names = [ | |
41 | 'ORANGE', | |
42 | 'GOLDENROD', | |
43 | 'WHEAT', | |
44 | 'SPRING GREEN', | |
45 | 'SKY BLUE', | |
46 | 'SLATE BLUE', | |
47 | 'MEDIUM VIOLET RED', | |
48 | 'PURPLE', | |
49 | ||
50 | 'RED', | |
51 | 'YELLOW', | |
52 | 'MEDIUM SPRING GREEN', | |
53 | 'PALE GREEN', | |
54 | 'CYAN', | |
55 | 'LIGHT STEEL BLUE', | |
56 | 'ORCHID', | |
57 | 'LIGHT MAGENTA', | |
58 | ||
59 | 'BROWN', | |
60 | 'YELLOW', | |
61 | 'GREEN', | |
62 | 'CADET BLUE', | |
63 | 'MEDIUM BLUE', | |
64 | 'MAGENTA', | |
65 | 'MAROON', | |
66 | 'ORANGE RED', | |
67 | ||
68 | 'FIREBRICK', | |
69 | 'CORAL', | |
70 | 'FOREST GREEN', | |
71 | 'AQUAMARINE', | |
72 | 'BLUE', | |
73 | 'NAVY', | |
74 | 'THISTLE', | |
75 | 'MEDIUM VIOLET RED', | |
76 | ||
77 | 'INDIAN RED', | |
78 | 'GOLD', | |
79 | 'MEDIUM SEA GREEN', | |
80 | 'MEDIUM BLUE', | |
81 | 'MIDNIGHT BLUE', | |
82 | 'GREY', | |
83 | 'PURPLE', | |
84 | 'KHAKI', | |
85 | ||
86 | 'BLACK', | |
87 | 'MEDIUM FOREST GREEN', | |
88 | 'KHAKI', | |
89 | 'DARK GREY', | |
90 | 'SEA GREEN', | |
91 | 'LIGHT GREY', | |
92 | 'MEDIUM SLATE BLUE', | |
93 | 'WHITE', | |
94 | ] | |
95 | ||
96 | # Generate the custom colours. These colours are shared across | |
97 | # all instances of the colour chooser | |
98 | NO_CUSTOM_COLOURS = 16 | |
99 | custom_colours = [ (wx.Colour(255, 255, 255), | |
100 | pycolourslider.PyColourSlider.HEIGHT / 2) | |
101 | ] * NO_CUSTOM_COLOURS | |
102 | last_custom = 0 | |
103 | ||
104 | idADD_CUSTOM = wx.NewId() | |
105 | idSCROLL = wx.NewId() | |
106 | ||
107 | def __init__(self, parent, id): | |
108 | """Creates an instance of the colour chooser. Note that it is best to | |
109 | accept the given size of the colour chooser as it is currently not | |
110 | resizeable.""" | |
111 | wx.Panel.__init__(self, parent, id) | |
112 | ||
113 | self.basic_label = wx.StaticText(self, -1, _("Basic Colours:")) | |
114 | self.custom_label = wx.StaticText(self, -1, _("Custom Colours:")) | |
115 | self.add_button = wx.Button(self, self.idADD_CUSTOM, _("Add to Custom Colours")) | |
116 | ||
117 | self.Bind(wx.EVT_BUTTON, self.onAddCustom, self.add_button) | |
118 | ||
119 | # Since we're going to be constructing widgets that require some serious | |
120 | # computation, let's process any events (like redraws) right now | |
121 | wx.Yield() | |
122 | ||
123 | # Create the basic colours palette | |
124 | self.colour_boxs = [ ] | |
125 | colour_grid = wx.GridSizer(6, 8) | |
126 | for name in self.colour_names: | |
127 | new_id = wx.NewId() | |
128 | box = pycolourbox.PyColourBox(self, new_id) | |
129 | ||
130 | box.GetColourBox().Bind(wx.EVT_LEFT_DOWN, lambda x, b=box: self.onBasicClick(x, b)) | |
131 | ||
132 | self.colour_boxs.append(box) | |
133 | colour_grid.Add(box, 0, wx.EXPAND) | |
134 | ||
135 | # Create the custom colours palette | |
136 | self.custom_boxs = [ ] | |
137 | custom_grid = wx.GridSizer(2, 8) | |
138 | for wxcolour, slidepos in self.custom_colours: | |
139 | new_id = wx.NewId() | |
140 | custom = pycolourbox.PyColourBox(self, new_id) | |
141 | ||
142 | custom.GetColourBox().Bind(wx.EVT_LEFT_DOWN, lambda x, b=custom: self.onCustomClick(x, b)) | |
143 | ||
144 | custom.SetColour(wxcolour) | |
145 | custom_grid.Add(custom, 0, wx.EXPAND) | |
146 | self.custom_boxs.append(custom) | |
147 | ||
148 | csizer = wx.BoxSizer(wx.VERTICAL) | |
149 | csizer.Add((1, 25)) | |
150 | csizer.Add(self.basic_label, 0, wx.EXPAND) | |
151 | csizer.Add((1, 5)) | |
152 | csizer.Add(colour_grid, 0, wx.EXPAND) | |
153 | csizer.Add((1, 25)) | |
154 | csizer.Add(self.custom_label, 0, wx.EXPAND) | |
155 | csizer.Add((1, 5)) | |
156 | csizer.Add(custom_grid, 0, wx.EXPAND) | |
157 | csizer.Add((1, 5)) | |
158 | csizer.Add(self.add_button, 0, wx.EXPAND) | |
159 | ||
160 | self.palette = pypalette.PyPalette(self, -1) | |
161 | self.colour_slider = pycolourslider.PyColourSlider(self, -1) | |
162 | self.slider = wx.Slider( | |
163 | self, self.idSCROLL, 86, 0, self.colour_slider.HEIGHT - 1, | |
164 | style=wx.SL_VERTICAL, size=(15, self.colour_slider.HEIGHT) | |
165 | ) | |
166 | ||
167 | self.Bind(wx.EVT_COMMAND_SCROLL, self.onScroll, self.slider) | |
168 | psizer = wx.BoxSizer(wx.HORIZONTAL) | |
169 | psizer.Add(self.palette, 0, 0) | |
170 | psizer.Add((10, 1)) | |
171 | psizer.Add(self.colour_slider, 0, wx.ALIGN_CENTER_VERTICAL) | |
172 | psizer.Add(self.slider, 0, wx.ALIGN_CENTER_VERTICAL) | |
173 | ||
174 | # Register mouse events for dragging across the palette | |
175 | self.palette.Bind(wx.EVT_LEFT_DOWN, self.onPaletteDown) | |
176 | self.palette.Bind(wx.EVT_LEFT_UP, self.onPaletteUp) | |
177 | self.palette.Bind(wx.EVT_MOTION, self.onPaletteMotion) | |
178 | self.mouse_down = False | |
179 | ||
180 | self.solid = pycolourbox.PyColourBox(self, -1, size=(75, 50)) | |
181 | slabel = wx.StaticText(self, -1, _("Solid Colour")) | |
182 | ssizer = wx.BoxSizer(wx.VERTICAL) | |
183 | ssizer.Add(self.solid, 0, 0) | |
184 | ssizer.Add((1, 2)) | |
185 | ssizer.Add(slabel, 0, wx.ALIGN_CENTER_HORIZONTAL) | |
186 | ||
187 | hlabel = wx.StaticText(self, -1, _("H:")) | |
188 | self.hentry = wx.TextCtrl(self, -1) | |
189 | self.hentry.SetSize((40, -1)) | |
190 | slabel = wx.StaticText(self, -1, _("S:")) | |
191 | self.sentry = wx.TextCtrl(self, -1) | |
192 | self.sentry.SetSize((40, -1)) | |
193 | vlabel = wx.StaticText(self, -1, _("V:")) | |
194 | self.ventry = wx.TextCtrl(self, -1) | |
195 | self.ventry.SetSize((40, -1)) | |
196 | hsvgrid = wx.FlexGridSizer(1, 6, 2, 2) | |
197 | hsvgrid.AddMany ([ | |
198 | (hlabel, 0, wx.ALIGN_CENTER_VERTICAL), (self.hentry, 0, 0), | |
199 | (slabel, 0, wx.ALIGN_CENTER_VERTICAL), (self.sentry, 0, 0), | |
200 | (vlabel, 0, wx.ALIGN_CENTER_VERTICAL), (self.ventry, 0, 0), | |
201 | ]) | |
202 | ||
203 | rlabel = wx.StaticText(self, -1, _("R:")) | |
204 | self.rentry = wx.TextCtrl(self, -1) | |
205 | self.rentry.SetSize((40, -1)) | |
206 | glabel = wx.StaticText(self, -1, _("G:")) | |
207 | self.gentry = wx.TextCtrl(self, -1) | |
208 | self.gentry.SetSize((40, -1)) | |
209 | blabel = wx.StaticText(self, -1, _("B:")) | |
210 | self.bentry = wx.TextCtrl(self, -1) | |
211 | self.bentry.SetSize((40, -1)) | |
212 | lgrid = wx.FlexGridSizer(1, 6, 2, 2) | |
213 | lgrid.AddMany([ | |
214 | (rlabel, 0, wx.ALIGN_CENTER_VERTICAL), (self.rentry, 0, 0), | |
215 | (glabel, 0, wx.ALIGN_CENTER_VERTICAL), (self.gentry, 0, 0), | |
216 | (blabel, 0, wx.ALIGN_CENTER_VERTICAL), (self.bentry, 0, 0), | |
217 | ]) | |
218 | ||
219 | gsizer = wx.GridSizer(2, 1) | |
220 | gsizer.SetVGap (10) | |
221 | gsizer.SetHGap (2) | |
222 | gsizer.Add(hsvgrid, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER_HORIZONTAL) | |
223 | gsizer.Add(lgrid, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER_HORIZONTAL) | |
224 | ||
225 | hsizer = wx.BoxSizer(wx.HORIZONTAL) | |
226 | hsizer.Add(ssizer, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER_HORIZONTAL) | |
227 | hsizer.Add(gsizer, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER_HORIZONTAL) | |
228 | ||
229 | vsizer = wx.BoxSizer(wx.VERTICAL) | |
230 | vsizer.Add((1, 5)) | |
231 | vsizer.Add(psizer, 0, 0) | |
232 | vsizer.Add((1, 15)) | |
233 | vsizer.Add(hsizer, 0, wx.EXPAND) | |
234 | ||
235 | sizer = wx.BoxSizer(wx.HORIZONTAL) | |
236 | sizer.Add((5, 1)) | |
237 | sizer.Add(csizer, 0, wx.EXPAND) | |
238 | sizer.Add((10, 1)) | |
239 | sizer.Add(vsizer, 0, wx.EXPAND) | |
240 | self.SetAutoLayout(True) | |
241 | self.SetSizer(sizer) | |
242 | sizer.Fit(self) | |
243 | ||
244 | self.InitColours() | |
245 | self.UpdateColour(self.solid.GetColour()) | |
246 | ||
247 | def InitColours(self): | |
248 | """Initializes the pre-set palette colours.""" | |
249 | for i in range(len(self.colour_names)): | |
250 | colour = wx.TheColourDatabase.FindColour(self.colour_names[i]) | |
251 | self.colour_boxs[i].SetColourTuple((colour.Red(), | |
252 | colour.Green(), | |
253 | colour.Blue())) | |
254 | ||
255 | def onBasicClick(self, event, box): | |
256 | """Highlights the selected colour box and updates the solid colour | |
257 | display and colour slider to reflect the choice.""" | |
258 | if hasattr(self, '_old_custom_highlight'): | |
259 | self._old_custom_highlight.SetHighlight(False) | |
260 | if hasattr(self, '_old_colour_highlight'): | |
261 | self._old_colour_highlight.SetHighlight(False) | |
262 | box.SetHighlight(True) | |
263 | self._old_colour_highlight = box | |
264 | self.UpdateColour(box.GetColour()) | |
265 | ||
266 | def onCustomClick(self, event, box): | |
267 | """Highlights the selected custom colour box and updates the solid | |
268 | colour display and colour slider to reflect the choice.""" | |
269 | if hasattr(self, '_old_colour_highlight'): | |
270 | self._old_colour_highlight.SetHighlight(False) | |
271 | if hasattr(self, '_old_custom_highlight'): | |
272 | self._old_custom_highlight.SetHighlight(False) | |
273 | box.SetHighlight(True) | |
274 | self._old_custom_highlight = box | |
275 | ||
276 | # Update the colour panel and then the slider accordingly | |
277 | box_index = self.custom_boxs.index(box) | |
278 | base_colour, slidepos = self.custom_colours[box_index] | |
279 | self.UpdateColour(box.GetColour()) | |
280 | self.slider.SetValue(slidepos) | |
281 | ||
282 | def onAddCustom(self, event): | |
283 | """Adds a custom colour to the custom colour box set. Boxes are | |
284 | chosen in a round-robin fashion, eventually overwriting previously | |
285 | added colours.""" | |
286 | # Store the colour and slider position so we can restore the | |
287 | # custom colours just as they were | |
288 | self.setCustomColour(self.last_custom, | |
289 | self.solid.GetColour(), | |
290 | self.colour_slider.GetBaseColour(), | |
291 | self.slider.GetValue()) | |
292 | self.last_custom = (self.last_custom + 1) % self.NO_CUSTOM_COLOURS | |
293 | ||
294 | def setCustomColour (self, index, true_colour, base_colour, slidepos): | |
295 | """Sets the custom colour at the given index. true_colour is wxColour | |
296 | object containing the actual rgb value of the custom colour. | |
297 | base_colour (wxColour) and slidepos (int) are used to configure the | |
298 | colour slider and set everything to its original position.""" | |
299 | self.custom_boxs[index].SetColour(true_colour) | |
300 | self.custom_colours[index] = (base_colour, slidepos) | |
301 | ||
302 | def UpdateColour(self, colour): | |
303 | """Performs necessary updates for when the colour selection has | |
304 | changed.""" | |
305 | # Reset the palette to erase any highlighting | |
306 | self.palette.ReDraw() | |
307 | ||
308 | # Set the color info | |
309 | self.solid.SetColour(colour) | |
310 | self.colour_slider.SetBaseColour(colour) | |
311 | self.colour_slider.ReDraw() | |
312 | self.slider.SetValue(0) | |
313 | self.UpdateEntries(colour) | |
314 | ||
315 | def UpdateEntries(self, colour): | |
316 | """Updates the color levels to display the new values.""" | |
317 | # Temporary bindings | |
318 | r = colour.Red() | |
319 | g = colour.Green() | |
320 | b = colour.Blue() | |
321 | ||
322 | # Update the RGB entries | |
323 | self.rentry.SetValue(str(r)) | |
324 | self.gentry.SetValue(str(g)) | |
325 | self.bentry.SetValue(str(b)) | |
326 | ||
327 | # Convert to HSV | |
328 | h,s,v = colorsys.rgb_to_hsv(r / 255.0, g / 255.0, b / 255.0) | |
329 | self.hentry.SetValue("%.2f" % (h)) | |
330 | self.sentry.SetValue("%.2f" % (s)) | |
331 | self.ventry.SetValue("%.2f" % (v)) | |
332 | ||
333 | def onPaletteDown(self, event): | |
334 | """Stores state that the mouse has been pressed and updates | |
335 | the selected colour values.""" | |
336 | self.mouse_down = True | |
337 | self.palette.ReDraw() | |
338 | self.doPaletteClick(event.m_x, event.m_y) | |
339 | ||
340 | def onPaletteUp(self, event): | |
341 | """Stores state that the mouse is no longer depressed.""" | |
342 | self.mouse_down = False | |
343 | ||
344 | def onPaletteMotion(self, event): | |
345 | """Updates the colour values during mouse motion while the | |
346 | mouse button is depressed.""" | |
347 | if self.mouse_down: | |
348 | self.doPaletteClick(event.m_x, event.m_y) | |
349 | ||
350 | def doPaletteClick(self, m_x, m_y): | |
351 | """Updates the colour values based on the mouse location | |
352 | over the palette.""" | |
353 | # Get the colour value and update | |
354 | colour = self.palette.GetValue(m_x, m_y) | |
355 | self.UpdateColour(colour) | |
356 | ||
357 | # Highlight a fresh selected area | |
358 | self.palette.ReDraw() | |
359 | self.palette.HighlightPoint(m_x, m_y) | |
360 | ||
361 | # Force an onscreen update | |
362 | self.solid.Update() | |
363 | self.colour_slider.Refresh() | |
364 | ||
365 | def onScroll(self, event): | |
366 | """Updates the solid colour display to reflect the changing slider.""" | |
367 | value = self.slider.GetValue() | |
368 | colour = self.colour_slider.GetValue(value) | |
369 | self.solid.SetColour(colour) | |
370 | self.UpdateEntries(colour) | |
371 | ||
372 | def SetValue(self, colour): | |
373 | """Updates the colour chooser to reflect the given wxColour.""" | |
374 | self.UpdateColour(colour) | |
375 | ||
376 | def GetValue(self): | |
377 | """Returns a wxColour object indicating the current colour choice.""" | |
378 | return self.solid.GetColour() | |
379 | ||
380 | def main(): | |
381 | """Simple test display.""" | |
382 | class App(wx.App): | |
383 | def OnInit(self): | |
384 | frame = wx.Frame(None, -1, 'PyColourChooser Test') | |
385 | ||
386 | chooser = wxPyColourChooser(frame, -1) | |
387 | sizer = wx.BoxSizer(wx.VERTICAL) | |
388 | sizer.Add(chooser, 0, 0) | |
389 | frame.SetAutoLayout(True) | |
390 | frame.SetSizer(sizer) | |
391 | sizer.Fit(frame) | |
392 | ||
393 | frame.Show(True) | |
394 | self.SetTopWindow(frame) | |
395 | return True | |
396 | app = App(False) | |
397 | app.MainLoop() | |
398 | ||
399 | if __name__ == '__main__': | |
400 | main() |