--- /dev/null
+"""
+This module contains some common functions used by wxPython-AUI to
+manipulate colours, bitmaps, text, gradient shadings and custom
+dragging images for AuiNotebook tabs.
+"""
+
+__author__ = "Andrea Gavana <andrea.gavana@gmail.com>"
+__date__ = "31 March 2009"
+
+
+import wx
+
+from aui_constants import *
+
+
+if wx.Platform == "__WXMAC__":
+ import Carbon.Appearance
+
+
+def BlendColour(fg, bg, alpha):
+ """
+ Blends the two colour component `fg` and `bg` into one colour component, adding
+ an optional alpha channel.
+
+ :param `fg`: the first colour component;
+ :param `bg`: the second colour component;
+ :param `alpha`: an optional transparency value.
+ """
+
+ result = bg + (alpha*(fg - bg))
+
+ if result < 0.0:
+ result = 0.0
+ if result > 255:
+ result = 255
+
+ return result
+
+
+def StepColour(c, ialpha):
+ """
+ Darken/lighten the input colour `c`.
+
+ :param `c`: a colour to darken/lighten;
+ :param `ialpha`: a transparency value.
+ """
+
+ if ialpha == 100:
+ return c
+
+ r, g, b = c.Red(), c.Green(), c.Blue()
+
+ # ialpha is 0..200 where 0 is completely black
+ # and 200 is completely white and 100 is the same
+ # convert that to normal alpha 0.0 - 1.0
+ ialpha = min(ialpha, 200)
+ ialpha = max(ialpha, 0)
+ alpha = (ialpha - 100.0)/100.0
+
+ if ialpha > 100:
+
+ # blend with white
+ bg = 255
+ alpha = 1.0 - alpha # 0 = transparent fg 1 = opaque fg
+
+ else:
+
+ # blend with black
+ bg = 0
+ alpha = 1.0 + alpha # 0 = transparent fg 1 = opaque fg
+
+ r = BlendColour(r, bg, alpha)
+ g = BlendColour(g, bg, alpha)
+ b = BlendColour(b, bg, alpha)
+
+ return wx.Colour(r, g, b)
+
+
+def LightContrastColour(c):
+ """
+ Creates a new, lighter colour based on the input colour `c`.
+
+ :param `c`: the input colour to analyze.
+ """
+
+ amount = 120
+
+ # if the colour is especially dark, then
+ # make the contrast even lighter
+ if c.Red() < 128 and c.Green() < 128 and c.Blue() < 128:
+ amount = 160
+
+ return StepColour(c, amount)
+
+
+def ChopText(dc, text, max_size):
+ """
+ Chops the input `text` if its size does not fit in `max_size`, by cutting the
+ text and adding ellipsis at the end.
+
+ :param `dc`: a `wx.DC` device context;
+ :param `text`: the text to chop;
+ :param `max_size`: the maximum size in which the text should fit.
+ """
+
+ # first check if the text fits with no problems
+ x, y, dummy = dc.GetMultiLineTextExtent(text)
+
+ if x <= max_size:
+ return text
+
+ textLen = len(text)
+ last_good_length = 0
+
+ for i in xrange(textLen, -1, -1):
+ s = text[0:i]
+ s += "..."
+
+ x, y = dc.GetTextExtent(s)
+ last_good_length = i
+
+ if x < max_size:
+ break
+
+ ret = text[0:last_good_length] + "..."
+ return ret
+
+
+def BitmapFromBits(bits, w, h, colour):
+ """
+ BitmapFromBits() is a utility function that creates a
+ masked bitmap from raw bits (XBM format).
+
+ :param `bits`: a string containing the raw bits of the bitmap;
+ :param `w`: the bitmap width;
+ :param `h`: the bitmap height;
+ :param `colour`: the colour which will replace all white pixels in the
+ raw bitmap.
+ """
+
+ img = wx.BitmapFromBits(bits, w, h).ConvertToImage()
+ img.Replace(0, 0, 0, 123, 123, 123)
+ img.Replace(255, 255, 255, colour.Red(), colour.Green(), colour.Blue())
+ img.SetMaskColour(123, 123, 123)
+ return wx.BitmapFromImage(img)
+
+
+def IndentPressedBitmap(rect, button_state):
+ """
+ Indents the input rectangle `rect` based on the value of `button_state`.
+
+ :param `rect`: an instance of wx.Rect;
+ :param `button_state`: an L{AuiNotebook} button state.
+ """
+
+ if button_state == AUI_BUTTON_STATE_PRESSED:
+ rect.x += 1
+ rect.y += 1
+
+ return rect
+
+
+def GetBaseColour():
+ """
+ Returns the face shading colour on push buttons/backgrounds, mimicking as closely
+ as possible the platform UI colours.
+ """
+
+ if wx.Platform == "__WXMAC__":
+
+ if hasattr(wx, 'MacThemeColour'):
+ base_colour = wx.MacThemeColour(Carbon.Appearance.kThemeBrushToolbarBackground)
+ else:
+ brush = wx.Brush(wx.BLACK)
+ brush.MacSetTheme(Carbon.Appearance.kThemeBrushToolbarBackground)
+ base_colour = brush.GetColour()
+
+ else:
+
+ base_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE)
+
+ # the base_colour is too pale to use as our base colour,
+ # so darken it a bit
+ if ((255-base_colour.Red()) +
+ (255-base_colour.Green()) +
+ (255-base_colour.Blue()) < 60):
+
+ base_colour = StepColour(base_colour, 92)
+
+ return base_colour
+
+
+def MakeDisabledBitmap(bitmap):
+ """
+ Convert the given image (in place) to a grayed-out version,
+ appropriate for a 'disabled' appearance.
+
+ :param `bitmap`: the bitmap to gray-out.
+ """
+
+ anImage = bitmap.ConvertToImage()
+ factor = 0.7 # 0 < f < 1. Higher Is Grayer
+
+ if anImage.HasMask():
+ maskColour = (anImage.GetMaskRed(), anImage.GetMaskGreen(), anImage.GetMaskBlue())
+ else:
+ maskColour = None
+
+ data = map(ord, list(anImage.GetData()))
+
+ for i in range(0, len(data), 3):
+
+ pixel = (data[i], data[i+1], data[i+2])
+ pixel = MakeGray(pixel, factor, maskColour)
+
+ for x in range(3):
+ data[i+x] = pixel[x]
+
+ anImage.SetData(''.join(map(chr, data)))
+
+ return anImage.ConvertToBitmap()
+
+
+def MakeGray(rgbTuple, factor, maskColour):
+ """
+ Make a pixel grayed-out. If the pixel matches the `maskColour`, it won't be
+ changed.
+
+ :param `rgbTuple`: a tuple representing a pixel colour;
+ :param `factor`: a graying-out factor;
+ :param `maskColour`: a colour mask.
+ """
+
+ if rgbTuple != maskColour:
+ r, g, b = rgbTuple
+ return map(lambda x: int((230 - x) * factor) + x, (r, g, b))
+ else:
+ return rgbTuple
+
+
+def Clip(a, b, c):
+ """
+ Clips the value in `a` based on the extremes `b` and `c`.
+
+ :param `a`: the value to analyze;
+ :param `b`: a minimum value;
+ :param `c`: a maximum value.
+ """
+
+ return ((a < b and [b]) or [(a > c and [c] or [a])[0]])[0]
+
+
+def LightColour(colour, percent):
+ """
+ Brighten input `colour` by `percent`.
+
+ :param `colour`: the colour to be brightened;
+ :param `percent`: brightening percentage.
+ """
+
+ end_colour = wx.WHITE
+
+ rd = end_colour.Red() - colour.Red()
+ gd = end_colour.Green() - colour.Green()
+ bd = end_colour.Blue() - colour.Blue()
+
+ high = 100
+
+ # We take the percent way of the colour from colour -. white
+ i = percent
+ r = colour.Red() + ((i*rd*100)/high)/100
+ g = colour.Green() + ((i*gd*100)/high)/100
+ b = colour.Blue() + ((i*bd*100)/high)/100
+ return wx.Colour(r, g, b)
+
+
+def PaneCreateStippleBitmap():
+ """
+ Creates a stipple bitmap to be used in a `wx.Brush`.
+ This is used to draw sash resize hints.
+ """
+
+ data = [0, 0, 0, 192, 192, 192, 192, 192, 192, 0, 0, 0]
+ img = wx.EmptyImage(2, 2)
+ counter = 0
+
+ for ii in xrange(2):
+ for jj in xrange(2):
+ img.SetRGB(ii, jj, data[counter], data[counter+1], data[counter+2])
+ counter = counter + 3
+
+ return img.ConvertToBitmap()
+
+
+def DrawMACCloseButton(colour, backColour=None):
+ """
+ Draws the wxMAC tab close button using `wx.GraphicsContext`.
+
+ :param `colour`: the colour to use to draw the circle;
+ :param `backColour`: the optional background colour for the circle.
+ """
+
+ bmp = wx.EmptyBitmapRGBA(16, 16)
+ dc = wx.MemoryDC()
+ dc.SelectObject(bmp)
+
+ gc = wx.GraphicsContext.Create(dc)
+ gc.SetBrush(wx.Brush(colour))
+ path = gc.CreatePath()
+ path.AddCircle(6.5, 7, 6.5)
+ path.CloseSubpath()
+ gc.FillPath(path)
+
+ path = gc.CreatePath()
+ if backColour is not None:
+ pen = wx.Pen(backColour, 2)
+ else:
+ pen = wx.Pen("white", 2)
+
+ pen.SetCap(wx.CAP_BUTT)
+ pen.SetJoin(wx.JOIN_BEVEL)
+ gc.SetPen(pen)
+ path.MoveToPoint(3.5, 4)
+ path.AddLineToPoint(9.5, 10)
+ path.MoveToPoint(3.5, 10)
+ path.AddLineToPoint(9.5, 4)
+ path.CloseSubpath()
+ gc.DrawPath(path)
+
+ dc.SelectObject(wx.NullBitmap)
+ return bmp
+
+
+def DarkenBitmap(bmp, caption_colour, new_colour):
+ """
+ Darkens the input bitmap on wxMAC using the input colour.
+
+ :param `bmp`: the bitmap to be manipulated;
+ :param `caption_colour`: the colour of the pane caption;
+ :param `new_colour`: the colour used to darken the bitmap.
+ """
+
+ image = bmp.ConvertToImage()
+ red = caption_colour.Red()/float(new_colour.Red())
+ green = caption_colour.Green()/float(new_colour.Green())
+ blue = caption_colour.Blue()/float(new_colour.Blue())
+ image = image.AdjustChannels(red, green, blue)
+ return image.ConvertToBitmap()
+
+
+def DrawGradientRectangle(dc, rect, start_colour, end_colour, direction, offset=0, length=0):
+ """
+ Draws a gradient-shaded rectangle.
+
+ :param `dc`: a `wx.DC` device context;
+ :param `rect`: the rectangle in which to draw the gradient;
+ :param `start_colour`: the first colour of the gradient;
+ :param `end_colour`: the second colour of the gradient;
+ :param `direction`: the gradient direction (horizontal or vertical).
+ """
+
+ if direction == AUI_GRADIENT_VERTICAL:
+ dc.GradientFillLinear(rect, start_colour, end_colour, wx.SOUTH)
+ else:
+ dc.GradientFillLinear(rect, start_colour, end_colour, wx.EAST)
+
+
+def FindFocusDescendant(ancestor):
+ """
+ Find a window with the focus, that is also a descendant of the given window.
+ This is used to determine the window to initially send commands to.
+
+ :param `ancestor`: the window to check for ancestry.
+ """
+
+ # Process events starting with the window with the focus, if any.
+ focusWin = wx.Window.FindFocus()
+ win = focusWin
+
+ # Check if this is a descendant of this frame.
+ # If not, win will be set to NULL.
+ while win:
+ if win == ancestor:
+ break
+ else:
+ win = win.GetParent()
+
+ if win is None:
+ focusWin = None
+
+ return focusWin
+
+
+def GetLabelSize(dc, label, vertical):
+ """
+ Returns the L{AuiToolBar} item label size.
+
+ :param `label`: the toolbar tool label;
+ :param `vertical`: whether the toolbar tool orientation is vertical or not.
+ """
+
+ text_width = text_height = 0
+
+ # get the text height
+ dummy, text_height = dc.GetTextExtent("ABCDHgj")
+ # get the text width
+ if label.strip():
+ text_width, dummy = dc.GetTextExtent(label)
+
+ if vertical:
+ tmp = text_height
+ text_height = text_width
+ text_width = tmp
+
+ return wx.Size(text_width, text_height)
+
+
+#---------------------------------------------------------------------------
+# TabDragImage implementation
+# This class handles the creation of a custom image when dragging
+# AuiNotebook tabs
+#---------------------------------------------------------------------------
+
+class TabDragImage(wx.DragImage):
+ """
+ This class handles the creation of a custom image in case of drag and
+ drop of a notebook tab.
+ """
+
+ def __init__(self, notebook, page, button_state, tabArt):
+ """
+ Default class constructor.
+
+ For internal use: do not call it in your code!
+
+ :param `notebook`: an instance of L{AuiNotebook};
+ :param `page`: the dragged L{AuiNotebook} page;
+ :param `button_state`: the state of the close button on the tab;
+ :param `tabArt`: an instance of L{AuiDefaultTabArt} or one of its derivations.
+ """
+
+ self._backgroundColour = wx.NamedColour("pink")
+ self._bitmap = self.CreateBitmap(notebook, page, button_state, tabArt)
+ wx.DragImage.__init__(self, self._bitmap)
+
+
+ def CreateBitmap(self, notebook, page, button_state, tabArt):
+ """
+ Actually creates the drag and drop bitmap.
+
+ :param `notebook`: an instance of L{AuiNotebook};
+ :param `page`: the dragged L{AuiNotebook} page;
+ :param `button_state`: the state of the close button on the tab;
+ :param `tabArt`: an instance of L{AuiDefaultTabArt} or one of its derivations.
+ """
+
+ control = page.control
+ memory = wx.MemoryDC(wx.EmptyBitmap(1, 1))
+
+ tab_size, x_extent = tabArt.GetTabSize(memory, notebook, page.caption, page.bitmap, page.active,
+ button_state, control)
+
+ tab_width, tab_height = tab_size
+ rect = wx.Rect(0, 0, tab_width, tab_height)
+
+ bitmap = wx.EmptyBitmap(tab_width+1, tab_height+1)
+ memory.SelectObject(bitmap)
+
+ if wx.Platform == "__WXMAC__":
+ memory.SetBackground(wx.TRANSPARENT_BRUSH)
+ else:
+ memory.SetBackground(wx.Brush(self._backgroundColour))
+
+ memory.SetBackgroundMode(wx.TRANSPARENT)
+ memory.Clear()
+
+ paint_control = wx.Platform != "__WXMAC__"
+ tabArt.DrawTab(memory, notebook, page, rect, button_state, paint_control=paint_control)
+
+ memory.SetBrush(wx.TRANSPARENT_BRUSH)
+ memory.SetPen(wx.BLACK_PEN)
+ memory.DrawRoundedRectangle(0, 0, tab_width+1, tab_height+1, 2)
+
+ memory.SelectObject(wx.NullBitmap)
+
+ # Gtk and Windows unfortunatly don't do so well with transparent
+ # drawing so this hack corrects the image to have a transparent
+ # background.
+ if wx.Platform != '__WXMAC__':
+ timg = bitmap.ConvertToImage()
+ if not timg.HasAlpha():
+ timg.InitAlpha()
+ for y in xrange(timg.GetHeight()):
+ for x in xrange(timg.GetWidth()):
+ pix = wx.Colour(timg.GetRed(x, y),
+ timg.GetGreen(x, y),
+ timg.GetBlue(x, y))
+ if pix == self._backgroundColour:
+ timg.SetAlpha(x, y, 0)
+ bitmap = timg.ConvertToBitmap()
+ return bitmap
+
+
+def GetDockingImage(direction, useAero, center):
+ """
+ Returns the correct name of the docking bitmap depending on the input parameters.
+
+ :param `useAero`: whether L{AuiManager} is using Aero-style or Whidbey-style docking
+ images or not;
+ :param `center`: whether we are looking for the center diamond-shaped bitmap or not.
+ """
+
+ suffix = (center and [""] or ["_single"])[0]
+ prefix = ""
+ if useAero == 2:
+ # Whidbey docking guides
+ prefix = "whidbey_"
+ elif useAero == 1:
+ # Aero docking style
+ prefix = "aero_"
+
+ if direction == wx.TOP:
+ bmp_unfocus = eval("%sup%s"%(prefix, suffix)).GetBitmap()
+ bmp_focus = eval("%sup_focus%s"%(prefix, suffix)).GetBitmap()
+ elif direction == wx.BOTTOM:
+ bmp_unfocus = eval("%sdown%s"%(prefix, suffix)).GetBitmap()
+ bmp_focus = eval("%sdown_focus%s"%(prefix, suffix)).GetBitmap()
+ elif direction == wx.LEFT:
+ bmp_unfocus = eval("%sleft%s"%(prefix, suffix)).GetBitmap()
+ bmp_focus = eval("%sleft_focus%s"%(prefix, suffix)).GetBitmap()
+ elif direction == wx.RIGHT:
+ bmp_unfocus = eval("%sright%s"%(prefix, suffix)).GetBitmap()
+ bmp_focus = eval("%sright_focus%s"%(prefix, suffix)).GetBitmap()
+ else:
+ bmp_unfocus = eval("%stab%s"%(prefix, suffix)).GetBitmap()
+ bmp_focus = eval("%stab_focus%s"%(prefix, suffix)).GetBitmap()
+
+ return bmp_unfocus, bmp_focus
+
+
+def TakeScreenShot(rect):
+ """
+ Takes a screenshot of the screen at given position and size (rect).
+
+ :param `rect`: the screen rectangle for which we want to take a screenshot.
+ """
+
+ # Create a DC for the whole screen area
+ dcScreen = wx.ScreenDC()
+
+ # Create a Bitmap that will later on hold the screenshot image
+ # Note that the Bitmap must have a size big enough to hold the screenshot
+ # -1 means using the current default colour depth
+ bmp = wx.EmptyBitmap(rect.width, rect.height)
+
+ # Create a memory DC that will be used for actually taking the screenshot
+ memDC = wx.MemoryDC()
+
+ # Tell the memory DC to use our Bitmap
+ # all drawing action on the memory DC will go to the Bitmap now
+ memDC.SelectObject(bmp)
+
+ # Blit (in this case copy) the actual screen on the memory DC
+ # and thus the Bitmap
+ memDC.Blit( 0, # Copy to this X coordinate
+ 0, # Copy to this Y coordinate
+ rect.width, # Copy this width
+ rect.height, # Copy this height
+ dcScreen, # From where do we copy?
+ rect.x, # What's the X offset in the original DC?
+ rect.y # What's the Y offset in the original DC?
+ )
+
+ # Select the Bitmap out of the memory DC by selecting a new
+ # uninitialized Bitmap
+ memDC.SelectObject(wx.NullBitmap)
+
+ return bmp
+
+
+def RescaleScreenShot(bmp, thumbnail_size=200):
+ """
+ Rescales a bitmap to be 300 pixels wide (or tall) at maximum.
+
+ :param `bmp`: the bitmap to rescale;
+ :param `thumbnail_size`: the maximum size of every page thumbnail.
+ """
+
+ bmpW, bmpH = bmp.GetWidth(), bmp.GetHeight()
+ img = bmp.ConvertToImage()
+
+ newW, newH = bmpW, bmpH
+
+ if bmpW > bmpH:
+ if bmpW > thumbnail_size:
+ ratio = bmpW/float(thumbnail_size)
+ newW, newH = int(bmpW/ratio), int(bmpH/ratio)
+ img.Rescale(newW, newH, wx.IMAGE_QUALITY_HIGH)
+ else:
+ if bmpH > thumbnail_size:
+ ratio = bmpH/float(thumbnail_size)
+ newW, newH = int(bmpW/ratio), int(bmpH/ratio)
+ img.Rescale(newW, newH, wx.IMAGE_QUALITY_HIGH)
+
+ newBmp = img.ConvertToBitmap()
+ otherBmp = wx.EmptyBitmap(newW+5, newH+5)
+
+ memDC = wx.MemoryDC()
+ memDC.SelectObject(otherBmp)
+ memDC.SetBackground(wx.WHITE_BRUSH)
+ memDC.Clear()
+
+ memDC.SetPen(wx.TRANSPARENT_PEN)
+
+ pos = 0
+ for i in xrange(5, 0, -1):
+ brush = wx.Brush(wx.Colour(50*i, 50*i, 50*i))
+ memDC.SetBrush(brush)
+ memDC.DrawRoundedRectangle(0, 0, newW+5-pos, newH+5-pos, 2)
+ pos += 1
+
+ memDC.DrawBitmap(newBmp, 0, 0, True)
+
+ # Select the Bitmap out of the memory DC by selecting a new
+ # uninitialized Bitmap
+ memDC.SelectObject(wx.NullBitmap)
+
+ return otherBmp
+
+
+def GetSlidingPoints(rect, size, direction):
+ """
+ Returns the point at which the sliding in and out of a minimized pane begins.
+
+ :param `rect`: the L{AuiToolBar} tool screen rectangle;
+ :param `size`: the pane window size;
+ :param `direction`: the pane docking direction.
+ """
+
+ if direction == AUI_DOCK_LEFT:
+ startX, startY = rect.x + rect.width + 2, rect.y
+ elif direction == AUI_DOCK_TOP:
+ startX, startY = rect.x, rect.y + rect.height + 2
+ elif direction == AUI_DOCK_RIGHT:
+ startX, startY = rect.x - size.x - 2, rect.y
+ elif direction == AUI_DOCK_BOTTOM:
+ startX, startY = rect.x, rect.y - size.y - 2
+ else:
+ raise Exception("How did we get here?")
+
+ caption_height = wx.SystemSettings.GetMetric(wx.SYS_CAPTION_Y)
+ frame_border_x = wx.SystemSettings.GetMetric(wx.SYS_FRAMESIZE_X)
+ frame_border_y = wx.SystemSettings.GetMetric(wx.SYS_FRAMESIZE_Y)
+
+ stopX = size.x + caption_height + frame_border_x
+ stopY = size.x + frame_border_y
+
+ return startX, startY, stopX, stopY
+
+
+def CopyAttributes(newArt, oldArt):
+ """
+ Copies pens, brushes, colours and fonts from the old tab art to the new one.
+
+ :param `newArt`: the new instance of L{AuiDefaultTabArt};
+ :param `oldArt`: the old instance of L{AuiDefaultTabArt}.
+ """
+
+ attrs = dir(oldArt)
+
+ for attr in attrs:
+ if attr.startswith("_") and (attr.endswith("_colour") or attr.endswith("_font") or \
+ attr.endswith("_font") or attr.endswith("_brush") or \
+ attr.endswith("Pen") or attr.endswith("_pen")):
+ setattr(newArt, attr, getattr(oldArt, attr))
+
+ return newArt
+