2 This module contains some common functions used by wxPython-AUI to
3 manipulate colours, bitmaps, text, gradient shadings and custom
4 dragging images for AuiNotebook tabs.
7 __author__ = "Andrea Gavana <andrea.gavana@gmail.com>"
8 __date__ = "31 March 2009"
13 from aui_constants import *
16 if wx.Platform == "__WXMAC__":
17 import Carbon.Appearance
20 def BlendColour(fg, bg, alpha):
22 Blends the two colour component `fg` and `bg` into one colour component, adding
23 an optional alpha channel.
25 :param `fg`: the first colour component;
26 :param `bg`: the second colour component;
27 :param `alpha`: an optional transparency value.
30 result = bg + (alpha*(fg - bg))
40 def StepColour(c, ialpha):
42 Darken/lighten the input colour `c`.
44 :param `c`: a colour to darken/lighten;
45 :param `ialpha`: a transparency value.
51 r, g, b = c.Red(), c.Green(), c.Blue()
53 # ialpha is 0..200 where 0 is completely black
54 # and 200 is completely white and 100 is the same
55 # convert that to normal alpha 0.0 - 1.0
56 ialpha = min(ialpha, 200)
57 ialpha = max(ialpha, 0)
58 alpha = (ialpha - 100.0)/100.0
64 alpha = 1.0 - alpha # 0 = transparent fg 1 = opaque fg
70 alpha = 1.0 + alpha # 0 = transparent fg 1 = opaque fg
72 r = BlendColour(r, bg, alpha)
73 g = BlendColour(g, bg, alpha)
74 b = BlendColour(b, bg, alpha)
76 return wx.Colour(r, g, b)
79 def LightContrastColour(c):
81 Creates a new, lighter colour based on the input colour `c`.
83 :param `c`: the input colour to analyze.
88 # if the colour is especially dark, then
89 # make the contrast even lighter
90 if c.Red() < 128 and c.Green() < 128 and c.Blue() < 128:
93 return StepColour(c, amount)
96 def ChopText(dc, text, max_size):
98 Chops the input `text` if its size does not fit in `max_size`, by cutting the
99 text and adding ellipsis at the end.
101 :param `dc`: a `wx.DC` device context;
102 :param `text`: the text to chop;
103 :param `max_size`: the maximum size in which the text should fit.
106 # first check if the text fits with no problems
107 x, y, dummy = dc.GetMultiLineTextExtent(text)
115 for i in xrange(textLen, -1, -1):
119 x, y = dc.GetTextExtent(s)
125 ret = text[0:last_good_length] + "..."
129 def BitmapFromBits(bits, w, h, colour):
131 BitmapFromBits() is a utility function that creates a
132 masked bitmap from raw bits (XBM format).
134 :param `bits`: a string containing the raw bits of the bitmap;
135 :param `w`: the bitmap width;
136 :param `h`: the bitmap height;
137 :param `colour`: the colour which will replace all white pixels in the
141 img = wx.BitmapFromBits(bits, w, h).ConvertToImage()
142 img.Replace(0, 0, 0, 123, 123, 123)
143 img.Replace(255, 255, 255, colour.Red(), colour.Green(), colour.Blue())
144 img.SetMaskColour(123, 123, 123)
145 return wx.BitmapFromImage(img)
148 def IndentPressedBitmap(rect, button_state):
150 Indents the input rectangle `rect` based on the value of `button_state`.
152 :param `rect`: an instance of wx.Rect;
153 :param `button_state`: an L{AuiNotebook} button state.
156 if button_state == AUI_BUTTON_STATE_PRESSED:
165 Returns the face shading colour on push buttons/backgrounds, mimicking as closely
166 as possible the platform UI colours.
169 if wx.Platform == "__WXMAC__":
171 if hasattr(wx, 'MacThemeColour'):
172 base_colour = wx.MacThemeColour(Carbon.Appearance.kThemeBrushToolbarBackground)
174 brush = wx.Brush(wx.BLACK)
175 brush.MacSetTheme(Carbon.Appearance.kThemeBrushToolbarBackground)
176 base_colour = brush.GetColour()
180 base_colour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE)
182 # the base_colour is too pale to use as our base colour,
184 if ((255-base_colour.Red()) +
185 (255-base_colour.Green()) +
186 (255-base_colour.Blue()) < 60):
188 base_colour = StepColour(base_colour, 92)
193 def MakeDisabledBitmap(bitmap):
195 Convert the given image (in place) to a grayed-out version,
196 appropriate for a 'disabled' appearance.
198 :param `bitmap`: the bitmap to gray-out.
201 anImage = bitmap.ConvertToImage()
202 factor = 0.7 # 0 < f < 1. Higher Is Grayer
204 if anImage.HasMask():
205 maskColour = (anImage.GetMaskRed(), anImage.GetMaskGreen(), anImage.GetMaskBlue())
209 data = map(ord, list(anImage.GetData()))
211 for i in range(0, len(data), 3):
213 pixel = (data[i], data[i+1], data[i+2])
214 pixel = MakeGray(pixel, factor, maskColour)
219 anImage.SetData(''.join(map(chr, data)))
221 return anImage.ConvertToBitmap()
224 def MakeGray(rgbTuple, factor, maskColour):
226 Make a pixel grayed-out. If the pixel matches the `maskColour`, it won't be
229 :param `rgbTuple`: a tuple representing a pixel colour;
230 :param `factor`: a graying-out factor;
231 :param `maskColour`: a colour mask.
234 if rgbTuple != maskColour:
236 return map(lambda x: int((230 - x) * factor) + x, (r, g, b))
243 Clips the value in `a` based on the extremes `b` and `c`.
245 :param `a`: the value to analyze;
246 :param `b`: a minimum value;
247 :param `c`: a maximum value.
250 return ((a < b and [b]) or [(a > c and [c] or [a])[0]])[0]
253 def LightColour(colour, percent):
255 Brighten input `colour` by `percent`.
257 :param `colour`: the colour to be brightened;
258 :param `percent`: brightening percentage.
261 end_colour = wx.WHITE
263 rd = end_colour.Red() - colour.Red()
264 gd = end_colour.Green() - colour.Green()
265 bd = end_colour.Blue() - colour.Blue()
269 # We take the percent way of the colour from colour -. white
271 r = colour.Red() + ((i*rd*100)/high)/100
272 g = colour.Green() + ((i*gd*100)/high)/100
273 b = colour.Blue() + ((i*bd*100)/high)/100
274 return wx.Colour(r, g, b)
277 def PaneCreateStippleBitmap():
279 Creates a stipple bitmap to be used in a `wx.Brush`.
280 This is used to draw sash resize hints.
283 data = [0, 0, 0, 192, 192, 192, 192, 192, 192, 0, 0, 0]
284 img = wx.EmptyImage(2, 2)
289 img.SetRGB(ii, jj, data[counter], data[counter+1], data[counter+2])
290 counter = counter + 3
292 return img.ConvertToBitmap()
295 def DrawMACCloseButton(colour, backColour=None):
297 Draws the wxMAC tab close button using `wx.GraphicsContext`.
299 :param `colour`: the colour to use to draw the circle;
300 :param `backColour`: the optional background colour for the circle.
303 bmp = wx.EmptyBitmapRGBA(16, 16)
307 gc = wx.GraphicsContext.Create(dc)
308 gc.SetBrush(wx.Brush(colour))
309 path = gc.CreatePath()
310 path.AddCircle(6.5, 7, 6.5)
314 path = gc.CreatePath()
315 if backColour is not None:
316 pen = wx.Pen(backColour, 2)
318 pen = wx.Pen("white", 2)
320 pen.SetCap(wx.CAP_BUTT)
321 pen.SetJoin(wx.JOIN_BEVEL)
323 path.MoveToPoint(3.5, 4)
324 path.AddLineToPoint(9.5, 10)
325 path.MoveToPoint(3.5, 10)
326 path.AddLineToPoint(9.5, 4)
330 dc.SelectObject(wx.NullBitmap)
334 def DarkenBitmap(bmp, caption_colour, new_colour):
336 Darkens the input bitmap on wxMAC using the input colour.
338 :param `bmp`: the bitmap to be manipulated;
339 :param `caption_colour`: the colour of the pane caption;
340 :param `new_colour`: the colour used to darken the bitmap.
343 image = bmp.ConvertToImage()
344 red = caption_colour.Red()/float(new_colour.Red())
345 green = caption_colour.Green()/float(new_colour.Green())
346 blue = caption_colour.Blue()/float(new_colour.Blue())
347 image = image.AdjustChannels(red, green, blue)
348 return image.ConvertToBitmap()
351 def DrawGradientRectangle(dc, rect, start_colour, end_colour, direction, offset=0, length=0):
353 Draws a gradient-shaded rectangle.
355 :param `dc`: a `wx.DC` device context;
356 :param `rect`: the rectangle in which to draw the gradient;
357 :param `start_colour`: the first colour of the gradient;
358 :param `end_colour`: the second colour of the gradient;
359 :param `direction`: the gradient direction (horizontal or vertical).
362 if direction == AUI_GRADIENT_VERTICAL:
363 dc.GradientFillLinear(rect, start_colour, end_colour, wx.SOUTH)
365 dc.GradientFillLinear(rect, start_colour, end_colour, wx.EAST)
368 def FindFocusDescendant(ancestor):
370 Find a window with the focus, that is also a descendant of the given window.
371 This is used to determine the window to initially send commands to.
373 :param `ancestor`: the window to check for ancestry.
376 # Process events starting with the window with the focus, if any.
377 focusWin = wx.Window.FindFocus()
380 # Check if this is a descendant of this frame.
381 # If not, win will be set to NULL.
386 win = win.GetParent()
394 def GetLabelSize(dc, label, vertical):
396 Returns the L{AuiToolBar} item label size.
398 :param `label`: the toolbar tool label;
399 :param `vertical`: whether the toolbar tool orientation is vertical or not.
402 text_width = text_height = 0
404 # get the text height
405 dummy, text_height = dc.GetTextExtent("ABCDHgj")
408 text_width, dummy = dc.GetTextExtent(label)
412 text_height = text_width
415 return wx.Size(text_width, text_height)
418 #---------------------------------------------------------------------------
419 # TabDragImage implementation
420 # This class handles the creation of a custom image when dragging
422 #---------------------------------------------------------------------------
424 class TabDragImage(wx.DragImage):
426 This class handles the creation of a custom image in case of drag and
427 drop of a notebook tab.
430 def __init__(self, notebook, page, button_state, tabArt):
432 Default class constructor.
434 For internal use: do not call it in your code!
436 :param `notebook`: an instance of L{AuiNotebook};
437 :param `page`: the dragged L{AuiNotebook} page;
438 :param `button_state`: the state of the close button on the tab;
439 :param `tabArt`: an instance of L{AuiDefaultTabArt} or one of its derivations.
442 self._backgroundColour = wx.NamedColour("pink")
443 self._bitmap = self.CreateBitmap(notebook, page, button_state, tabArt)
444 wx.DragImage.__init__(self, self._bitmap)
447 def CreateBitmap(self, notebook, page, button_state, tabArt):
449 Actually creates the drag and drop bitmap.
451 :param `notebook`: an instance of L{AuiNotebook};
452 :param `page`: the dragged L{AuiNotebook} page;
453 :param `button_state`: the state of the close button on the tab;
454 :param `tabArt`: an instance of L{AuiDefaultTabArt} or one of its derivations.
457 control = page.control
458 memory = wx.MemoryDC(wx.EmptyBitmap(1, 1))
460 tab_size, x_extent = tabArt.GetTabSize(memory, notebook, page.caption, page.bitmap, page.active,
461 button_state, control)
463 tab_width, tab_height = tab_size
464 rect = wx.Rect(0, 0, tab_width, tab_height)
466 bitmap = wx.EmptyBitmap(tab_width+1, tab_height+1)
467 memory.SelectObject(bitmap)
469 if wx.Platform == "__WXMAC__":
470 memory.SetBackground(wx.TRANSPARENT_BRUSH)
472 memory.SetBackground(wx.Brush(self._backgroundColour))
474 memory.SetBackgroundMode(wx.TRANSPARENT)
477 paint_control = wx.Platform != "__WXMAC__"
478 tabArt.DrawTab(memory, notebook, page, rect, button_state, paint_control=paint_control)
480 memory.SetBrush(wx.TRANSPARENT_BRUSH)
481 memory.SetPen(wx.BLACK_PEN)
482 memory.DrawRoundedRectangle(0, 0, tab_width+1, tab_height+1, 2)
484 memory.SelectObject(wx.NullBitmap)
486 # Gtk and Windows unfortunatly don't do so well with transparent
487 # drawing so this hack corrects the image to have a transparent
489 if wx.Platform != '__WXMAC__':
490 timg = bitmap.ConvertToImage()
491 if not timg.HasAlpha():
493 for y in xrange(timg.GetHeight()):
494 for x in xrange(timg.GetWidth()):
495 pix = wx.Colour(timg.GetRed(x, y),
498 if pix == self._backgroundColour:
499 timg.SetAlpha(x, y, 0)
500 bitmap = timg.ConvertToBitmap()
504 def GetDockingImage(direction, useAero, center):
506 Returns the correct name of the docking bitmap depending on the input parameters.
508 :param `useAero`: whether L{AuiManager} is using Aero-style or Whidbey-style docking
510 :param `center`: whether we are looking for the center diamond-shaped bitmap or not.
513 suffix = (center and [""] or ["_single"])[0]
516 # Whidbey docking guides
522 if direction == wx.TOP:
523 bmp_unfocus = eval("%sup%s"%(prefix, suffix)).GetBitmap()
524 bmp_focus = eval("%sup_focus%s"%(prefix, suffix)).GetBitmap()
525 elif direction == wx.BOTTOM:
526 bmp_unfocus = eval("%sdown%s"%(prefix, suffix)).GetBitmap()
527 bmp_focus = eval("%sdown_focus%s"%(prefix, suffix)).GetBitmap()
528 elif direction == wx.LEFT:
529 bmp_unfocus = eval("%sleft%s"%(prefix, suffix)).GetBitmap()
530 bmp_focus = eval("%sleft_focus%s"%(prefix, suffix)).GetBitmap()
531 elif direction == wx.RIGHT:
532 bmp_unfocus = eval("%sright%s"%(prefix, suffix)).GetBitmap()
533 bmp_focus = eval("%sright_focus%s"%(prefix, suffix)).GetBitmap()
535 bmp_unfocus = eval("%stab%s"%(prefix, suffix)).GetBitmap()
536 bmp_focus = eval("%stab_focus%s"%(prefix, suffix)).GetBitmap()
538 return bmp_unfocus, bmp_focus
541 def TakeScreenShot(rect):
543 Takes a screenshot of the screen at given position and size (rect).
545 :param `rect`: the screen rectangle for which we want to take a screenshot.
548 # Create a DC for the whole screen area
549 dcScreen = wx.ScreenDC()
551 # Create a Bitmap that will later on hold the screenshot image
552 # Note that the Bitmap must have a size big enough to hold the screenshot
553 # -1 means using the current default colour depth
554 bmp = wx.EmptyBitmap(rect.width, rect.height)
556 # Create a memory DC that will be used for actually taking the screenshot
557 memDC = wx.MemoryDC()
559 # Tell the memory DC to use our Bitmap
560 # all drawing action on the memory DC will go to the Bitmap now
561 memDC.SelectObject(bmp)
563 # Blit (in this case copy) the actual screen on the memory DC
564 # and thus the Bitmap
565 memDC.Blit( 0, # Copy to this X coordinate
566 0, # Copy to this Y coordinate
567 rect.width, # Copy this width
568 rect.height, # Copy this height
569 dcScreen, # From where do we copy?
570 rect.x, # What's the X offset in the original DC?
571 rect.y # What's the Y offset in the original DC?
574 # Select the Bitmap out of the memory DC by selecting a new
575 # uninitialized Bitmap
576 memDC.SelectObject(wx.NullBitmap)
581 def RescaleScreenShot(bmp, thumbnail_size=200):
583 Rescales a bitmap to be 300 pixels wide (or tall) at maximum.
585 :param `bmp`: the bitmap to rescale;
586 :param `thumbnail_size`: the maximum size of every page thumbnail.
589 bmpW, bmpH = bmp.GetWidth(), bmp.GetHeight()
590 img = bmp.ConvertToImage()
592 newW, newH = bmpW, bmpH
595 if bmpW > thumbnail_size:
596 ratio = bmpW/float(thumbnail_size)
597 newW, newH = int(bmpW/ratio), int(bmpH/ratio)
598 img.Rescale(newW, newH, wx.IMAGE_QUALITY_HIGH)
600 if bmpH > thumbnail_size:
601 ratio = bmpH/float(thumbnail_size)
602 newW, newH = int(bmpW/ratio), int(bmpH/ratio)
603 img.Rescale(newW, newH, wx.IMAGE_QUALITY_HIGH)
605 newBmp = img.ConvertToBitmap()
606 otherBmp = wx.EmptyBitmap(newW+5, newH+5)
608 memDC = wx.MemoryDC()
609 memDC.SelectObject(otherBmp)
610 memDC.SetBackground(wx.WHITE_BRUSH)
613 memDC.SetPen(wx.TRANSPARENT_PEN)
616 for i in xrange(5, 0, -1):
617 brush = wx.Brush(wx.Colour(50*i, 50*i, 50*i))
618 memDC.SetBrush(brush)
619 memDC.DrawRoundedRectangle(0, 0, newW+5-pos, newH+5-pos, 2)
622 memDC.DrawBitmap(newBmp, 0, 0, True)
624 # Select the Bitmap out of the memory DC by selecting a new
625 # uninitialized Bitmap
626 memDC.SelectObject(wx.NullBitmap)
631 def GetSlidingPoints(rect, size, direction):
633 Returns the point at which the sliding in and out of a minimized pane begins.
635 :param `rect`: the L{AuiToolBar} tool screen rectangle;
636 :param `size`: the pane window size;
637 :param `direction`: the pane docking direction.
640 if direction == AUI_DOCK_LEFT:
641 startX, startY = rect.x + rect.width + 2, rect.y
642 elif direction == AUI_DOCK_TOP:
643 startX, startY = rect.x, rect.y + rect.height + 2
644 elif direction == AUI_DOCK_RIGHT:
645 startX, startY = rect.x - size.x - 2, rect.y
646 elif direction == AUI_DOCK_BOTTOM:
647 startX, startY = rect.x, rect.y - size.y - 2
649 raise Exception("How did we get here?")
651 caption_height = wx.SystemSettings.GetMetric(wx.SYS_CAPTION_Y)
652 frame_border_x = wx.SystemSettings.GetMetric(wx.SYS_FRAMESIZE_X)
653 frame_border_y = wx.SystemSettings.GetMetric(wx.SYS_FRAMESIZE_Y)
655 stopX = size.x + caption_height + frame_border_x
656 stopY = size.x + frame_border_y
658 return startX, startY, stopX, stopY
661 def CopyAttributes(newArt, oldArt):
663 Copies pens, brushes, colours and fonts from the old tab art to the new one.
665 :param `newArt`: the new instance of L{AuiDefaultTabArt};
666 :param `oldArt`: the old instance of L{AuiDefaultTabArt}.
672 if attr.startswith("_") and (attr.endswith("_colour") or attr.endswith("_font") or \
673 attr.endswith("_font") or attr.endswith("_brush") or \
674 attr.endswith("Pen") or attr.endswith("_pen")):
675 setattr(newArt, attr, getattr(oldArt, attr))