!AddressB

(a small address book)
Current release is v0.4.4
Author: Benoît GILON
Logo de HTMLEdit v3
Back to the main !AddressB WWW page

Contents


Overview

!AddressB is an application suite which can serve as a basis for developing a "Names" database. !AddressB is both the name of the application suite and the name of one of its component application. In is current release !AddressB is composed of four modules (each one having its own dedicated Wimp task): Obviously, the geographical association table is a child table of both the "Identity" and the "Geographical location" table. So: We can say the same regarding the resources table and the Identity table. Each of those applications can be started separately or grouped (by running the !Address0 application). Group starting the applications allows to quickly benefit from the inter-application data communication built in the design of those applications. Each application can be considered as:

Implementation notes

Storage part

The basic idea was to use the GDBM RISC OS module ported by Steve Ellacott. However, I was forced to patch the module's wrapper (file c.gdbm in the GDBM module archive) to make !AddressB work with the module; the only change to apply is to substitute the strncpy function calls with memcpy function calls as my application store binary data, not only zero terminated string data.
I designed two versions for the storage module (files c.mstubs*): The preprocessing directive GDBM is used to differentiate between the two options from within the code.
The record format exchanged between disk and RAM memory thru GDBM and for each component application is:
!AddressB component
typedef struct
        {
        int key;
        struct
                {
                char titre;
                char nom[32];
                char prenom[32];
                } data;
        } tmdonnees;
!AddressG component
typedef struct
        {
        int key;
        struct
                {
                char country;
                char typevoie[16];
                int  numvoie;
                char nom[32];
                char zipcode[16];
                char commune[32];
                } data;
        } tmdonnees;
!Address0 component
typedef struct
        {
        int key;
        struct
                {
                int ididentity, idaddress;
                int type;
                } data;
        } tmdonnees;
!AddressI component
typedef struct
        {
        int key;
        struct
                {
                int ididentity;
                int type /* E-Mail, Web etc... */, kind /* Private, business */;
                char url[64];
                } data;
        } tmdonnees;
The module in charge of handling all interactions between GUI and the storage interface is c.gdbmfront. A state variable holds the status of the current record (being displayed). It can be either: Here is the initialization part for the gdbmfront module (bgig_init function)(other applications initialization functions are similar); as control exists from this function, the gkey is set to the max value of all keys in the database file and the record being displayed is the first record in the database (if not empty) otherwise the NEW value is forced in the state variable and the window appearance is updated by calling the refresh function. Also a chained list memory structure holds every key value stored in the database, this list will support the browse operations as the user clicks on one of the arrow adjuster gadgets.
static void mrefresh(void)
{
_kernel_oserror *p;
char buffer[8];
p= stringset_set_selected(StringSet_IndexedSelection, woid,
        KSTRSETTITLE, (char *) (mesdonnees.data.titre -
 '0') /* pseltitre[mesdonnees.data.titre - '0'] */);
if(!p) p= writablefield_set_value(0, woid,
        KWRTFLDNOM, mesdonnees.data.nom);
if(!p) p= writablefield_set_value(0, woid,
        KWRTFLDPRE, mesdonnees.data.prenom);
sprintf(buffer,state == OLD ? "%u" : "%u *", currkey);
if(!p) p= window_set_title(0, woid, buffer);
ERROR_CHECK(p)
}
...
void bgig_refresh(const ObjectId oid, const int key)
{
int res;
mesdonnees.key= key;
res= gdbm_fetch(mdbf, mkey, mval);
if(res != -1) mrefresh();
}
...
static _kernel_oserror *mgadgetsetf(const ComponentId cid, const int value)
{
return gadget_set_flags(0, toid, cid, value);
}

