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