We access the storage system by using two selectors, each of which is made
of 4 bits, theoretically providing up to 16 x 16 areas.
The first selector addresses what is called a main entry or mode. These
modes are mainly used by the built-in applications, here the are:
- 10h: TEL
- 20h: TEL (Company)
- 30h: Memo
- 40h: Schedule
- 50h: Spreadsheet
- 60h: Mail
- 70h: EXPENSE
- 80h: Clock (City setting)
- 90h: Dual window
- A0h: ADD IN
- B0h: Quick Memo
For each of these entries we have 16 sub entries or sub modes, addressed
by the second selector.
Now, let's mind our own business and just focus the attention to ADD IN mode (0xA0).
We can store our data here. Moreover this section lets us assign a name to each
subentry (Other main entry's submodes don't have names). ADD IN section has
specialized APIs for 'names management', here are some:
- LibSubEntrySearch
- LibSubEntrySave
- LibSubEntryDel
- LibSubEntryRename
- LibGetAllEntry
Anyway this doesn't mean we only have 16 files!!! At this point data is
further divided into records. How many records? Well, as many as the total memory
allows, i guess.
Each submode manages a pool of fixed size blocks. We can allocate, free, read and
write them. But HOW?
FILE_BUF and FILE_INFO structures, LibFileRead, LibFileWrite will save our day.
FILE_BUF is about data definition. FILE_INFO is about data operations.
FILE_BUF
Now don't waste your time with all the fields this structure shows. We only need:
- fsb_main_entry_
- fsb_sub_entry_
- fsb_scrt_info_
- fbuf.text.fsb_text_buf_[2048+1]
- fbuf.bin.char_num
- fbuf.bin.bin_buf[3072]
'fsb_main_entry_' is the main entry selector (0xA0) for ADD IN.
'fsb_sub_entry_' is the submode selector.
'fsb_scrt_info_' specifies whether the block we're working with is hidden
in the secret area or not. Set it to FILE_SECRET_MODE to hide it or else set it to
FILE_OPEN_MODE.
Now it makes difference whether we're working with text or binary data.
If working with text we use 'fbuf.text.fsb_text_buf_'. It's a 2k buffer and
accepts a NULL terminated BIG BIG string.
That's the reason why MEMO stores messages no greater than 2k.
If working with binary data we use 'fbuf.bin.bin_buf' and put or get the data size
in 'fbuf.bin.char_num'. Whatever we choose, these buffers are stored into memory
by LibFileWrite and filled up by LibFileRead.
FILE_INFO
It only has 2 fields, 'fp' which identifies the block we're working with
and 'kind', which says whether the block is binary FILE_KIND_BIN or text
FILE_KIND_TEXT
ADD IN
If we're working with something other than ADD IN we must fill these
structures by hand. While, with ADD IN, some functions will help us.
- LibSubEntrySearch searches for a named submode and returns its selector.
- LibSubEntrySave associates a name to a free submode and returns its selector.
- LibGetAllEntry returns both the main and sub entries.
Well, time to put it all together.
The following code lets us configure, store and retrieve one byte of information.
It shows an arrays of 8 special buttons (bits) to configure data. By clicking
on a button we alternately set and clear the bit. Two buttons are provided to
load and save the information. Try setting a configuration, save it, quit and
run the application again, you will see the saved data back.
#include <stdrom.h>
#include "define.h"
#include "libc.h"
Here's what we have. One button that we are going to cut into 8 pieces,
and the load and save buttons.
#define BITS_X 30
#define BITS_Y 10
#define BITS_W 100
#define BITS_H 10
#define BUTTONS_X1 50
#define BUTTONS_X2 110
#define BUTTON_LOAD_Y1 50
#define BUTTON_LOAD_Y2 69
#define BUTTON_SAVE_Y1 80
#define BUTTON_SAVE_Y2 99
#define BUTTON_LOAD_CAPTION "Load"
#define BUTTON_SAVE_CAPTION "Save"
#define OBJ_LOAD 0xc012
#define OBJ_SAVE 0xc013
#define OBJ_BITS 0xc014
Let's use a named submode and call it "Example". We wrap all those long chains
of identifiers, in the structure, with macros. We'd better put FILE_BUF variables into
global space, rather than local. Even though someone says that in the smallest
memory model the stack gets 8k, when i put this structure inside a function,
i had troubles with the stack. Is sizeof FILE_BUF something like 8K? I don't
know, i don't feel like finding out. BlockNumber reminds us which block
data is stored into. Data is... well guess.
#define ENTRYNAME "Example"
#define SUBENTRY(f) ((f).fsb_sub_entry_)
#define MAINENTRY(f) ((f).fsb_main_entry_)
#define AREA(f) ((f).fsb_scrt_info_)
#define BUFFER(f) ((f).fbuf.bin.bin_buf)
#define BSIZE(f) ((f).fbuf.bin.char_num)
FILE_BUF fb;
int RecordNumber;
byte Data;
void DoLoad();
void DoSave();
We should actually add 8 + 2 buttons, but we cheat ;P
TCHTBL TchList[4] =
{
/* Load button */
BUTTONS_X1, BUTTON_LOAD_Y1,
BUTTONS_X2, BUTTON_LOAD_Y2,
ACT_MAKE,
OBJ_LOAD,
0x0000,
/* Save button */
BUTTONS_X1, BUTTON_SAVE_Y1,
BUTTONS_X2, BUTTON_SAVE_Y2,
ACT_MAKE,
OBJ_SAVE,
0x0000,
/* Bits button(s) */
BITS_X, BITS_Y,
BITS_X + BITS_W, BITS_Y + BITS_H,
ACT_MAKE,
OBJ_BITS,
0x0000,
/* End */
0, 0, 0, 0,
ACT_NONE,
OBJ_END,
0x0000
};
With this function the program says hello to the storage system and checks the
situation. We LibSubEntrySearch to see if a previous instance of the program has
already allocated things. If this is the case we know we stored the byte in block 0,
we just have to DoLoad it. On the contrary, if no subentry named "Example" exists,
we create it (LibSubEntrySave). If we fail, we're nice enough to let the program
go on, but we have to tell the next data functions not to operate (RecordNumber = -1).
We created an entry, what are its mode and submode? Don't think about it, ask
LibGetAllEntry, it stores the corresponding values into fb. We want to save the
byte into the open area, we have nothing to hide ;) We save our value as binary
in the first position of the bin_buf(fer). If we got to here, that's because
nothing has been saved yet. So we must ask the system for a NEW block. If we want
to allocate a NEW block we have to set FILE_INFO's file pointer to 0xFFFF.
LibFileWrite will return us the number of the block it allocated, placing it into
the same field (fp). Since we just defined a whole subentry, why should we expect it
doesn't return 0? If there's a clear blue sky why should it rain....?
UPDATE!!!
...and as it always happens it rained!!! It's a mistake to believe that for every
new subentry, the first block we get is '0'. I guess the block number has a global
nature, it is not just related to its current subentry. So the programmer must run
after the blocks he/she stored with a fly-catcher. TWISTED just TWISTED, it's like
we have to remember where we placed what we wanted to remember.
WORKAROUND
Well, the old version of the sample did only work on the emulator, since, unless you
add other stuff, it contains no data stored. The code did not work on my PV
until i made the modification marked in red. If our data is already in memory
we have to search for the block number we allocated, LibFileFindNext being
our fly-catcher, or just imagine the programmer having to knock on every block's
door. WHY !?!?!
void DataSetup()
{
FILE_INFO fi;
Data = 0;
if (LibSubEntrySearch(ENTRYNAME, &SUBENTRY(fb)))
{
bool res;
if (!LibGetAllEntry(ENTRYNAME, &MAINENTRY(fb), &SUBENTRY(fb)))
{
LibPutMsgDlg("LibGetAllEntry error");
RecordNumber = -1;
return;
}
AREA(fb) = FILE_OPEN_MODE;
fi.fp = 0xFFFF;
fi.kind = FILE_KIND_BIN;
res = LibFileFindNext(&fb, &fi, 0x00);
if (res)
{
RecordNumber = fi.fp;
DoLoad();
}
else
RecordNumber = -1;
return;
}
if (!LibSubEntrySave(ENTRYNAME, &SUBENTRY(fb)))
{
LibPutMsgDlg("LibSubEntrySave error");
RecordNumber = -1;
return;
}
if (!LibGetAllEntry(ENTRYNAME, &MAINENTRY(fb), &SUBENTRY(fb)))
{
LibPutMsgDlg("LibGetAllEntry error");
RecordNumber = -1;
return;
}
AREA(fb) = FILE_OPEN_MODE;
memset(BUFFER(fb), 0, 3072);
BSIZE(fb) = 1;
*BUFFER(fb) = Data;
fi.fp = 0xFFFF;
fi.kind = FILE_KIND_BIN;
if (!LibFileWrite(&fb, &fi))
{
LibPutMsgDlg("LibFileWrite error");
RecordNumber = -1;
return;
}
RecordNumber = fi.fp;
}
This loads the byte into 'Data'. If something went wrong (RecordNumber == -1) we
do nothing, else LibGetAllEntry fills the 'entry' fields with values referring to
our name. This time we know which block we're addressing and tell it (fi.fp =
RecordNumber).
If it is alright after LibFileRead, the bin_buf(fer) contains, in the first position,
the value previously stored.
void DoLoad()
{
FILE_INFO fi;
if (RecordNumber == -1) return;
if (!LibGetAllEntry(ENTRYNAME, &MAINENTRY(fb), &SUBENTRY(fb)))
{
LibPutMsgDlg("LibGetAllEntry error");
return;
}
AREA(fb) = FILE_OPEN_MODE;
fi.fp = RecordNumber;
fi.kind = FILE_KIND_BIN;
if (!LibFileRead(&fb, &fi))
LibPutMsgDlg("LibFileRead error");
Data = *BUFFER(fb);
}
This saves the byte. This time we don't need to allocate a NEW block, we want to
rewrite the old one, so fi.fp = 'the block we want to override'.
void DoSave()
{
FILE_INFO fi;
if (RecordNumber == -1) return;
if (!LibGetAllEntry(ENTRYNAME, &MAINENTRY(fb), &SUBENTRY(fb)))
{
LibPutMsgDlg("LibGetAllEntry error");
return;
}
AREA(fb) = FILE_OPEN_MODE;
fi.fp = RecordNumber;
fi.kind = FILE_KIND_BIN;
*BUFFER(fb) = Data;
if (!LibFileWrite(&fb, &fi))
LibPutMsgDlg("LibFileWrite error");
}
This shows an array of small buttons.
void ShowBits()
{
int i, bit_w;
byte acc;
int x1, y1, x2, y2;
bit_w = BITS_W / 8;
acc = 0x80;
for (i = 0; i < 8; i++)
{
x1 = BITS_X + bit_w * i;
y1 = BITS_Y;
x2 = BITS_X + bit_w * (i + 1);
y2 = BITS_Y + BITS_H;
LibGdsBox(x1, y1, x2, y2);
LibGdsClr(x1 + 2, y1 + 2, x2 - 2, y2 - 2);
if (Data & acc)
{
LibGdsReverse(x1 + 2, y1 + 2, x2 - 2, y2 - 2);
}
acc >>= 1;
}
LibPutDispBox(BITS_X, BITS_Y, BITS_W + 1, BITS_H + 1);
}
The main 'data and display' setup function.
void Setup()
{
int sz;
DataSetup();
LibClrDisp();
LibGdsBox(BUTTONS_X1, BUTTON_LOAD_Y1, BUTTONS_X2, BUTTON_LOAD_Y2);
sz = LibGetProStrSize(IB_PFONT2, BUTTON_LOAD_CAPTION);
LibPutProStr(IB_PFONT2,
BUTTONS_X1 + (BUTTONS_X2 - BUTTONS_X1 - sz) / 2,
BUTTON_LOAD_Y1 + (BUTTON_LOAD_Y2 - BUTTON_LOAD_Y1 - 9) / 2,
BUTTON_LOAD_CAPTION, sz);
LibGdsBox(BUTTONS_X1, BUTTON_SAVE_Y1, BUTTONS_X2, BUTTON_SAVE_Y2);
sz = LibGetProStrSize(IB_PFONT2, BUTTON_SAVE_CAPTION);
LibPutProStr(IB_PFONT2,
BUTTONS_X1 + (BUTTONS_X2 - BUTTONS_X1 - sz) / 2,
BUTTON_SAVE_Y1 + (BUTTON_SAVE_Y2 - BUTTON_SAVE_Y1 - 9) / 2,
BUTTON_SAVE_CAPTION, sz);
LibPutDisp();
ShowBits();
}
Just because we haven't provided the bitmaps for the buttons it doesn't mean
we can't show some press-release action...
void DoAnimate(int x1, int y1, int x2, int y2)
{
LibGdsReverse(x1, y1, x2, y2);
LibPutDispBox(x1, y1, x2 - x1, y2 - y1);
LibWait(IB_125MWAIT);
LibGdsReverse(x1, y1, x2, y2);
LibPutDispBox(x1, y1, x2 - x1, y2 - y1);
}
If we press Load or Save we animate the button, perform its action and show the
results. If we clicked on the BIG configuration button we have to see which bit we
selected. Some formulas saves us from adding 8 touch areas :)
void AnimateButtons(TCHSTS *tsts)
{
int bit, mask;
if (tsts->obj == OBJ_LOAD)
{
DoAnimate( BUTTONS_X1, BUTTON_LOAD_Y1,
BUTTONS_X2, BUTTON_LOAD_Y2);
DoLoad();
ShowBits();
}
if (tsts->obj == OBJ_SAVE)
{
DoAnimate( BUTTONS_X1, BUTTON_SAVE_Y1,
BUTTONS_X2, BUTTON_SAVE_Y2);
DoSave();
}
if (tsts->obj == OBJ_BITS)
{
bit = (tsts->x - BITS_X) / (BITS_W / 8);
mask = 0x80 >> bit;
if (Data & mask)
Data &= ~mask;
else
Data |= mask;
ShowBits();
}
}
No comment.
void main()
{
TCHSTS tsts;
bool bExit = FALSE;
LibTchStackClr();
LibTchStackPush(NULL);
LibTchStackPush(TchHardIcon);
LibTchStackPush(&TchList[0]);
Setup();
LibTchInit();
for (;bExit == FALSE;)
{
LibTchWait(&tsts);
if (tsts.obj == OBJ_HIC_ESC)
bExit = TRUE;
AnimateButtons(&tsts);
}
LibTchStackClr();
LibJumpMenu();
}
|