!AddressB

(un petit carnet d'adresses)
La version actuelle est la v0.4.4
Auteur: Benoît GILON
Logo de HTMLEdit v3
Retour à la page principale de !AddressB

Sommaire


Présentation

!AddressB est une suite d'applications toolbox ayant comme ambition de devenir votre agenda personnel. !AddressB est à la fois le nom de la suite comme le nom d'un module particulier la composant. Dans la version actuelle, !AddressB est découpée en quatre modules (chaque module donnant lieu à l'existence d'une application Wimp dédiée):
  1. Gestion des identités des contacts (application !AddressB);
  2. Gestion des lieux géographiques (application !AddressG);
  3. Gestion des associations entre lieux géographiques et identités (application !Address0). En effet, un lieu géographique peut regrouper plusieurs individus; réciproquement, un individu peut potentiellement occuper plusieurs lieux géographiques (bureau et domicile par ex.)
  4. Gestion des ressources (désignées par des URLs) rattachée à une personne (application !AddressI)
La table des associations est bien sûr ici "fille" de chacune des tables "Identité" et "Lieu géographique". A ce titre: Réciproquement, puisque il y a possibilité au sein de chacune des applications !AddressB et !AddressG de vérifier l'absence de références au sein de l'application !Address0 à des données devant être supprimées dans chacune des application !AddressB et !AddressG, le lancement de l'une de ces deux applications entraine alors le lancement en conséquence de l'application !Address0.

Un tel lien relit aussi les tables d'identités (!AddressB) et de ressources URL (!AddressI).

Chacune de ces applications peuvent s'exécuter séparément ou en groupe. Le démarrage en groupe permet toutefois de bénéficier des échanges inter-applications prévus dans le design. Chaque application composante peut être considérée comme:

Notes d'implémentation

Stockage de données

L'idée de base a été d'utiliser le module RISC OS GDBM porté par Steve Ellacott. Cependant, j'ai été contraint de patcher le "wrapper" du module (fichier c.gdbm dans l'archive GDBM) pour faire marcher !AddressB au dessus du module; le seul changement de ce dernier a été de substituer les appels à la fonction strncpy par des appels à la fonction memcpy puisque mon application stocke des données binaires et non uniquement des chaines de caractères terminées par des caractères nuls.
J'ai alors conçu deux versions du module d'interface pour le stockage des données (fichiers c.mstubs*) La macro GDBM est utilisée au sein du code source pour différencier les deux options.
Les formats d'enregistrements échangés entre le disque et la mémoire vive à travers GDBM et pour chaque module composant est le suivant:
Composant !AddressB
typedef struct
        {
        int key;
        struct
                {
                char titre;
                char nom[32];
                char prenom[32];
                } data;
        } tmdonnees;
Composant !AdressG
typedef struct
        {
        int key;
        struct
                {
                int  numvoie;
                char typevoie;
                char country;
                char nom[32];
                char zipcode[16];
                char commune[32];
                } data;
        } tmdonnees;
Composant !Address0
typedef struct
        {
        int key;
        struct
                {
                int ididentity, idaddress;
                int type;
                } data;
        } tmdonnees;
Composant !AddressI
typedef struct
        {
        int key;
        struct
                {
                int ididentity;
                int type /* E-Mail, Web etc... */, kind /* Privée, professionnelle */;
                char url[64];
                } data;
        } tmdonnees;
Le module chargé de la gestion des interactions entre l'IHM et l'interface de stockage est c.gdbmfront. Une variable d'état permet de connaitre le status de l'enregistrement courant (en cours d'affichage). Sa valeur peut être: Ce qui suit est la fonction d'initialisation du module gdbmfront du composant !AddressB (fonction bgig_init) (les fonctions d'initialisation des autres composants sont semblables); lorsque cette fonction se termine, la variable gkey est positionnée à la valeur maximale de toutes les clés d'enregistrement de la base de données et l'enregistrement courant est le premier enregistrement de la base (si cette dernire est non vide), sinon la valeur NEW est forcée dans la variable state et la fonction refresh est appelèe pour mettre à jour l'apparence de la fenêtre. De même une chaine de structures en mémoire contient toutes les valeurs de clé de la base de données , c'est à travers le parcours de cette liste que l'utilisateur peut naviguer avec les touches Flèches gauche et droite.
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();
}
Le code s'exécutant lorsque l'utilisateur clique sur l'une des f;èches de navigation et donné ci-dessous:
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 est utiliser pour appliquer dans la base de données les changement opérés dans la fenêtre de saisie, cela dès que l'utilisateur a cliqué sur le bouton OK.
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;
        if(!p) 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;
}

Notes finales

Voici quelques travaux sur lesquels se pencher (sans aller jusqu'à s'endormir) pour une prochaine version: