]>
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 | ||
b881fc78 RD |
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): | |
d14a1e28 RD |
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 | |
b881fc78 | 99 | custom_colours = [ (wx.Colour(255, 255, 255), |
d14a1e28 RD |
100 | pycolourslider.PyColourSlider.HEIGHT / 2) |
101 | ] * NO_CUSTOM_COLOURS | |
102 | last_custom = 0 | |
103 | ||
b881fc78 RD |
104 | idADD_CUSTOM = wx.NewId() |
105 | idSCROLL = wx.NewId() | |
d14a1e28 RD |
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.""" | |
b881fc78 | 111 | wx.Panel.__init__(self, parent, id) |
d14a1e28 | 112 | |
b881fc78 RD |
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")) | |
d14a1e28 | 116 | |
b881fc78 | 117 | self.Bind(wx.EVT_BUTTON, self.onAddCustom, self.add_button) |
d14a1e28 RD |
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 | |
b881fc78 | 121 | wx.Yield() |
d14a1e28 RD |
122 | |
123 | # Create the basic colours palette | |
124 | self.colour_boxs = [ ] | |
b881fc78 | 125 | colour_grid = wx.GridSizer(6, 8) |
d14a1e28 | 126 | for name in self.colour_names: |
b881fc78 | 127 | new_id = wx.NewId() |
d14a1e28 | 128 | box = pycolourbox.PyColourBox(self, new_id) |
b881fc78 RD |
129 | |
130 | box.GetColourBox().Bind(wx.EVT_LEFT_DOWN, lambda x, b=box: self.onBasicClick(x, b)) | |
131 | ||
d14a1e28 | 132 | self.colour_boxs.append(box) |
b881fc78 | 133 | colour_grid.Add(box, 0, wx.EXPAND) |
d14a1e28 RD |
134 | |
135 | # Create the custom colours palette | |
136 | self.custom_boxs = [ ] | |
b881fc78 | 137 | custom_grid = wx.GridSizer(2, 8) |
d14a1e28 | 138 | for wxcolour, slidepos in self.custom_colours: |
b881fc78 | 139 | new_id = wx.NewId() |
d14a1e28 | 140 | custom = pycolourbox.PyColourBox(self, new_id) |
b881fc78 RD |
141 | |
142 | custom.GetColourBox().Bind(wx.EVT_LEFT_DOWN, lambda x, b=custom: self.onCustomClick(x, b)) | |
143 | ||
d14a1e28 | 144 | custom.SetColour(wxcolour) |
b881fc78 | 145 | custom_grid.Add(custom, 0, wx.EXPAND) |
d14a1e28 RD |
146 | self.custom_boxs.append(custom) |
147 | ||
b881fc78 | 148 | csizer = wx.BoxSizer(wx.VERTICAL) |
7722248d | 149 | csizer.Add((1, 25)) |
b881fc78 | 150 | csizer.Add(self.basic_label, 0, wx.EXPAND) |
7722248d | 151 | csizer.Add((1, 5)) |
b881fc78 | 152 | csizer.Add(colour_grid, 0, wx.EXPAND) |
7722248d | 153 | csizer.Add((1, 25)) |
b881fc78 | 154 | csizer.Add(self.custom_label, 0, wx.EXPAND) |
7722248d | 155 | csizer.Add((1, 5)) |
b881fc78 | 156 | csizer.Add(custom_grid, 0, wx.EXPAND) |
7722248d | 157 | csizer.Add((1, 5)) |
b881fc78 | 158 | csizer.Add(self.add_button, 0, wx.EXPAND) |
d14a1e28 RD |
159 | |
160 | self.palette = pypalette.PyPalette(self, -1) | |
161 | self.colour_slider = pycolourslider.PyColourSlider(self, -1) | |
b881fc78 RD |
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) | |
d14a1e28 | 169 | psizer.Add(self.palette, 0, 0) |
7722248d | 170 | psizer.Add((10, 1)) |
b881fc78 RD |
171 | psizer.Add(self.colour_slider, 0, wx.ALIGN_CENTER_VERTICAL) |
172 | psizer.Add(self.slider, 0, wx.ALIGN_CENTER_VERTICAL) | |
d14a1e28 RD |
173 | |
174 | # Register mouse events for dragging across the palette | |
b881fc78 RD |
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) | |
d14a1e28 RD |
178 | self.mouse_down = False |
179 | ||
b881fc78 RD |
180 | self.solid = pycolourbox.PyColourBox(self, -1, size=(75, 50)) |
181 | slabel = wx.StaticText(self, -1, _("Solid Colour")) | |
182 | ssizer = wx.BoxSizer(wx.VERTICAL) | |
d14a1e28 | 183 | ssizer.Add(self.solid, 0, 0) |
7722248d | 184 | ssizer.Add((1, 2)) |
b881fc78 | 185 | ssizer.Add(slabel, 0, wx.ALIGN_CENTER_HORIZONTAL) |
d14a1e28 | 186 | |
b881fc78 RD |
187 | hlabel = wx.StaticText(self, -1, _("H:")) |
188 | self.hentry = wx.TextCtrl(self, -1) | |
d14a1e28 | 189 | self.hentry.SetSize((40, -1)) |
b881fc78 RD |
190 | slabel = wx.StaticText(self, -1, _("S:")) |
191 | self.sentry = wx.TextCtrl(self, -1) | |
d14a1e28 | 192 | self.sentry.SetSize((40, -1)) |
b881fc78 RD |
193 | vlabel = wx.StaticText(self, -1, _("V:")) |
194 | self.ventry = wx.TextCtrl(self, -1) | |
d14a1e28 | 195 | self.ventry.SetSize((40, -1)) |
b881fc78 | 196 | hsvgrid = wx.FlexGridSizer(1, 6, 2, 2) |
d14a1e28 | 197 | hsvgrid.AddMany ([ |
b881fc78 RD |
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), | |
d14a1e28 RD |
201 | ]) |
202 | ||
b881fc78 RD |
203 | rlabel = wx.StaticText(self, -1, _("R:")) |
204 | self.rentry = wx.TextCtrl(self, -1) | |
d14a1e28 | 205 | self.rentry.SetSize((40, -1)) |
b881fc78 RD |
206 | glabel = wx.StaticText(self, -1, _("G:")) |
207 | self.gentry = wx.TextCtrl(self, -1) | |
d14a1e28 | 208 | self.gentry.SetSize((40, -1)) |
b881fc78 RD |
209 | blabel = wx.StaticText(self, -1, _("B:")) |
210 | self.bentry = wx.TextCtrl(self, -1) | |
d14a1e28 | 211 | self.bentry.SetSize((40, -1)) |
b881fc78 | 212 | lgrid = wx.FlexGridSizer(1, 6, 2, 2) |
d14a1e28 | 213 | lgrid.AddMany([ |
b881fc78 RD |
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), | |
d14a1e28 RD |
217 | ]) |
218 | ||
b881fc78 | 219 | gsizer = wx.GridSizer(2, 1) |
d14a1e28 RD |
220 | gsizer.SetVGap (10) |
221 | gsizer.SetHGap (2) | |
b881fc78 RD |
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) | |
d14a1e28 | 224 | |
b881fc78 RD |
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) | |
d14a1e28 | 228 | |
b881fc78 | 229 | vsizer = wx.BoxSizer(wx.VERTICAL) |
7722248d | 230 | vsizer.Add((1, 5)) |
d14a1e28 | 231 | vsizer.Add(psizer, 0, 0) |
7722248d | 232 | vsizer.Add((1, 15)) |
b881fc78 | 233 | vsizer.Add(hsizer, 0, wx.EXPAND) |
d14a1e28 | 234 | |
b881fc78 | 235 | sizer = wx.BoxSizer(wx.HORIZONTAL) |
7722248d | 236 | sizer.Add((5, 1)) |
b881fc78 | 237 | sizer.Add(csizer, 0, wx.EXPAND) |
7722248d | 238 | sizer.Add((10, 1)) |
b881fc78 | 239 | sizer.Add(vsizer, 0, wx.EXPAND) |
d14a1e28 RD |
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)): | |
b881fc78 | 250 | colour = wx.TheColourDatabase.FindColour(self.colour_names[i]) |
d14a1e28 RD |
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.""" | |
b881fc78 | 382 | class App(wx.App): |
d14a1e28 | 383 | def OnInit(self): |
b881fc78 | 384 | frame = wx.Frame(None, -1, 'PyColourChooser Test') |
d14a1e28 RD |
385 | |
386 | chooser = wxPyColourChooser(frame, -1) | |
b881fc78 | 387 | sizer = wx.BoxSizer(wx.VERTICAL) |
d14a1e28 RD |
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 | |
b881fc78 | 396 | app = App(False) |
d14a1e28 RD |
397 | app.MainLoop() |
398 | ||
399 | if __name__ == '__main__': | |
400 | main() |