]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/colourchooser/pycolourchooser.py
fixed wxVsnprintf() to write as much as it can if the output buffer is too short
[wxWidgets.git] / wxPython / wx / lib / colourchooser / pycolourchooser.py
1 """
2 PyColourChooser
3 Copyright (C) 2002 Michael Gilfix <mgilfix@eecs.tufts.edu>
4
5 This file is part of PyColourChooser.
6
7 This version of PyColourChooser 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 # 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 #
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
37 class PyColourChooser(wx.Panel):
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
106 custom_colours = [ (wx.Colour(255, 255, 255),
107 pycolourslider.PyColourSlider.HEIGHT / 2)
108 ] * NO_CUSTOM_COLOURS
109 last_custom = 0
110
111 idADD_CUSTOM = wx.NewId()
112 idSCROLL = wx.NewId()
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."""
118 wx.Panel.__init__(self, parent, id)
119
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"))
123
124 self.Bind(wx.EVT_BUTTON, self.onAddCustom, self.add_button)
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
128 wx.Yield()
129
130 # Create the basic colours palette
131 self.colour_boxs = [ ]
132 colour_grid = wx.GridSizer(6, 8)
133 for name in self.colour_names:
134 new_id = wx.NewId()
135 box = pycolourbox.PyColourBox(self, new_id)
136
137 box.GetColourBox().Bind(wx.EVT_LEFT_DOWN, lambda x, b=box: self.onBasicClick(x, b))
138
139 self.colour_boxs.append(box)
140 colour_grid.Add(box, 0, wx.EXPAND)
141
142 # Create the custom colours palette
143 self.custom_boxs = [ ]
144 custom_grid = wx.GridSizer(2, 8)
145 for wxcolour, slidepos in self.custom_colours:
146 new_id = wx.NewId()
147 custom = pycolourbox.PyColourBox(self, new_id)
148
149 custom.GetColourBox().Bind(wx.EVT_LEFT_DOWN, lambda x, b=custom: self.onCustomClick(x, b))
150
151 custom.SetColour(wxcolour)
152 custom_grid.Add(custom, 0, wx.EXPAND)
153 self.custom_boxs.append(custom)
154
155 csizer = wx.BoxSizer(wx.VERTICAL)
156 csizer.Add((1, 25))
157 csizer.Add(self.basic_label, 0, wx.EXPAND)
158 csizer.Add((1, 5))
159 csizer.Add(colour_grid, 0, wx.EXPAND)
160 csizer.Add((1, 25))
161 csizer.Add(self.custom_label, 0, wx.EXPAND)
162 csizer.Add((1, 5))
163 csizer.Add(custom_grid, 0, wx.EXPAND)
164 csizer.Add((1, 5))
165 csizer.Add(self.add_button, 0, wx.EXPAND)
166
167 self.palette = pypalette.PyPalette(self, -1)
168 self.colour_slider = pycolourslider.PyColourSlider(self, -1)
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)
176 psizer.Add(self.palette, 0, 0)
177 psizer.Add((10, 1))
178 psizer.Add(self.colour_slider, 0, wx.ALIGN_CENTER_VERTICAL)
179 psizer.Add(self.slider, 0, wx.ALIGN_CENTER_VERTICAL)
180
181 # Register mouse events for dragging across the palette
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)
185 self.mouse_down = False
186
187 self.solid = pycolourbox.PyColourBox(self, -1, size=(75, 50))
188 slabel = wx.StaticText(self, -1, _("Solid Colour"))
189 ssizer = wx.BoxSizer(wx.VERTICAL)
190 ssizer.Add(self.solid, 0, 0)
191 ssizer.Add((1, 2))
192 ssizer.Add(slabel, 0, wx.ALIGN_CENTER_HORIZONTAL)
193
194 hlabel = wx.StaticText(self, -1, _("H:"))
195 self.hentry = wx.TextCtrl(self, -1)
196 self.hentry.SetSize((40, -1))
197 slabel = wx.StaticText(self, -1, _("S:"))
198 self.sentry = wx.TextCtrl(self, -1)
199 self.sentry.SetSize((40, -1))
200 vlabel = wx.StaticText(self, -1, _("V:"))
201 self.ventry = wx.TextCtrl(self, -1)
202 self.ventry.SetSize((40, -1))
203 hsvgrid = wx.FlexGridSizer(1, 6, 2, 2)
204 hsvgrid.AddMany ([
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),
208 ])
209
210 rlabel = wx.StaticText(self, -1, _("R:"))
211 self.rentry = wx.TextCtrl(self, -1)
212 self.rentry.SetSize((40, -1))
213 glabel = wx.StaticText(self, -1, _("G:"))
214 self.gentry = wx.TextCtrl(self, -1)
215 self.gentry.SetSize((40, -1))
216 blabel = wx.StaticText(self, -1, _("B:"))
217 self.bentry = wx.TextCtrl(self, -1)
218 self.bentry.SetSize((40, -1))
219 lgrid = wx.FlexGridSizer(1, 6, 2, 2)
220 lgrid.AddMany([
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),
224 ])
225
226 gsizer = wx.GridSizer(2, 1)
227 gsizer.SetVGap (10)
228 gsizer.SetHGap (2)
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)
231
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)
235
236 vsizer = wx.BoxSizer(wx.VERTICAL)
237 vsizer.Add((1, 5))
238 vsizer.Add(psizer, 0, 0)
239 vsizer.Add((1, 15))
240 vsizer.Add(hsizer, 0, wx.EXPAND)
241
242 sizer = wx.BoxSizer(wx.HORIZONTAL)
243 sizer.Add((5, 1))
244 sizer.Add(csizer, 0, wx.EXPAND)
245 sizer.Add((10, 1))
246 sizer.Add(vsizer, 0, wx.EXPAND)
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)):
257 colour = wx.TheColourDatabase.FindColour(self.colour_names[i])
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."""
389 class App(wx.App):
390 def OnInit(self):
391 frame = wx.Frame(None, -1, 'PyColourChooser Test')
392
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)
400 sizer = wx.BoxSizer(wx.VERTICAL)
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
409 app = App(False)
410 app.MainLoop()
411
412 if __name__ == '__main__':
413 main()