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, 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
), 
 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, 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
), 
 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__':