The function prototype is
Code: Select all
PyDraw(dims, ShapeName, FillSpecs, ShapeText)
dims (required)
This is an array or CellRange specifying the ShapeType, sheet, position, size, and optional rotaion. Each row is a shape.
Column 1 is the sheet number.
Column 2 is the x coordinate in centimeters of the upper left corner of the shape's bounding rectangle. (0,0) is the upper left corner of Cell A1, of course.
Column 3 is the y coordinate of the upper left corner, this increases down the page.
Columns 4 and 5 are the width and height in cm.
Column 6 is degrees of counterclockwise rotation about the shape's center. This can change the actual position such that the shape is partially rotated off the sheet, but I didn't think this would be much of a problem.
Column 7 is the shape type: 0 is a RectangleShape, 1 is an EllipseShape, and 2 is a TextShape. A TextShape here doesn't seem to be anything more than a RectangleShape without the default boundary lines. In the case of an EllipseShape, the width and height are the major and minor axes, which, of course, becomes a circle when these are equal.
The ShapeName is used by the function to name the shapes. When they are inserted, the function names them ShapeName_0, ShapeName_1, etc. With this scheme in place, the function can delete all its previous entries when the parameters are changed prior to inserting new ones.
The FillSpecs are actually optional, but will generally be specified, and are also where most of the action is,
If the entry in the first column is a number, the FillStyle will be a solid color specified by that number, and the second column is an optional transparency percentage, 0-100.
The first column may also be a letter (case insensitive), specifying the FillStyle. The options are
n - NONE
s - SOLID, a solid color. For this, the second column becomes the color number and the third column is the transparency %.
g - GRADIENT. If this is chosen, the second column is the name as normally seen in the UI, "Gradient 1," "Rectangular red/white," etc., and the third column is the transparency.
h - HATCH. Second column name, third column transparency.
b - BITMAP. Second column name ("Metal," "Sky"...), third column transparency.
u - Also BITMAP, but the second column is a url for the bitmap instead of the name.
Then we have optional ShapeText.
The first column is the text to be entered in the shape.
The second column offers some formatting options: b - bold, u - underline, i - italic.
The third column specifies the TextVerticalAdjust and the fourth column is the TextHorizontalAdjust. These should be entered by their actual names, TOP, LEFT, BLOCK, etc., though I have made it case-insensitive.
Columns five, six and seven are the character height (points), the font color (long), and the font name.
It will be noted, of course, that much, much, more could be done with the text formatting.
The following picture show the result of entering =PYDRAW(A2:G5;"JackFlash";H2:J5;K2:Q5) in cell A9. The return value is just the ShapeName, but it will also include error information, such as invalid coordinates or fill name.
One unusual thing about this function is that it's results remain even if the function is deleted from its cell. That is, the shapes remain - so I also provide a utility function DELPYDRAW, which when entered with a ShapeName, will delete all the corresponding shapes from all sheets, returning the number of shapes deleted. DELPYDRAW may then be deleted after its job is done.
Another quirk is that multiple instances of the function can have the same ShapeName. In this case, the last instance calculated "wins," deleting the shapes from all previously calculated instances.
Here is the .idl
Code: Select all
#include <com/sun/star/uno/XInterface.idl>
#include <com/sun/star/beans/XPropertySet.idl>
module com { module pydraw {
interface XPyDraw
{
string PyDraw( [in] sequence< sequence< any > > dims, [in] string ShapeName, [in] any FillSpecs, [in] any ShapeText);
long delPyDraw( [in] string ShapeName );
};
}; };
Code: Select all
import uno
import unohelper
import math
from com.pydraw import XPyDraw
from com.sun.star.awt import Size, Point
from com.sun.star.drawing import HomogenMatrix3, HomogenMatrixLine3
#Set up enum classes.
class FillStyle():
from com.sun.star.drawing.FillStyle import (NONE, SOLID, GRADIENT, HATCH, BITMAP)
class BitmapMode():
from com.sun.star.drawing.BitmapMode import (REPEAT, STRETCH, NO_REPEAT)
class FontWeight():
from com.sun.star.awt.FontWeight import (NORMAL, BOLD)
class FontUnderline():
from com.sun.star.awt.FontUnderline import (SINGLE, NONE)
class FontSlant():
from com.sun.star.awt.FontSlant import (NONE, ITALIC)
class TextHorizontalAdjust():
from com.sun.star.drawing.TextHorizontalAdjust import (LEFT, CENTER, RIGHT, BLOCK)
class TextVerticalAdjust():
from com.sun.star.drawing.TextVerticalAdjust import (TOP, CENTER, BOTTOM, BLOCK)
# Draw Shapes add-in function.
class PyDrawImpl( unohelper.Base, XPyDraw ):
def __init__( self, ctx ):
self.ctx = ctx
def PyDraw(self, dims, ShapeName, FillSpecs, ShapeText):
desktop = self.ctx.getServiceManager().createInstanceWithContext("com.sun.star.frame.Desktop", self.ctx)
oDoc = desktop.getCurrentComponent()
oSheets = oDoc.getSheets()
#Gets style for default text properties.
defaultStyle = oDoc.getStyleFamilies().getByName("CellStyles").getByName("Default")
ShapeServices = ["com.sun.star.drawing.RectangleShape","com.sun.star.drawing.EllipseShape","com.sun.star.drawing.TextShape"]
ShapesIn = getShapes(dims)
#Erase any existing shapes named ShapeName_number
self.delPyDraw(ShapeName)
ReturnStr = ShapeName
hasError = False
pyShapes= []
#Check shapes for valid coordinates and type.
for i in range(len(ShapesIn)):
ShapeError = False
pyShape = ShapesIn[i]
sheet, x, y, w, h, Rotate, ShapeType = map(float,pyShape)
sheet = int(sheet)
if sheet < 0 or sheet >= oSheets.getCount():
if not ShapeError:
ShapeError = True
ReturnStr = ReturnStr + ": " if not hasError else ReturnStr + ", "
else:
ReturnStr = ReturnStr + ", "
ReturnStr = ReturnStr + "Shape " + str(i) + " Sheet out of range"
else:
oSheet = oSheets.getByIndex(sheet)
x, y, w, h, ShapeType = long(1000*x), long(1000*y), long(1000*w), long(1000*h), int(ShapeType)
SheetPos = oSheet.Position
SheetSize = oSheet.Size
if (x < SheetPos.X or x + w > SheetPos.X + SheetSize.Width) or (y < SheetPos.Y or y + h > SheetPos.Y + SheetSize.Height) or (w <= 0 or h <= 0):
if not ShapeError:
ShapeError = True
ReturnStr = ReturnStr + ": " if not hasError else ReturnStr + ", "
else:
ReturnStr = ReturnStr + ", "
ReturnStr = ReturnStr + "Shape " + str(i) + " dimensions out of range"
elif ShapeType < 0 or ShapeType > 2:
if not ShapeError:
ShapeError = True
ReturnStr = ReturnStr + ": " if not hasError else ReturnStr + ", "
else:
ReturnStr = ReturnStr + ", "
ReturnStr = ReturnStr + "Shape " + str(i) + " Invalid shape type"
#add shape if it's OK.
if not ShapeError:
pyShapes.append(pyShape)
else:
hasError = True
#pyShape = [sheet,x,y,width,height,theta,type]
fillList = [FillStyle.NONE,FillStyle.SOLID,FillStyle.GRADIENT,FillStyle.HATCH,FillStyle.BITMAP,FillStyle.BITMAP]
#Set defaults for Gradient, Hatch, and Bitmap fill.
fillDefaults = ["Gradient 1","Black 0 degrees","Blank"]
#Insert the shapes
for i in range(len(pyShapes)):
pyShape = pyShapes[i]
sheet, x, y, w, h, Rotate, ShapeType = map(float,pyShape)
x, y, w, h, ShapeType = long(1000*x), long(1000*y), long(1000*w), long(1000*h), int(ShapeType)
filltrans = 0
fillurl = False
if type(FillSpecs) == tuple:
FillSpec = FillSpecs[i] if len(FillSpecs) > i else [float(-1)]
if type(FillSpec[0]) == float:
fillstyle = FillStyle.SOLID
FillColor = int(FillSpec[0])
# Read transparency value from second column.
filltrans = 0 if len(FillSpec) < 2 or type(FillSpec[1]) != float else int(FillSpec[1]) if 0 < FillSpec[1] <= 100 else 0
elif type(FillSpec[0]) == unicode:
if len(FillSpec[0]) > 0:
#Get fill type from letter code.
fillIndex = "nsghbu".find(FillSpec[0][0].lower())
if fillIndex != -1:
fillstyle = fillList[fillIndex]
fillurl = False if fillIndex < 5 else True
else:
ReturnStr = ReturnStr + ": " if not hasError else ReturnStr + ", "
ReturnStr = ReturnStr + "Shape " + str(i) + " Invalid fill, set to SOLID"
hasError = True
else:
fillstyle = FillStyle.SOLID #default to solid color.
if fillstyle in [FillStyle.GRADIENT,FillStyle.HATCH,FillStyle.BITMAP]:
#Get the fill name
if len(FillSpec) > 1 and type(FillSpec[1]) == unicode:
fillname = FillSpec[1] if len(FillSpec[1]) > 0 else fillDefaults[fillIndex - 2]
else:
fillname = fillDefaults[fillIndex - 2]
#Read transparency value from third column
filltrans = 0 if len(FillSpec) < 3 or type(FillSpec[2]) != float else int(FillSpec[2]) if 0 < FillSpec[2] <= 100 else 0
elif type(FillSpecs) == float:
#Default to solid color fill for all shapes with color FillSpecs
fillstyle = FillStyle.SOLID
FillColor = int(FillSpecs)
else:
fillstyle = FillStyle.SOLID
FillColor = -1
hasText = False
if type(ShapeText) == tuple:
TextSpec = ShapeText[i] if len(ShapeText) > i else [""]
ShapeString = ""
if type(TextSpec[0]) == unicode and len(TextSpec[0]) > 0:
hasText = True
ShapeString = TextSpec[0]
if hasText:
if len(TextSpec) > 1 and type(TextSpec[1]) == unicode and len(TextSpec[1]) > 0:
TextBold = FontWeight.BOLD if "b" in TextSpec[1].lower() else FontWeight.NORMAL
TextUnder = FontUnderline.SINGLE if "u" in TextSpec[1].lower() else FontUnderline.NONE
TextItalic = FontSlant.ITALIC if "i" in TextSpec[1].lower() else FontSlant.NONE
else:
TextBold = FontWeight.NORMAL
TextUnder = FontUnderline.NONE
TextItalic = FontSlant.NONE
if len(TextSpec) > 2 and type(TextSpec[2]) == unicode:
if TextSpec[2].upper() == "TOP":
TextVertical = TextVerticalAdjust.TOP
elif TextSpec[2].upper() == "BOTTOM":
TextVertical = TextVerticalAdjust.BOTTOM
elif TextSpec[2].upper() == "BLOCK":
TextVertical = TextVerticalAdjust.BLOCK
else:
TextVertical = TextVerticalAdjust.CENTER
else:
TextVertical = TextVerticalAdjust.CENTER
if len(TextSpec) > 3 and type(TextSpec[3]) == unicode:
if TextSpec[3].upper() == "LEFT":
TextHorizontal = TextHorizontalAdjust.LEFT
elif TextSpec[3].upper() == "RIGHT":
TextHorizontal = TextHorizontalAdjust.RIGHT
elif TextSpec[3].upper() == "BLOCK":
TextHorizontal = TextHorizontalAdjust.BLOCK
else:
TextHorizontal = TextHorizontalAdjust.CENTER
else:
TextHorizontal = TextHorizontalAdjust.CENTER
if len(TextSpec) > 4 and type(TextSpec[4]) == float:
TextHeight = TextSpec[4] if TextSpec[4] > 0 else defaultStyle.CharHeight
else:
TextHeight = defaultStyle.CharHeight
if len(TextSpec) > 5 and type(TextSpec[5]) == float:
TextColor = long(TextSpec[5]) if TextSpec[5] > 0 else defaultStyle.CharColor
else:
TextColor = defaultStyle.CharColor
if len(TextSpec) > 6 and type(TextSpec[6]) == unicode:
TextFontName = TextSpec[6] if len(TextSpec[6]) > 0 else defaultStyle.CharFontName
else:
TextFontName = defaultStyle.CharFontName
oShape = oDoc.createInstance(ShapeServices[ShapeType])
oShape.Name = ShapeName + "_" + str(i)
sheet = pyShape[0]
oShapes = oSheets.getByIndex(sheet).getDrawPage()
oShapes.add(oShape)
oShape.FillStyle = fillstyle
try:
if fillstyle == FillStyle.GRADIENT:
oShape.FillGradientName = fillname
elif fillstyle == FillStyle.HATCH:
oShape.FillHatchName = fillname
elif fillstyle == FillStyle.BITMAP:
if not fillurl:
oShape.FillBitmapName = fillname
else:
oShape.FillBitmapURL = fillname
oShape.FillBitmapMode = BitmapMode.STRETCH
else:
oShape.FillColor = FillColor
except:
ReturnStr = ReturnStr + ": " if not hasError else ReturnStr + ", "
ReturnStr = ReturnStr + "Shape " + str(i) + " Invalid fill name, set to default."
hasError = True
if fillstyle == FillStyle.GRADIENT:
oShape.FillGradientName = "Gradient 1"
elif fillstyle == FillStyle.HATCH:
oShape.FillHatchName = "Black 0 degrees"
elif fillstyle == FillStyle.BITMAP:
oShape.FillBitmapName = "Blank"
else:
oShape.FillColor = FillColor
if hasText:
try:
oShape.String = ShapeString
oShape.CharWeight = TextBold
oShape.CharUnderline = TextUnder
oShape.CharPosture = TextItalic
oShape.CharHeight = TextHeight
oShape.CharColor = TextColor
oShape.CharFontName = TextFontName
oShape.TextHorizontalAdjust = TextHorizontal
oShape.TextVerticalAdjust = TextVertical
except:
ReturnStr = ReturnStr + ": " if not hasError else ReturnStr + ", "
ReturnStr = ReturnStr + "Shape " + str(i) + " Invalid text format, set to default"
hasError = True
oShape.String = ShapeString
oShape.CharWeight = defaultStyle.CharWeight
oShape.CharUnderline = defaultStyle.CharUnderline
oShape.CharPosture = defaultStyle.CharPosture
oShape.CharHeight = defaultStyle.CharHeight
oShape.CharColor = defaultStyle.CharColor
oShape.CharFontName = defaultStyle.CharFontName
oShape.TextHorizontalAdjust = TextHorizontalAdjust.CENTER
oShape.TextVerticalAdjust = TextVerticalAdjust.CENTER
oShape.FillTransparence = filltrans
s = Size(w, h)
p = Point(x, y)
theta = math.radians(Rotate)
oShape.setSize(s)
oShape.setPosition(p)
#Apply rotation
if theta != 0:
tMatrix = HomogenMatrix3()
l1 = HomogenMatrixLine3()
l2 = HomogenMatrixLine3()
X = oShape.getPropertyValue("Transformation")
xMatrix = [[0,0],[0,0]]
xMatrix[0][0] = X.Line1.Column1
xMatrix[0][1] = X.Line1.Column2
xMatrix[1][0] = X.Line2.Column1
xMatrix[1][1] = X.Line2.Column2
#Rotate the transformation matrix.
stheta = math.sin(theta)
ctheta = math.cos(theta)
l1.Column1 = xMatrix[0][0] * ctheta + xMatrix[1][0] * stheta
l2.Column1 = -xMatrix[0][0] * stheta + xMatrix[1][0] * ctheta
l1.Column2 = xMatrix[0][1] * ctheta + xMatrix[1][1] * stheta
l2.Column2 = -xMatrix[0][1] * stheta + xMatrix[1][1] * ctheta
#Adjust position to rotate about center.
l1.Column3 = X.Line1.Column3 + long((w - w * ctheta - h * stheta)/2)
l2.Column3 = X.Line2.Column3 + long((h - h * ctheta + w * stheta)/2)
tMatrix.Line1 = l1
tMatrix.Line2 = l2
tMatrix.Line3 = X.Line3
oShape.setPropertyValue("Transformation",tMatrix)
return ReturnStr
#Deletes all shapes named ShapeName_number
def delPyDraw(self, ShapeName):
desktop = self.ctx.getServiceManager().createInstanceWithContext("com.sun.star.frame.Desktop", self.ctx)
oDoc = desktop.getCurrentComponent()
ShapeServices = ["com.sun.star.drawing.RectangleShape","com.sun.star.drawing.EllipseShape","com.sun.star.drawing.TextShape"]
oSheets = oDoc.getSheets()
delCount = 0
for i in range(oSheets.getCount()):
oSheet = oSheets.getByIndex(i)
oShapes = oSheet.getDrawPage()
c = oShapes.getCount()
while c > 0:
oShape = oShapes.getByIndex(c - 1)
if oShape.getShapeType() in ShapeServices and oShape.getName().startswith(ShapeName + "_"):
oShapes.remove(oShape)
delCount += 1
c -= 1
return delCount
def getShapes(dims):
dimlen = len(dims)
dimlen0 = len(dims[0])
pyShapes = [tuple([int(dims[i][0]) if type(dims[i][0]) == float else 0] + [dims[i][j] if j < dimlen0 else 0 for j in range(1,7)]) for i in range(dimlen)]
return pyShapes
def createInstance( ctx ):
return PyDrawImpl( ctx )
g_ImplementationHelper = unohelper.ImplementationHelper()
g_ImplementationHelper.addImplementation(
createInstance,"com.pydraw.python.PyDrawImpl",
("com.sun.star.sheet.AddIn",),)