Once the component object model is clearly defined, you can start implementing the classes.
The best method is to have one source file and one header file for each class and its related virtual classes. See below for more information about virtual classes.
Here is an extract from the Qt component source directory:
$ pwd ~/gambas/src/lib/qt $ ls CButton.cpp CIconView.cpp CScreen.cpp main.cpp CButton.h CIconView.h CScreen.h main.h ...
You can declare several classes in the same file, but you should generally avoid it. Things are clearer that way.
What is a virtual class? It is a class which represents a sub-component
of a class, but which you cannot instanciate nor reference into a
variable. For example, the property Item
of the Qt component ListBox
class is a
virtual class that represents a ListBox
item.
Virtual classes are just used as datatypes by the interpreter. But the
object used behind is the real object coming from the real non-virtual
class. For example, the Item property of the ListBox
class stores the
index of the item you want to deal with in the ListBox
object, and
returns this ListBox
object. The ListBox
object becomes then a virtual
class object that you cannot store in a variable. As you must use the
virtual class object immediately, by calling a method or a property
on it, the stored index will be used immediately too.
This mechanism has been designed so that the user manipulates temporary objects, without being compelled to create them. It is SO much faster!
Note that the name of a virtual class must begin with a dot. For
example, the name of the virtual class used by the Item
property is
.ListBoxItem
.
A class source file contains:
The structure of the class header file is as follows:
/* MyClass.h */ #ifndef __MYCLASS_H #define __MYCLASS_HInclude
main.h
there, and any other includes needed by the declarations located in this file.
#include "main.h"If the class is instanciable, declare the structure of your objects. Note that the structure must begin with a GB_BASE field, and that the other fields are free.
typedef struct { GB_BASE ob; ... } MyClassStruct;
main.c
will include the class include files, so it must have an
access to the class description.
#ifndef __CEXAMPLE_C extern GB_DESC MyClass[]; extern GB_DESC MyVirtualClassDesc[];Otherwise, you can declare useful macros that helps writing the class implmentation. For example, the followinf constant makes the code more readable. Note that it can only be used where
_object
is declared, i.e. inside a method or property implementation.
#else #define THIS ((MyClassStruct *)_object) #endif #endif /* __MYCLASS_H */
The structure of the class source file is as follows:
/* MyClass.c */ #define __CEXAMPLE_CInclude the class header file, and any other include files needed by the contents of the source file.
#includeIf your class raises events, you must declare them with the DECLARE_EVENT macro.#include ... #include "MyClass.h"
DECLARE_EVENT(FirstEvent); DECLARE_EVENT(SecondEvent); ...Include any static functions that may be needed by your implementation.
static void do_job(...) { ... }Then write the implementation of each method and property.
BEGIN_METHOD(...) ... END_METHOD BEGIN_PROPERTY(...) ... END_METHODFinally, the last part of the class source file is the declaration of its description.
A class description is an array of GB_DESC structure filled
with special macros declared in gambas.h
GB_DESC MyClassDesc[] = { GB_DECLARE("MyClass", sizeof(MyClassStruct)), ... GB_END_DECLARE };
The class description must begin with a GB_DECLARE
macro and end
with a GB_END_DECLARE
macro.
Use the GB_DECLARE
macro to declare the name of the class and the
size of its object.
If the class is virtual, add a line with the GB_VIRTUAL_CLASS
macro. Don't forget that the class name must begin with a dot, and the size of the object must be
equal to zero.
If the class is normal, but not creatable by the user, add a line with the GB_NOT_CREATABLE
macro.
Then you add a declaration line for each class symbol.
Your class can inherit from another class, by using the
GB_INHERITS
macro and specifying the parent class name.
A class inherits from its parent all methods, properties, constants and events.
Moreover, the class object structure must include the parent object structure at its beginning. Otherwise, the inheritance will not work!
Example:
The TreeView class inherits from the Control.
The Control
object structure is as follows:
typedef struct { GB_BASE ob; QWidget *widget; ... } CWIDGET;
And the TreeView
object structure is as follows:
typedef struct { CWIDGET widget; ... } CTREEVIEW;
A constant is depicted by its name, its type and its value.
To declare a constant, use the GB_CONSTANT
macro.
See the macro description for more information.
A property is depicted by its name, its type, and its implementation function.
To declare a property, use the GB_PROPERTY
macro, or the
GB_STATIC_PROPERTY
macro if the property is static.
If the property is read-only, you must use the GB_PROPERTY_READ
or
GB_STATIC_PROPERTY_READ
macro instead.
If you want a special property that returns the same object as a virtual class, use the
GB_PROPERTY_SELF
or
GB_STATIC_PROPERTY_SELF
macro.
See the macro description for more information.
A method is depicted by its name, its return type, its signature and its implementation function.
To declare a method, use the GB_METHOD
macro, or the
GB_STATIC_METHOD
macro if the method is static.
See the macro description for more information.
An event is depicted by its name, its return type, and its signature.
To declare an event, use the GB_EVENT
macro. This macro takes a variable
pointer as an argument to let the interpreter allocate an identifier to this event. This variable
must be declared in the source file with the DECLARE_EVENT
macro.
See the macro descriptions for more information.
To implement a method, you must write a function whose code is enclosed
between the two macros: BEGIN_METHOD
and
END_METHOD
.
The BEGIN_METHOD
macro takes TWO arguments:
the function name, and a list of arguments separated by semicolons.
The method arguments are NOT separated by commas, because they are in reality fields of a structure passed to the function.
If your method takes no argument, you must use the BEGIN_METHOD_VOID
instead of BEGIN_METHOD
. The BEGIN_METHOD_VOID
macro takes only one argument, the name
of the function.
The gambas.h
include file contains definitions for the argument types:
GB_BOOLEAN
for a boolean argument.
GB_INTEGER
for an integer argument.
GB_FLOAT
for a double argument.
GB_STRING
for a string argument.
GB_DATE
for a date argument.
GB_VARIANT
for a variant argument.
GB_OBJECT
for a object reference.
You MUST use these datatypes!
To get the parameters, you have two macros : ARG()
and
VARG()
.
The ARG()
macro returns the address of the parameter in the interpreter stack, and
is used with functions like GB.ToZeroString()
, or
GB.Store()
.
The VARG()
macro returns the value of the parameter.
Example :
BEGIN_METHOD ( TheFunctionName , GB_INTEGER anInteger; GB_STRING aString; GB_VARIANT aVariant; GB_BOOLEAN aBoolean; )To get the value of a parameter, you must use the
VARG
macro.
printf("anInteger = %d\n", VARG(anInteger));To get a string parameter, you must use the special macros
STRING
and
LENGTH
to get the address of the string and its length.
printf("aString = %*.s\n", LENGTH(aString), STRING(aString));You can also transform the Gambas string into a C zero-terminated string with the
GB.ToZeroString
function. You must use the ARG
macro to
get the parameter address, and not the VARG
macro that returns its value.
printf("aString = %.s\n", GB.ToZeroString(ARG(aString)));
GB_VARIANT
is a union of different datatypes. The type field of this union
is one of the GB_T_* constants.
if (VARG(aVariant).type == GB_T_STRING) printf("I got the following string: %s\n", VARG(aVariant)._string.value);
GB_BOOLEAN
is stored as an integer, which is zero when FALSE
, and different from
zero when TRUE
.
printf("aBoolean = %s\n", VARG(aBoolean) ? "TRUE" : "FALSE");If you want to raise an error into your method, you must use the
GB.Error()
function to register the error,
and returns immediately after.
if (VARG(aBoolean)) { GB.Error("There was an error!"); return; }To return a value from the method, you can use the
GB.Return()
interface
functions: GB.ReturnInteger()
to return a integer,
GB.ReturnBoolean()
to return a boolean, and so on.
GB.ReturnInteger(VARG(anInteger) * 2); END_METHOD
To implement a property, you must write a function whose code is enclosed
between the two macros : BEGIN_PROPERTY
and
END_PROPERTY
.
The BEGIN_PROPERTY
macro takes one argument: the property name.
The function is called both for reading and writing the property. To
distinguish between the two cases, you must use the READ_PROPERTY
macro. Of course, if your property is read-only, this is not necessary.
When reading the property, you must return the property value with one of the
GB.Return
functions.
When writing the property, you get the value to write with the VPROP
macro. This macro takes one argument: the datatype of the property, which must be one
of the Gambas value structure defined in gambas.h
:
GB_BOOLEAN
for a boolean argument.
GB_INTEGER
for an integer argument.
GB_FLOAT
for a double argument.
GB_STRING
for a string argument.
GB_DATE
for a date argument.
GB_VARIANT
for a variant argument.
GB_OBJECT
for a object reference.
Use the PROP
macro to get the address of the value, if you want to
use functions like like GB.ToZeroString()
, or
GB.Store()
.
Example:
BEGIN_PROPERTY ( ThePropertyName )First, you must check if we want to read or to write the property.
if (READ_PROPERTY) {Here we are reading the property...
printf("Returning the property value\n");The
THIS
macro is defined in the class header file. It returns a pointer
to the data of the object structure.
We suppose here that there is a char * AStringProperty
defined in the object
structure, that points to a Gambas string.
GB.ReturnString(THIS->AStringProperty); } else {Here we are writing the property...
To store a complex Gambas datatype like String
or Object
, you must use
GB.StoreString()
or
GB.StoreObject()
. These functions deal with
reference counting.
printf("I'm going to write the value: %s\n", GB.ToZeroString(PROP(GB_STRING))); GB.StoreString(PROP(GB_STRING), &THIS->AStringProperty);Generally, a modified property implies some other actions...
printf("Property has been modified. The new value is %s\n", THIS->AStringProperty); } END_PROPERTY