3 Copyright (C) 2002 Michael Gilfix <mgilfix@eecs.tufts.edu>
5 This file is part of PyColourChooser.
7 This version of PyColourChooser is open source; you can redistribute it
8 and/or modify it under the licensed terms.
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.
15 # 12/14/2003 - Jeff Grimmett (grimmtooth@softhome.net)
17 # o 2.5 compatability update.
19 # 12/21/2003 - Jeff Grimmett (grimmtooth@softhome.net)
21 # o wxPyColorChooser -> PyColorChooser
22 # o wxPyColourChooser -> PyColourChooser
23 # o Added wx.InitAllImageHandlers() to test code since
24 # that's where it belongs.
35 from intl
import _
# _
37 class PyColourChooser(wx
.Panel
):
38 """A Pure-Python implementation of the colour chooser dialog.
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.
59 'MEDIUM SPRING GREEN',
94 'MEDIUM FOREST GREEN',
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
111 idADD_CUSTOM
= wx
.NewId()
112 idSCROLL
= wx
.NewId()
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
118 wx
.Panel
.__init
__(self
, parent
, id)
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"))
124 self
.Bind(wx
.EVT_BUTTON
, self
.onAddCustom
, self
.add_button
)
126 # Since we're going to be constructing widgets that require some serious
127 # computation, let's process any events (like redraws) right now
130 # Create the basic colours palette
131 self
.colour_boxs
= [ ]
132 colour_grid
= wx
.GridSizer(6, 8)
133 for name
in self
.colour_names
:
135 box
= pycolourbox
.PyColourBox(self
, new_id
)
137 box
.GetColourBox().Bind(wx
.EVT_LEFT_DOWN
, lambda x
, b
=box
: self
.onBasicClick(x
, b
))
139 self
.colour_boxs
.append(box
)
140 colour_grid
.Add(box
, 0, wx
.EXPAND
)
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
:
147 custom
= pycolourbox
.PyColourBox(self
, new_id
)
149 custom
.GetColourBox().Bind(wx
.EVT_LEFT_DOWN
, lambda x
, b
=custom
: self
.onCustomClick(x
, b
))
151 custom
.SetColour(wxcolour
)
152 custom_grid
.Add(custom
, 0, wx
.EXPAND
)
153 self
.custom_boxs
.append(custom
)
155 csizer
= wx
.BoxSizer(wx
.VERTICAL
)
157 csizer
.Add(self
.basic_label
, 0, wx
.EXPAND
)
159 csizer
.Add(colour_grid
, 0, wx
.EXPAND
)
161 csizer
.Add(self
.custom_label
, 0, wx
.EXPAND
)
163 csizer
.Add(custom_grid
, 0, wx
.EXPAND
)
165 csizer
.Add(self
.add_button
, 0, wx
.EXPAND
)
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
)
174 self
.Bind(wx
.EVT_COMMAND_SCROLL
, self
.onScroll
, self
.slider
)
175 psizer
= wx
.BoxSizer(wx
.HORIZONTAL
)
176 psizer
.Add(self
.palette
, 0, 0)
178 psizer
.Add(self
.colour_slider
, 0, wx
.ALIGN_CENTER_VERTICAL
)
179 psizer
.Add(self
.slider
, 0, wx
.ALIGN_CENTER_VERTICAL
)
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
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)
192 ssizer
.Add(slabel
, 0, wx
.ALIGN_CENTER_HORIZONTAL
)
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)
205 (hlabel
, 0, wx
.ALIGN_CENTER_VERTICAL
), (self
.hentry
, 0, 0),
206 (slabel
, 0, wx
.ALIGN_CENTER_VERTICAL
), (self
.sentry
, 0, 0),
207 (vlabel
, 0, wx
.ALIGN_CENTER_VERTICAL
), (self
.ventry
, 0, 0),
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)
221 (rlabel
, 0, wx
.ALIGN_CENTER_VERTICAL
), (self
.rentry
, 0, 0),
222 (glabel
, 0, wx
.ALIGN_CENTER_VERTICAL
), (self
.gentry
, 0, 0),
223 (blabel
, 0, wx
.ALIGN_CENTER_VERTICAL
), (self
.bentry
, 0, 0),
226 gsizer
= wx
.GridSizer(2, 1)
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
)
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
)
236 vsizer
= wx
.BoxSizer(wx
.VERTICAL
)
238 vsizer
.Add(psizer
, 0, 0)
240 vsizer
.Add(hsizer
, 0, wx
.EXPAND
)
242 sizer
= wx
.BoxSizer(wx
.HORIZONTAL
)
244 sizer
.Add(csizer
, 0, wx
.EXPAND
)
246 sizer
.Add(vsizer
, 0, wx
.EXPAND
)
247 self
.SetAutoLayout(True)
252 self
.UpdateColour(self
.solid
.GetColour())
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(),
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())
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
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
)
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
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
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
)
309 def UpdateColour(self
, colour
):
310 """Performs necessary updates for when the colour selection has
312 # Reset the palette to erase any highlighting
313 self
.palette
.ReDraw()
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
)
322 def UpdateEntries(self
, colour
):
323 """Updates the color levels to display the new values."""
329 # Update the RGB entries
330 self
.rentry
.SetValue(str(r
))
331 self
.gentry
.SetValue(str(g
))
332 self
.bentry
.SetValue(str(b
))
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
))
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
)
347 def onPaletteUp(self
, event
):
348 """Stores state that the mouse is no longer depressed."""
349 self
.mouse_down
= False
351 def onPaletteMotion(self
, event
):
352 """Updates the colour values during mouse motion while the
353 mouse button is depressed."""
355 self
.doPaletteClick(event
.m_x
, event
.m_y
)
357 def doPaletteClick(self
, m_x
, m_y
):
358 """Updates the colour values based on the mouse location
360 # Get the colour value and update
361 colour
= self
.palette
.GetValue(m_x
, m_y
)
362 self
.UpdateColour(colour
)
364 # Highlight a fresh selected area
365 self
.palette
.ReDraw()
366 self
.palette
.HighlightPoint(m_x
, m_y
)
368 # Force an onscreen update
370 self
.colour_slider
.Refresh()
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
)
379 def SetValue(self
, colour
):
380 """Updates the colour chooser to reflect the given wxColour."""
381 self
.UpdateColour(colour
)
384 """Returns a wxColour object indicating the current colour choice."""
385 return self
.solid
.GetColour()
388 """Simple test display."""
391 frame
= wx
.Frame(None, -1, 'PyColourChooser Test')
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
397 wx
.InitAllImageHandlers()
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
)
407 self
.SetTopWindow(frame
)
412 if __name__
== '__main__':