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*):
- One relying on the GDBM module C interface (file c.mstubsG)
- One relying on a GDBM emulation (while I debugged the GDBM interface...): file c.mstubsN).
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:
- OLD (originated from database, validated data);
- NEW (new data not yet validated);
- MOD (unvalidated data for which an old version resides in the database).
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;
}