# -*- coding: utf-8 -*- #---------------------------------------------------------------------------- # Name: divided.py # Purpose: DividedShape class # # Author: Pierre Hjälm (from C++ original by Julian Smart) # # Created: 2004-05-08 # Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart # Licence: wxWindows license # Tags: phoenix-port, unittest, py3-port, documented #---------------------------------------------------------------------------- """ The :class:`~lib.ogl.divided.DividedShape` class. """ import sys import wx from .basic import ControlPoint, RectangleShape, Shape from .oglmisc import * class DividedShapeControlPoint(ControlPoint): """The class:`DividedShapeControlPoint` class.""" def __init__(self, the_canvas, object, region, size, the_m_xoffset, the_m_yoffset, the_type): """ Default class constructor. :param `theCanvas`: a :class:`~lib.ogl.Canvas` :param `object`: not used ??? :param `region`: an instance of :class:`~lib.ogl.ShapeRegion` :param float `size`: the size :param float `the_m_xoffset`: the the_m_xoffset position ??? :param float `the_m_yoffset`: the the_m_yoffset position ??? :param int `the_type`: one of the following types ======================================== ================================== Control point type Description ======================================== ================================== `CONTROL_POINT_VERTICAL` Vertical `CONTROL_POINT_HORIZONTAL` Horizontal `CONTROL_POINT_DIAGONAL` Diagonal ======================================== ================================== """ ControlPoint.__init__(self, the_canvas, object, size, the_m_xoffset, the_m_yoffset, the_type) self.regionId = region def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0): """The drag left handler.""" dc = wx.MemoryDC() dc.SelectObject(self.GetCanvas()._Buffer) dc.SetLogicalFunction(OGLRBLF) dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT) dc.SetPen(dottedPen) dc.SetBrush(wx.TRANSPARENT_BRUSH) dividedObject = self._shape x1 = dividedObject.GetX() - dividedObject.GetWidth() / 2.0 y1 = y x2 = dividedObject.GetX() + dividedObject.GetWidth() / 2.0 y2 = y dc.DrawLine(x1, y1, x2, y2) def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0): """The begin drag left handler.""" dc = wx.MemoryDC() dc.SelectObject(self.GetCanvas()._Buffer) dc.SetLogicalFunction(OGLRBLF) dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_DOT) dc.SetPen(dottedPen) dc.SetBrush(wx.TRANSPARENT_BRUSH) dividedObject = self._shape x1 = dividedObject.GetX() - dividedObject.GetWidth() / 2.0 y1 = y x2 = dividedObject.GetX() + dividedObject.GetWidth() / 2.0 y2 = y dc.DrawLine(x1, y1, x2, y2) self._canvas.CaptureMouse() def OnEndDragLeft(self, x, y, keys = 0, attachment = 0): """The end drag left handler.""" dc = wx.MemoryDC() dc.SelectObject(self.GetCanvas()._Buffer) dividedObject = self._shape if not dividedObject.GetRegions()[self.regionId]: return thisRegion = dividedObject.GetRegions()[self.regionId] nextRegion = None dc.SetLogicalFunction(wx.COPY) if self._canvas.HasCapture(): self._canvas.ReleaseMouse() # Find the old top and bottom of this region, # and calculate the new proportion for this region # if legal. currentY = dividedObject.GetY() - dividedObject.GetHeight() / 2.0 maxY = dividedObject.GetY() + dividedObject.GetHeight() / 2.0 # Save values theRegionTop = 0 nextRegionBottom = 0 for i in range(len(dividedObject.GetRegions())): region = dividedObject.GetRegions()[i] proportion = region._regionProportionY yy = currentY + dividedObject.GetHeight() * proportion actualY = min(maxY, yy) if region == thisRegion: thisRegionTop = currentY if i + 1 < len(dividedObject.GetRegions()): nextRegion = dividedObject.GetRegions()[i + 1] if region == nextRegion: nextRegionBottom = actualY currentY = actualY if not nextRegion: return # Check that we haven't gone above this region or below # next region. if y <= thisRegionTop or y >= nextRegionBottom: return dividedObject.EraseLinks(dc) # Now calculate the new proportions of this region and the next region thisProportion = float(y - thisRegionTop) / dividedObject.GetHeight() nextProportion = float(nextRegionBottom - y) / dividedObject.GetHeight() thisRegion.SetProportions(0, thisProportion) nextRegion.SetProportions(0, nextProportion) self._yoffset = y - dividedObject.GetY() # Now reformat text for i, region in enumerate(dividedObject.GetRegions()): if region.GetText(): s = region.GetText() dividedObject.FormatText(dc, s, i) dividedObject.SetRegionSizes() dividedObject.Draw(dc) dividedObject.GetEventHandler().OnMoveLinks(dc) class DividedShape(RectangleShape): """ A :class:`DividedShape` is a rectangle with a number of vertical divisions. Each division may have its text formatted with independent characteristics, and the size of each division relative to the whole image may be specified. """ def __init__(self, w, h): """ Default class constructor. :param `w`: width of rectangle :param `h`: heigth of rectangle """ RectangleShape.__init__(self, w, h) self.ClearRegions() def OnDraw(self, dc): """The draw handler.""" RectangleShape.OnDraw(self, dc) def OnDrawContents(self, dc): """The draw contents handler.""" if self.GetRegions(): defaultProportion = 1.0 / len(self.GetRegions()) else: defaultProportion = 0.0 currentY = self._ypos - self._height / 2.0 maxY = self._ypos + self._height / 2.0 leftX = self._xpos - self._width / 2.0 rightX = self._xpos + self._width / 2.0 if self._pen: dc.SetPen(self._pen) dc.SetTextForeground(self._textColour) # For efficiency, don't do this under X - doesn't make # any visible difference for our purposes. if sys.platform[:3] == "win": dc.SetTextBackground(self._brush.GetColour()) if self.GetDisableLabel(): return xMargin = 2 yMargin = 2 dc.SetBackgroundMode(wx.TRANSPARENT) for region in self.GetRegions(): dc.SetFont(region.GetFont()) dc.SetTextForeground(region.GetActualColourObject()) if region._regionProportionY < 0: proportion = defaultProportion else: proportion = region._regionProportionY y = currentY + self._height * proportion actualY = min(maxY, y) centreX = self._xpos centreY = currentY + (actualY - currentY) / 2.0 DrawFormattedText(dc, region._formattedText, centreX, centreY, self._width - 2 * xMargin, actualY - currentY - 2 * yMargin, region._formatMode) if y <= maxY and region != self.GetRegions()[-1]: regionPen = region.GetActualPen() if regionPen: dc.SetPen(regionPen) dc.DrawLine(leftX, y, rightX, y) currentY = actualY def SetSize(self, w, h, recursive = True): """ Set the size. :param `w`: width of rectangle :param `h`: heigth of rectangle :param `recursive`: not implemented """ self.SetAttachmentSize(w, h) self._width = w self._height = h self.SetRegionSizes() def SetRegionSizes(self): """ Set all region sizes according to proportions and this object total size. """ if not self.GetRegions(): return if self.GetRegions(): defaultProportion = 1.0 / len(self.GetRegions()) else: defaultProportion = 0.0 currentY = self._ypos - self._height / 2.0 maxY = self._ypos + self._height / 2.0 for region in self.GetRegions(): if region._regionProportionY <= 0: proportion = defaultProportion else: proportion = region._regionProportionY sizeY = proportion * self._height y = currentY + sizeY actualY = min(maxY, y) centreY = currentY + (actualY - currentY) / 2.0 region.SetSize(self._width, sizeY) region.SetPosition(0, centreY - self._ypos) currentY = actualY def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None): """ Get the attachment position. Attachment points correspond to regions in the divided box. :param `attachment`: the attachment ??? :param `nth`: get nth attachment ??? :param `no_arcs`: ??? :param `line`: ??? """ totalNumberAttachments = len(self.GetRegions()) * 2 + 2 if self.GetAttachmentMode() == ATTACHMENT_MODE_NONE or attachment >= totalNumberAttachments: return Shape.GetAttachmentPosition(self, attachment, nth, no_arcs) n = len(self.GetRegions()) isEnd = line and line.IsEnd(self) left = self._xpos - self._width / 2.0 right = self._xpos + self._width / 2.0 top = self._ypos - self._height / 2.0 bottom = self._ypos + self._height / 2.0 # Zero is top, n + 1 is bottom if attachment == 0: y = top if self._spaceAttachments: if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE: # Align line according to the next handle along point = line.GetNextControlPoint(self) if point[0] < left: x = left elif point[0] > right: x = right else: x = point[0] else: x = left + (nth + 1) * self._width / (no_arcs + 1.0) else: x = self._xpos elif attachment == n + 1: y = bottom if self._spaceAttachments: if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE: # Align line according to the next handle along point = line.GetNextControlPoint(self) if point[0] < left: x = left elif point[0] > right: x = right else: x = point[0] else: x = left + (nth + 1) * self._width / (no_arcs + 1.0) else: x = self._xpos else: # Left or right isLeft = not attachment < (n + 1) if isLeft: i = totalNumberAttachments - attachment - 1 else: i = attachment - 1 region = self.GetRegions()[i] if region: if isLeft: x = left else: x = right # Calculate top and bottom of region top = self._ypos + region._y - region._height / 2.0 bottom = self._ypos + region._y + region._height / 2.0 # Assuming we can trust the absolute size and # position of these regions if self._spaceAttachments: if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE: # Align line according to the next handle along point = line.GetNextControlPoint(self) if point[1] < bottom: y = bottom elif point[1] > top: y = top else: y = point[1] else: y = top + (nth + 1) * region._height / (no_arcs + 1.0) else: y = self._ypos + region._y else: return False return x, y def GetNumberOfAttachments(self): """ Get the number of attachments. There are two attachments for each region (left and right), plus one on the top and one on the bottom. """ n = len(self.GetRegions()) * 2 + 2 maxN = n - 1 for point in self._attachmentPoints: if point._id > maxN: maxN = point._id return maxN + 1 def AttachmentIsValid(self, attachment): """ Is the attachment valid? :param `attachment`: the attachment """ totalNumberAttachments = len(self.GetRegions()) * 2 + 2 if attachment >= totalNumberAttachments: return Shape.AttachmentIsValid(self, attachment) else: return attachment >= 0 def MakeControlPoints(self): """ Make the control points. """ RectangleShape.MakeControlPoints(self) self.MakeMandatoryControlPoints() def MakeMandatoryControlPoints(self): """ Make the mandatory control points. """ currentY = self.GetY() - self._height / 2.0 maxY = self.GetY() + self._height / 2.0 for i, region in enumerate(self.GetRegions()): proportion = region._regionProportionY y = currentY + self._height * proportion actualY = min(maxY, y) if region != self.GetRegions()[-1]: controlPoint = DividedShapeControlPoint(self._canvas, self, i, CONTROL_POINT_SIZE, 0, actualY - self.GetY(), 0) self._canvas.AddShape(controlPoint) self._controlPoints.append(controlPoint) currentY = actualY def ResetControlPoints(self): """ Reset the control points. :note: May only have the region handles, (n - 1) of them """ if len(self._controlPoints) > len(self.GetRegions()) - 1: RectangleShape.ResetControlPoints(self) self.ResetMandatoryControlPoints() def ResetMandatoryControlPoints(self): """ Reset the mandatory control points. """ currentY = self.GetY() - self._height / 2.0 maxY = self.GetY() + self._height / 2.0 i = 0 for controlPoint in self._controlPoints: if isinstance(controlPoint, DividedShapeControlPoint): region = self.GetRegions()[i] proportion = region._regionProportionY y = currentY + self._height * proportion actualY = min(maxY, y) controlPoint._xoffset = 0 controlPoint._yoffset = actualY - self.GetY() currentY = actualY i += 1 def EditRegions(self): """ Edit the region colours and styles. Not implemented. """ print("EditRegions() is unimplemented") def OnRightClick(self, x, y, keys = 0, attachment = 0): """The right click handler.""" if keys & KEY_CTRL: self.EditRegions() else: RectangleShape.OnRightClick(self, x, y, keys, attachment)