void refresh(void)
{
struct MinNode *pnode;
_kernel_oserror *p;
char buffer[8];
int bF= state == OLD ? 0 : Gadget_Faded;
p= menu_set_fade(0, wmenuoid, KWMCOPYENT, bF);
if(!p) p= gadget_set_flags(0, woid, KACTBUTOK, state == OLD ? Gadget_Faded : 0);
if(!p) p= gadget_set_flags(0, woid, KACTBUTCANCEL, state == OLD ? Gadget_Faded : 0);
if(!p) p= mgadgetsetf(KACTBUTNEW, bF);
if(!p) p= mgadgetsetf(KACTBUTDEL, bF);
if(!p)  {
        if(state!= OLD)
                {
                p= mgadgetsetf(KADJPREV, Gadget_Faded);
                if(!p) p= mgadgetsetf(KADJNEXT, Gadget_Faded);
                }
        else    {
                pnode= (struct MinNode *) ((char *) pcurkey + offsetof(tkey, mnode));
                p= mgadgetsetf(KADJNEXT, pnode->mln_Succ->mln_Succ ? 0 : Gadget_Faded);
                if(!p) p= mgadgetsetf(KADJPREV, maliste.mlh_Head == pnode ? Gadget_Faded : 0);
                }
        }
if(!p && (state == NEW))
        {
        mesdonnees.data.titre= '0';
        strcpy(mesdonnees.data.nom, "Gilon");
        strcpy(mesdonnees.data.prenom, "Benoît");
        mrefresh();
        }
sprintf(buffer,state == OLD ? "%u" : "%u *", currkey);
if(!p) p= window_set_title(0, woid, buffer);
ERROR_CHECK(p);
}
...
void bgig_init(void)
{
int res;
mkey.dptr= (void *) &(mesdonnees.key);
mkey.dsize= 4;
mval.dptr= (void *) &(mesdonnees.data);
mval.dsize= sizeof(mesdonnees.data);
mdbf= gdbm_open("<AddressB$Dir>.Resources.Address");
if(mdbf == (gdbm_file_info *) -1) return;
atexit(bgig_atex);
NewList((struct List *) &maliste);
event_register_toolbox_handler(toid, KACTBUTNEWEVENT, new_event, 0);
event_register_toolbox_handler(toid, KACTBUTDELEVENT, del_event, 0);
event_register_toolbox_handler(woid, KACTBUTOKEVENT, ok_event, 0);
event_register_toolbox_handler(woid, KACTBUTCANEVENT, can_event, 0);
event_register_toolbox_handler(toid, Adjuster_Clicked, prevnext_event, 0);
event_register_toolbox_handler(woid, WritableField_ValueChanged, vchanged_event, 0); 
event_register_toolbox_handler(woid, StringSet_ValueChanged, vchanged_event, 0); 

pseltitre[0]= lookup_token("TITLE0:Mister");
pseltitre[1]= lookup_token("TITLE1:Missis");
pseltitre[2]= lookup_token("TITLE2:Miss");
pseltitre[3]= lookup_token("TITLE3:Doctor");

clipboard_init();
/* Find next key value */
#ifdef GDBM
res= gdbm_firstkey(mdbf, mkey);
#else
res= gdbm_firstkey(mdbf, mkey, sizeof(mesdonnees.data));
#endif
if(res != -1)
        {
        currkey= mesdonnees.key;
        do
                {
                if(!new_tkey(mesdonnees.key)) break;
                if(mesdonnees.key > gkey) gkey= mesdonnees.key;
#ifdef GDBM
                } while(gdbm_nextkey(mdbf, mkey, mkey)!= -1);
#else
                } while(gdbm_nextkey(mdbf, mkey, mkey, sizeof(mesdonnees.data))!= -1);
#endif
        state= OLD;
        gkey++;
        pcurkey= find_tkey(currkey);
        bgig_refresh(woid, currkey);
        }
else
        {
        currkey= gkey++;
        state= NEW;
        }
refresh();
}
The code excuted as the users clicks on one of the adjuster arrow gadgets is given below:
static int next_event(ToolboxEvent *event, void *handle)
{
struct MinNode *pnode= (struct MinNode *) ((char *) pcurkey + offsetof(tkey, mnode));
pnode= pnode->mln_Succ;
pcurkey= (tkey *) ((char *) pnode - offsetof(tkey, mnode));
currkey= pcurkey->key;
bgig_refresh(woid, currkey);
(void) mgadgetsetf(KADJPREV, 0);
if(!(pnode->mln_Succ->mln_Succ)) (void) mgadgetsetf(KADJNEXT, Gadget_Faded);
return 0;
}

static int prev_event(ToolboxEvent *event, void *handle)
{
struct MinNode *pnode= (struct MinNode *) ((char *) pcurkey + offsetof(tkey, mnode));
pnode= pnode->mln_Pred;
pcurkey= (tkey *) ((char *) pnode - offsetof(tkey, mnode));
currkey= pcurkey->key;
bgig_refresh(woid, currkey);
(void) mgadgetsetf(KADJNEXT, 0);
if(maliste.mlh_Head == pnode) (void) mgadgetsetf(KADJPREV, Gadget_Faded);
return 0;
}

static int prevnext_event(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
return (id_block->self_component == KADJNEXT) ? next_event(event, handle) : prev_event(event, handle);
}
bgi_gstore is used to apply screen changes back to the datafile as the user clicks on the OK action button.
void bgig_store(const ObjectId oid)
{
_kernel_oserror *p;
int nbytes;
int is;
p= stringset_get_selected(StringSet_IndexedSelection, oid,
        KSTRSETTITLE, &is, sizeof(int), &nbytes);
if(!p)  {
        mesdonnees.data.titre= is + '0';
        p= writablefield_get_value(0, oid,
        KWRTFLDNOM, mesdonnees.data.nom, sizeof(mesdonnees.data.nom), &nbytes);
        }
if(!p)  {
        mesdonnees.data.nom[nbytes]= '\0';
        p= writablefield_get_value(0, oid,
        KWRTFLDPRE, mesdonnees.data.prenom, sizeof(mesdonnees.data.prenom), &nbytes);
        }
if(!p)  {
        mesdonnees.data.prenom[nbytes]= '\0';
        mesdonnees.key= currkey;
        gdbm_store(mdbf, mkey, mval);
        }
ERROR_CHECK(p)
}
...
static int ok_event(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
bgig_store(woid);
if(state == NEW) new_tkey(currkey);
state= OLD;
refresh();
return 1;
}

Final notes

Here are some of the things to be inserted in a "todo" list: