From 2d51ef79d85d662153f0f53dd458a6b8fe1a0dcc Mon Sep 17 00:00:00 2001 From: tom surace Date: Mon, 14 Nov 2016 15:28:47 -0700 Subject: [PATCH] Fixed colourchooser palette and colour_slider controls. Palette box and slider box are now sized to fit their content, making the whole area accessible. Colour slider is now clickable. --- wx/lib/colourchooser/canvas.py | 11 ++-- wx/lib/colourchooser/pycolourbox.py | 1 + wx/lib/colourchooser/pycolourchooser.py | 72 ++++++++++++++++++++++--- wx/lib/colourchooser/pycolourslider.py | 18 ++++--- wx/lib/colourchooser/pypalette.py | 41 +++++++++----- 5 files changed, 114 insertions(+), 29 deletions(-) diff --git a/wx/lib/colourchooser/canvas.py b/wx/lib/colourchooser/canvas.py index 5a94f4cc..2b749c26 100644 --- a/wx/lib/colourchooser/canvas.py +++ b/wx/lib/colourchooser/canvas.py @@ -80,13 +80,16 @@ class Canvas(wx.Window): """ def __init__(self, parent, id, pos=wx.DefaultPosition, - size=wx.DefaultSize, - style=wx.SIMPLE_BORDER): + style=wx.SIMPLE_BORDER, + forceClientSize=None): """Creates a canvas instance and initializes the off-screen buffer. Also sets the handler for rendering the canvas automatically via size and paint calls from the windowing system.""" - wx.Window.__init__(self, parent, id, pos, size, style) + wx.Window.__init__(self, parent, id, pos, style=style) + if forceClientSize: + self.SetMaxClientSize(forceClientSize) + self.SetMinClientSize(forceClientSize) # Perform an intial sizing self.ReDraw() @@ -96,7 +99,7 @@ class Canvas(wx.Window): self.Bind(wx.EVT_PAINT, self.onPaint) def MakeNewBuffer(self): - size = self.GetSize() + size = self.GetClientSize() self.buffer = BitmapBuffer(size[0], size[1], self.GetBackgroundColour()) diff --git a/wx/lib/colourchooser/pycolourbox.py b/wx/lib/colourchooser/pycolourbox.py index a5ecbc90..5c7cb5d2 100644 --- a/wx/lib/colourchooser/pycolourbox.py +++ b/wx/lib/colourchooser/pycolourbox.py @@ -67,6 +67,7 @@ class PyColourBox(wx.Panel): """Sets the box's current couple to the given tuple.""" self.colour = colour self.colour_box.SetBackgroundColour(wx.Colour(*self.colour)) + self.colour_box.Refresh() def Update(self): wx.Panel.Update(self) diff --git a/wx/lib/colourchooser/pycolourchooser.py b/wx/lib/colourchooser/pycolourchooser.py index ba4570ee..60f9eb66 100644 --- a/wx/lib/colourchooser/pycolourchooser.py +++ b/wx/lib/colourchooser/pycolourchooser.py @@ -28,6 +28,7 @@ from __future__ import absolute_import # Tags: phoenix-port import wx +import wx.lib.newevent as newevent from . import pycolourbox from . import pypalette @@ -37,6 +38,19 @@ from . import intl from .intl import _ # _ +ColourChangedEventBase, EVT_COLOUR_CHANGED = newevent.NewEvent() + +class ColourChangedEvent(ColourChangedEventBase): + """Adds GetColour()/GetValue() for compatibility with ColourPickerCtrl and colourselect""" + def __init__(self, newColour): + super().__init__(newColour = newColour) + + def GetColour(self): + return self.newColour + + def GetValue(self): + return self.newColour + class PyColourChooser(wx.Panel): """A Pure-Python implementation of the colour chooser dialog. @@ -169,6 +183,8 @@ class PyColourChooser(wx.Panel): self.palette = pypalette.PyPalette(self, -1) self.colour_slider = pycolourslider.PyColourSlider(self, -1) + self.colour_slider.Bind(wx.EVT_LEFT_DOWN, self.onSliderDown) + self.colour_slider.Bind(wx.EVT_MOTION, self.onSliderMotion) self.slider = wx.Slider( self, self.idSCROLL, 86, 0, self.colour_slider.HEIGHT - 1, style=wx.SL_VERTICAL, size=(15, self.colour_slider.HEIGHT) @@ -185,7 +201,6 @@ class PyColourChooser(wx.Panel): self.palette.Bind(wx.EVT_LEFT_DOWN, self.onPaletteDown) self.palette.Bind(wx.EVT_LEFT_UP, self.onPaletteUp) self.palette.Bind(wx.EVT_MOTION, self.onPaletteMotion) - self.mouse_down = False self.solid = pycolourbox.PyColourBox(self, -1, size=(75, 50)) slabel = wx.StaticText(self, -1, _("Solid Colour")) @@ -366,11 +381,17 @@ class PyColourChooser(wx.Panel): s = 0 return self.hsvToColour((h, s, v)) + + def updateDisplayColour(self, colour): + """Update the displayed color box (solid) and send the EVT_COLOUR_CHANGED""" + self.solid.SetColour(colour) + evt = ColourChangedEvent(newColour=colour) + wx.PostEvent(self, evt) def UpdateColour(self, colour): """Updates displayed colour and HSV controls with the new colour""" # Set the color info - self.solid.SetColour(colour) + self.updateDisplayColour(colour) self.colour_slider.SetBaseColour(colour) self.colour_slider.ReDraw() @@ -404,22 +425,55 @@ class PyColourChooser(wx.Panel): self.hentry.SetValue("%.2f" % (h)) self.sentry.SetValue("%.2f" % (s)) self.ventry.SetValue("%.2f" % (v)) + + def onColourSliderClick(self, y): + """Shared helper for onSliderDown()/onSliderMotion()""" + v = self.colour_slider.GetVFromClick(y) + self.setSliderToV(v) + + # Now with the slider updated, update all controls + colour = self.getColourFromControls() + + self.updateDisplayColour(colour) # Update display + self.UpdateEntries(colour) + + # We don't move on the palette... + + # width, height = self.palette.GetSize() + # x = width * h + # y = height * (1 - s) + # self.palette.HighlightPoint(x, y) + + def onSliderDown(self, event): + """Handle mouse click on the colour slider palette""" + self.onColourSliderClick(event.GetY()) + + def onSliderMotion(self, event): + """Handle mouse-down drag on the colour slider palette""" + if event.LeftIsDown(): + self.onColourSliderClick(event.GetY()) def onPaletteDown(self, event): """Stores state that the mouse has been pressed and updates the selected colour values.""" - self.mouse_down = True self.doPaletteClick(event.GetX(), event.GetY()) + + # Prevent mouse from leaving window, so that we will also get events + # when mouse is dragged along the edges of the rectangle. + self.palette.CaptureMouse() def onPaletteUp(self, event): """Stores state that the mouse is no longer depressed.""" - self.mouse_down = False + self.palette.ReleaseMouse() # Must call once for each CaputreMouse() def onPaletteMotion(self, event): """Updates the colour values during mouse motion while the mouse button is depressed.""" - if self.mouse_down: + if event.LeftIsDown(): self.doPaletteClick(event.GetX(), event.GetY()) + + def onPaletteCaptureLost(self, event): + pass # I don't think we have to call ReleaseMouse in this event def doPaletteClick(self, m_x, m_y): """Updates the colour values based on the mouse location @@ -433,7 +487,7 @@ class PyColourChooser(wx.Panel): colour = self.getColourFromControls() - self.solid.SetColour(colour) # Update display + self.updateDisplayColour(colour) # Update display self.UpdateEntries(colour) # Highlight a fresh selected area @@ -443,7 +497,7 @@ class PyColourChooser(wx.Panel): """Updates the display to reflect the new "Value".""" value = self.slider.GetValue() colour = self.getColourFromControls() - self.solid.SetColour(colour) + self.updateDisplayColour(colour) self.UpdateEntries(colour) def getValueAsFloat(self, textctrl): @@ -537,8 +591,12 @@ def main(): def onClick(self, cmdEvt): with CCTestDialog(self, self.box.GetColour()) as dialog: + dialog.chooser.Bind(EVT_COLOUR_CHANGED, self.onColourChanged) dialog.ShowModal() self.box.SetColour(dialog.chooser.GetValue()) + + def onColourChanged(self, event): + self.box.SetColour(event.GetValue()) class App(wx.App): def OnInit(self): diff --git a/wx/lib/colourchooser/pycolourslider.py b/wx/lib/colourchooser/pycolourslider.py index f67c0785..0d4f9ee0 100644 --- a/wx/lib/colourchooser/pycolourslider.py +++ b/wx/lib/colourchooser/pycolourslider.py @@ -54,7 +54,7 @@ class PyColourSlider(canvas.Canvas): # drawing function self.SetBaseColour(colour) - canvas.Canvas.__init__(self, parent, id, size=(self.WIDTH, self.HEIGHT)) + canvas.Canvas.__init__(self, parent, id, forceClientSize=(self.WIDTH, self.HEIGHT)) def SetBaseColour(self, colour): """Sets the base, or target colour, to use as the central colour @@ -66,11 +66,17 @@ class PyColourSlider(canvas.Canvas): the slider.""" return self.base_colour - def GetValue(self, pos): - """Returns the colour value for a position on the slider. The position - must be within the valid height of the slider, or results can be - unpredictable.""" - return self.buffer.GetPixelColour(0, pos) + def GetVFromClick(self, pos): + """ + Returns the HSV value "V" based on the location of a mouse click at y offset "pos" + """ + _, height = self.GetClientSize() + if pos < 0: + return 1 # Snap to max + if pos >= height - 1: + return 0 # Snap to 0 + + return 1 - (pos / self.HEIGHT) def DrawBuffer(self): """Actual implementation of the widget's drawing. We simply draw diff --git a/wx/lib/colourchooser/pypalette.py b/wx/lib/colourchooser/pypalette.py index 789428c8..2cebd60e 100644 --- a/wx/lib/colourchooser/pypalette.py +++ b/wx/lib/colourchooser/pypalette.py @@ -37,6 +37,9 @@ import colorsys from wx.lib.embeddedimage import PyEmbeddedImage +# Size of Image +IMAGE_SIZE = (200,192) + Image = PyEmbeddedImage( "iVBORw0KGgoAAAANSUhEUgAAAMgAAADACAYAAABBCyzzAAAABHNCSVQICAgIfAhkiAAACwNJ" "REFUeJztnc16o0YQRZHt8SJZJO//lMkiWWQsKwsLK7S49dNdjTyTczYYhBBd7vqoom7B6bIs" @@ -112,7 +115,7 @@ class PyPalette(canvas.Canvas): HORIZONTAL_STEP = 2 VERTICAL_STEP = 4 - + def __init__(self, parent, id): """Creates a palette object.""" # Load the pre-generated palette XPM @@ -124,21 +127,35 @@ class PyPalette(canvas.Canvas): self.palette = Image.GetBitmap() self.point = None - canvas.Canvas.__init__ (self, parent, id, size=(200, 192)) + + canvas.Canvas.__init__ (self, parent, id, forceClientSize=IMAGE_SIZE) + + def DoGetBestClientSize(self): + """Overridden to create a client window that exactly fits our bitmap""" + return self.palette.GetSize() + + def xInBounds(self, x): + """Limit x to [0,width)""" + if x < 0: + x = 0 + if x >= self.buffer.width: + x = self.buffer.width - 1 + return x + + def yInBounds(self, y): + """Limit y to [0,height)""" + if y < 0: + y = 0 + if y >= self.buffer.height: + y = self.buffer.height - 1 + return y def GetValue(self, x, y): """Returns a colour value at a specific x, y coordinate pair. This is useful for determining the colour found a specific mouse click in an external event handler.""" - if x < 0: - x = 0 - if y < 0: - y = 0 - if x >= self.buffer.width: - x = self.buffer.width - 1 - if y >= self.buffer.height: - y = self.buffer.height - 1 - + x = self.xInBounds(x) + y = self.yInBounds(y) return self.buffer.GetPixelColour(x, y) def DrawBuffer(self): @@ -154,7 +171,7 @@ class PyPalette(canvas.Canvas): def HighlightPoint(self, x, y): """Highlights an area of the palette with a little circle around the coordinate point""" - self.point = (x, y) + self.point = (self.xInBounds(x), self.yInBounds(y)) self.ReDraw() def ClearPoint(self):