Hacking Calc - Adding Functions
From OpenOffice.org Wiki
The OpenOffice.org (OO.o) Calc source code has a lot going on, and adding new features often requires a number of steps. For those interested in starting, it is recommended they try the O3-build and Hacking Calc - The First Step. The latter article provides a very good overview of function organization. This tutorial will provide you with a way to add your own functions to the software, and see how to find functions based on their names in the actual software. It is a quick and dirty guide, and does not go into OO.o theory.
Please note that this guide is based on OO.o 2.2, and requires a good understanding of C++ programming.
Contents |
Defining A Function
As mentioned earlier, this article assumes you're using the O3-build and treats the <O3 HOME> tag as the home directory for the source code. We'll be implementing the Fibonacci sequence.
sc/source/core/src/compiler.src
Function names are stored in <O3 HOME>/sc/source/core/src/compiler.src. For example, Calc has the popular mathematical function Cos() available for users, and by searching the code for "COS", one will eventually find:
[cpp,N]
String SC_OPCODE_COS
{
Text [ de ] = "COS"; Text [ en-US ] = "COS";
};
Note that from milestone SRC680_m210 on the German resource strings are treated as normal translations, so they don't appear anymore in *.src files but are merged into the localize.sdf files instead.
To begin adding a function, we need to define our own description, which could be placed below the SC_OPCODE_COS one in the RID_SC_FUNCTION_NAMES resource.
[cpp,N]
String SC_OPCODE_FIB
{
Text [ en-US ] = "FIB";
};
Be sure to add the English-only name to the resource RID_SC_FUNCTION_NAMES_ENGLISH as well, which is used in document file storage. English-only resources that are not to be translated do not use an ISO language-country code marker.
[cpp,N]
String SC_OPCODE_FIB { Text = "FIB"; };
sc/inc/compiler.hrc
SC_OPCODE_FIB needs to be defined in a second file <O3 HOME>/sc/inc/compiler.hrc but in this case, needs a unique integer to represent it. Scroll down to:
[cpp,N]
- define SC_OPCODE_BAHTTEXT 133
- define SC_OPCODE_END_1_PAR 200
Now add your own function between the two, corresponding to 134. The final step deals with <O3 HOME>/sc/inc/opcode.hxx where the functions are enumerated. Add your function to the list, with a new variable. In our case, the top of the file was changed to:
[cpp,N]
enum OpCodeEnum
{
ocFib = SC_OPCODE_FIB, ocPush = SC_OPCODE_PUSH, ocJump = SC_OPCODE_JUMP,
// File continues...//
For good cosmetics we usually don't place new function opcode enums at the top but insert them where they topically belong, e.g. in the section with functions taking one parameter near the SIN function.
Making It Work
Most of the code corresponding to functions are stored within the ScInterpreter, in one of six different files. Each file is stored in <O3 home>/sc/source/core/tool/interprX.cxx where "X" ranges from 1 to 6.
sc/source/core/tool/interpr4.cxx
The first important part of in this step includes adding your function to the interpreter. Open interpr4.cxx and find the method ScInterpreter::Interpret(). Within this method, you'll see a listing of all the functions again, and we can add our own:
[cpp,N]
switch( eOp )
{
case ocSep : case ocClose : // pushed by the compiler case ocMissing : ScMissing(); break; // We add the line below. case ocFib : ScFib(); break; // File continues as before...
This tells Calc what to call when FIB() is written in a cell.
sc/source/core/inc/interpre.hxx
Our next step is to tell the Interpeter that this function exists, and this is done by editing the <O3 HOME>/sc/source/core/inc/interpre.hxx file, where we simply add ScFib() to the list:
[cpp,N]
// File starts...
double Compare();
ScMatrixRef CompareMat();
void ScEqual();
// We now add ScFib() below.
void ScFib();
// And the file continues...
sc/source/core/tool/interpr?.cxx
Now we define our function. Technically it doesn't matter which interprX.cxx file we use, again, these are arranged a bit topically and with common sense we may find the suiting place. So let's continue where we started, near Cos(). Open <O3 HOME>/sc/source/core/tool/interpr1.cxx and search for "ScCos()". We can add our function above to it. The code is below, with comments to explain what parts of the code do:
[cpp,N]
void ScInterpreter::ScFib()
{
int fib1 = 1; int fib2 = 1; int fibcur = 0;
// The code below obtains a double from the formula call. // i.e. If we use FIB(3.2), GetDouble() returns 3.2. double d = GetDouble();
// Get an int from the function. int num = (int)floor(d);
// We use PushInt() below for our return value to FIB().
if (num < 1) {
PushInt(-1);
} else if (num == 1) {
PushInt(fib1);
} else if (num == 2) {
PushInt(fib2);
} else {
for (int i = 3; i <= num; i++) {
fibcur = fib1 + fib2;
fib1 = fib2;
fib2 = fibcur;
}
PushInt(fibcur);
};
}
Now you're done! Compile and run.
UI polishing
Until now we're able to enter the function's name in a formula and it will be compiled and interpreted. However, it will not show up in the function auto pilot, nor will it be possible to add online help content specific to this function.
sc/source/ui/src/scfuncs.src
TODO: add description and sample
sc/inc/scfuncs.hrc
TODO: add description and sample
util/hidother.src
TODO: add description and sample
Additional considerations
Depending on the type of functionality added, the interpreter may have to be told how to treat arguments under certain conditions.
sc/source/core/tool/parclass.cxx
TODO: add description and sample

