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