Join Now

Hi, I'm Paul, but you can also call me Todd and I won't get upset.

New API's

Paul.Todd | 04 June, 2006 21:04

All the topics in this blog are going to be on Symbian and C++, sorry, but is all I program in these days.

So welcome, have all downloaded the WebKit and source code for the OSS browser?

The best starting off point seems to be the trunk where you will find all the sub project including the C++ source code and

The nicest part of this is the subfolder labelled S60Internals. It seems the nice guys at Nokia have provided some more API's that developers can look at.

There are a number of new API's and header files for S60 that might be useful for developers.

Next week I will be looking at RArray and specifically how to copy instances of it efficiently.

In the meantime you can normally find me answering the complex stuff on the newsgroups.

RArray and Resource Mangement

Paul.Todd | 12 June, 2006 00:37

 

If you are not using RArray, you should be!
It offers a substantial performance improvement over the CArray.. implementations for almost all the cases I can think of.

  • It is fast - RArray is coded in ARM assembly in release versions of the code.
  • It does not use asserts internally and also does not leave, it just returns error codes, so the use of trap statements in runtime significant areas of the code can be minimized or reduced.
  • It is lightweight
  • It is a wrapper over a more complex implementation of a generic array.
  • There are templated version of TInt and TUint built into EUSER so it does not bloat your executable.

Most of RArray is fairly simple to understand and works the way any generic array implementation works.

The biggest problem I do have with RArray is that the search and insert functions do not allow a variable to be passed to them so you are required to use a global or some other hack to get a state variable over into the comparison function.

The areas I am going look at are how RArray manages resources and how RArray can be applied effectively used to improve performance.

Before we can continue there are some questions that need to be answered:

How does RArray allocates memory?
If you play around with RArray you will soon find out that it allocates memory via the User::Alloc functions and these allocations are always a multiple of 4 bytes.

What RArray Constructors are there?
There is a default constructor, a constructor that specifies by how much the array will be expanded when it runs out of spare slots, and most importantly a constructor that maps onto a memory locations- but more of that later.

What is not present is a copy constructor, so what will happen then is that the C++ compiler will create a default constructor that is a bitwise copy of the item it is being copied from.

By default RArray does not have any items, however if one item is added, it preallocates by default 8 items, well okay KGranularity items so it can avoid memory fragmentation when adding items by avoiding a reallocate and copy each time an item is added.

Resource Management:
For the purposes of the example we will create a structure called TVector which has two integers in it.
THIS STRUCTURE IS A MUTIPLE OF 4 BYTES WHICH IS MANDATORY FOR RARRAY ELEMENTS!

struct TVector
{
  TInt iSpeed;
  TInt iDirection;
};
typedef RArray<TVector> RVectorArray; // Note that we keep the R prefix on our typedef

(1) static const TVector data[] = { {1,1}, {2,2}, {3,3} };
(2) const RVectorArray array(sizeof(TVector), CONST_CAST(TVector*,&data[0]), sizeof(data) / sizeof(data[0]));
(3) RVectorArray array1;
(4) array1 = array;
(5) TInt count = array.Count();

Each of the lines is numbered and documented as follows:
(1) we create a static const memory block. This could also be a block of memory from somewhere else such as a scanline or bitmap row.
(2) We now create an RArray that maps that memory into the RArray, note that the array is const so we can't expand it by accident.

It is important to read the documentation on this as it is very clear that this constructor does not allocate any memory nor take ownership of the memory, it just provides a mapping for developers to use the RArray with blocks of memory that needs to be manipulated.

(3) We now create a default RArray which has not memory allocated and it does not have any elements.

(4) Finally we create a clone of the array. It is vital to note that the compiler has underneath generated a default copy constructor and created a bitwise clone of data into array.

(5) Finally get the number of items in array after we have cloned it. What is the value of count?
Answer: See below

IMPORTANT NOTES:
(2) This just creates a mapping of the memory structure into the RArray is does not force the RArray to allocate any memory, hence there is nothing that needs to be put onto the cleanup stack. This is a very important point that a lot of people get wrong.
(4) This creates a clone of the array but ownership of the items in the data array is not taken
(5) Since array is a clone of data, count is 3
(6) When the function goes out of scope the arrays are destroyed, but we do not call reset. This is very important, because the arrays do not own any memory they MUST NOT be freed, so calling Reset is a big no-no! Likewise putting it on the cleanup stack is also a big mistake and will cause crashes.

Resource Management Part 2:
As I mentioned earlier, the underyling memory allocator for RArray is User::Alloc, so providing we allocate memory via this mechanism, we can move memory blobs from our structure into an RArray and then use the RArray.

(1) TVector* data2 = REINTERPRET_CAST(TVector*, User::AllocL(sizeof(data))); // actually sizeof(data) / (sizeof(data) / sizeof(data[0]))
(2) Mem::Copy(data2, data, sizeof(data));
(3) RVectorArray array2(sizeof(TVector), data2, sizeof(data) / (sizeof(data) / sizeof(data[0])));
(4) CleanupClosePushL(array2);
(5) const TVector element = {4,4};
(6) array2.Append(element);
(7) count = array2.Count();
(8) CleanupStack::PopAndDestroy(&array2);

Each of the lines is nummered and documented as follows:
(1) We allocate a block of memory using User::Alloc that is size of the 'data' structure (3 * sizeof(TVector))
This data is not put onto the cleanup stack, though of course we could put it onto the cleanup stack here and then remove it from the cleanup stack on line (4) and add the RArray back to the cleanup stack.
(2) We now copy the data from the const memory structure to our newly allocated memory blob
(3) We now create an RArray that maps the memory we just allocated to the internals of an RArray.
(4) Because we allocated the memory somewhere else, I decided that the RArray should now take ownership of the array, so that if any of the following lines should leave the memory will get correctly cleaned up by the RArray.
(5) we declare a new element
(6) This element is now added to our array that owns the memory. Where the array is full, the array will be expanded to cater for the new element, which may result in a memory reallocation, hence BE VERY CAREFUL where ownership of memory blocks is made.
(7) What should count now report? Answer: 4
(8) As we transfered ownship of the memory block to the array2 RArray, the cleanup stack may now delete it. If we did not put it onto the cleanup stack, we can just call Reset which has the same effect.

IMPORTANT NOTES:

(4) Is very important as you want to move ownership of the memory block to the RArray as soon as possible, especially if you want to add or remove items from the RArray.
(6) We should really check the return value from the append as it could have returned a negative value which would indicate that the item failed to expand. I have seen a number of cases where the error code is never checked and so unexpected things start happening.
(8) We do not neccessarily need to put the array2 as a parmeter to the PopAndDestroy but it provides an additional check that the item at the top of the stack is the item we expected it to be.

Some other things about RArray and RPointerArray

  • This is an advanced example of how to use resource management and RArray. Its not for beginners.
  • In real life I have used to read a block of integers (5000) from disk as a TPtrC8 and then cast it and move it to the RArray where the RArray takes ownership of the data. Which knocked about 10 seconds off the load time.
  • The arrays only take items that are 4 bytes or a multiple of 4 bytes, so if your object is say 9 bytes then the RArray will internally allocate 12 bytes for each item, hence take care when creating structures. Likewise, if your structure has different size items in it, check at compile time that the size is what you expect it to be. I have encountered situations where the sizeof has reported a different to what I had expected becuase of alignment issues in structure fields.
  • To get the address of the block of memory allocated to the RArray, take the address of element zero.
    i.e. TVector* ptr = &(array[0]);
  • RPointerArray does not have an implementation that destroys all the pointer objects. Normally you would do this via ResetAndDestroy. For some reason Symbian decided to put the implementation of this in mmfcontrollerpluginresolver.h. The function is called CleanupResetAndDestroyPushL
  • One source of problems with RArray is when appending a large number of items to the array in one go.
    For example lets say:
    (1) RArray<TInt> array;
    (2) for (TInt i=0; i < 1000; i++)
    (3)    array.Append(i);

    Each time (3) is called when the array hits capacity, it will need to be expanded by its default granularity (8) which means that there will be a large number (124 reallocations) during this example.
    In OS 9.x Symbian has added a Reserve function which preallocates a specified number of items in the RArray so that when adding items, the array does not need to be expanded which reduces memory fragmentation.
    To simulate this in OS7 and OS8, we can reuse the tech above to allocate and transfer ownership.

    For example:
    (1) RArray<TVector> temp(sizeof(TVector), REINTERPRET_CAST(TVector*, User::AllocL(1000 * sizeof(TVector))), 1000);
    (2) for (TInt i= 1000-1; i >= 0; i--)
    (3)    temp.Remove(i);
    (4) RArray<TVector> array3 = temp;

    Important Notes
    (1) We allocate 1000 items and put it into the RArray.
    (2,3) We then remove the items starting from the back which prevents horrific memory copying.
    (4) Finally assign the new array to the existing array. Note that if the existing array (array3) already has items then you will need to call Reset on it before assigning the items, otherwise the memory in the array will be orphaned and you will get a memory leak.

In Conclusion:
I hope this has provided an example of how to use resources in RArray where speed is critical. Needless to say, make sure that if you use this technique you keep it localized to one function so that the maintainer can know exactly what you are doing as these techniques need to be used to extreme care and only where there is time critical code.

Next week I want to discuss how I ported SyExpat to OS 9 which I have been promising to deliver for ages now. Its coded, I just hav'nt had a chance to document what I changed and why.

I only just posted a comment about multidimensional RArrays so I might publish some additional notes about these this week as well.

SyExpat Port to OS 9.x

Paul.Todd | 21 June, 2006 21:12

I was going to post this on Sunday but with the uncharacteristically good British Summer and England playing Sweden
in football, this slipped over to Wednesday.

Originally I thought that porting SyExpat to OS 9.x would involve a large number of changes.
In the end this turned out to be not the case.

The simplest fix was to xmlparse.c. It seems that the expat guys mixed up the return code enum and the error
code enum, so changing it is all that is needed for the main expat source code.

A slightly tricker problem was the with GCCE. It seems that it likes to define XMLCALL to something. GCCE seems to think it supports Microsoft extensions but it only does it in some cases.I think it was _MSC_EXTENSIONS, so in order to fix this we need to make sure that expat_config.h is defined before expat.h is included in the source files. This will then define XMLCALL and so stop expat_external.h defining it to the default.

The only change then resolves to just make the SyExpat.cpp file include the expat_config file before expat.h
as this sets up a  number of defines that specify how the functions are to be exported and so this sorts out the export declarations not being handles correctly.

Hence we just define XML call as blank and this fixes the compiler issues. See expat_config.h for more
information.

This is now sufficient to be able to build SyExpat for OS 9.x and still retain backward compatibility.

Unfortunately I have run into rather a nasty issue with Carbide.vs and the standard C library.
The whole sorry tale is here - http://discussion.forum.nokia.com/forum/showthread.php?t=75575 so for the
moment it seems there are some serious OS9 issues to fix first, even in the maintentance release.

Thanks to Harri Salminen for his fix for name space parsing which has now bee incorporated as well.

If anyone has any specific problem areas in Symbian they would like covered please email me and I will try to knock something up to show it is supposed to work.

Next week I will look at how to use MobInfo (and CTelephony) as a number of people seem to have a problem understanding how to use active objects with these libraries.

Addendum:
I have just found out I cannot attach files to blog entries so I will put the actual revised code up on my website.

FileBrowser

Paul.Todd | 29 June, 2006 23:56

I've been busy working on a new FileBrowser for the S60 3rd Edition that should help developers find out what is going on under the hood.

I'm looking for a couple of people to beta test it and provide feedback what they like or dislike or what they would want to see.

You must have a valid ACSID.

Please email me by clicking the "Feedback to Author" link on the side if you are interested.

If you have anything particular you would like please add a comment and I will see what I can do.

Update: 04/07/06 - The people requesting a version should have recieved it today. Sorry for the delay.
If you have still not recieved it please email me (paul@toddsoftware.com)

SisX format now documented

Paul.Todd | 06 July, 2006 18:19

Only one new note today, Symbian have released a new tech paper documenting the sisx file

The file browser is coming along nicely with some positive feedback!

Microsoft CE Emulator Goes Open Source

Paul.Todd | 20 July, 2006 11:28

Microsoft are definalty leading the way with their Windows CE Emulation (or to be correct Simulation).

Features include
  • CPU emulator that executes the ARM instruction set by JIT-compiling to x86
  • An MMU emulator to support virtual memory and page protection
  • A motherboard emulator that contains emulated RAM and NOR flash memory
  • A collection of peripheral devices attached to the motherboard: serial ports, LCD controller, touchscreen, keyboard, interrupt controller, programmable timers, real-time-clock, network cards, audio, etc.
  • A “DMA” interface which allows a Win32 application running outside the emulator to communicate with a WinCE application running inside the emulator, using a simple socket-like programming model.
Even though this is in effect a techview release of the emulator, it would be really nice to see Symbian provide the same level of emulation that Microsoft do, especially regarding the native arm support which provides full ARM emulation including alignment faults, which are the bane of every project that has one hacker on it :)

Come on Symbian, give us a better emulator! If Microsoft can do it and make it open source, this definitely raises the bar for you guys!

p.s FileMan is stuck in Symbian signed :(

Timezones

Paul.Todd | 31 July, 2006 15:44

My rant of the week!

Does anyone else have a problem with the Clock application and Timezones on Nokia 3rd Edition devices?

Problem 1:  Times are quoted relative to GMT with daylight saving.
-------------------------------------------------------------------------------------------
For most users I think this would present a problem as for example:
The company I work for has office in Idaho, USA. This means that the time difference is normally -7 hours with respect to the UK
It is always -7 except for two weeks in the year where the daylight savings changover occurs on different weeks.

If you look in the clock timezone this reports -6 hours because it is currently in Mountain Daylight Time and not Mountain Time.
This is really confusing as the first time a user looks at it they need to realize that unlike Windows the time is adjusted for daylight saving relative to GMT.

This is more annoying in that I had to stop and think why it would be -6 hours and not -7 which it is in real life.


Problem 2: Timezones are wrong.
----------------------------------------------
The Hawaii timezone is still wrong and despite a thread in the newsgroups there is still no word from Nokia about how to apply a patch or provide an update to correct this.

Currently we have to patch the code to get this to work correctly.

Hopefully Nokia will correct this. I say Nokia, because on the UIQ 3 devices this seems to have been fixed and reports the time correctly.

Does anyone have a comprehensive list of timezone bugs they would like to add to the list?

Three useful tips

Paul.Todd | 06 August, 2006 19:18

S60tips.com has some good tips on using your S60 from an end user perspective.
One thing it omits however (and if the web site owner sees this feel free to add it) is that you can mark items holding down the pencil key and then pressing the middle have button. (Use the ctl key on the E61).

Symbian have three downloadable getting started booklets avaliable now, including a chinese version.
http://www.symbian.com/developer/books/booklets.html to introduce people to various Symbian areas of development.
These are getting started, signing tips and coding tips.

Finally, thanks to one of my collegues Dave Holladay for the following tip to quickly get info on your application in the emulator.

If you need to find the UID of a system app, find the exe for the app & then run

dumpbin /section:.SYMBIAN /rawdata:longs <target exe name>

The format of the data block is

UID1        UID2        UID3        Priority
SecureID    VendorID    Capabilities1    Capabilities2
0        0        Version        Flags

Capabilites1:

0x00000001    TCB
0x00000002    CommDD
0x00000004    PowerMgmt
0x00000008    MultimediaDD
0x00000010    ReadDeviceData
0x00000020    WriteDeviceData
0x00000040     DRM
0x00000080    Trusted UI
0x00000100    ProtServ
0x00000200    DiskAdmin
0x00000400    NetworkControl
0x00000800    AllFiles
0x00001000    SwEvent
0x00002000    NetworkServices
0x00004000    LocalServices
0x00008000    ReadUserData
0x00010000    WriteUserData
0x00020000    Location
0x00040000    SurroundingsDD
0x00080000    UserEnvironment

Non Programming talkback

Paul.Todd | 30 August, 2006 00:21

I hav'nt been blogging last week as I took some time off to go to the reading festival.

Nice to see Nokia well represented in the "Rock up and Play" tent - Rockin' atmosphere guys! Well done!!

Another digg'd link today was Nokia Trivia which has 11 pieces of trivia on Nokia

Normal Service will resume shortly!

Symbian Signed DevCerts

Paul.Todd | 06 September, 2006 16:31

Well, people at Symbian Signed have finally stood up and taken notice that developers really need DevCerts with All Files/ReadDeviceData/WriteDeviceData.

It seems, according to Mark over at the Symbian Devleoper News Group, that you may now request a devcert

This experience (as outlined below) seems to mirror our requests for Manufacturer Set Capabilities with an ACSID.

I would be interested to see how other people get on requesting updated dev certs. I think for most small scale developers this would represent a huge step forward bringing more people on board Symbian Developement as it makes the security permission roughly equivalent to Microsoft Windows CE.

So Kudos to Mark and the Symbian Signed Team!

This is an extract from the thread:

Not withstanding the discussion in the other part of this thread, you can
obtain a Developer Certificate for Freeware applications for any capabilities.

The process is that you need to email CellMania (symbian @ staff.cellmania.com), copying to Symbian Signed (symbiansigned @
symbian.com) [remove spaces either side of the @], and specifying the following:

- your name, email address and contact details (address, phone number)
- an overview of your application
- the IMEI number (one only) of the device you are using to test
- the full list of capabilities you require. For manufacturer-approved
capabilities, you need to briefly explain why these are required
We'll update the Freeware FAQ on www.symbiansigned.com with this information
in due course.

Mark
Symbian Developer Network

Shutting down Servers and Threads Part 2

Paul.Todd | 19 September, 2006 17:43

In the first part we covered how to correctly and cleanly shutdown a process.

Now we are going to cover how to shutdown a thread.

Typically people start threads to do some form of processing but often fail to plan how to shutdown a thread.
I often heard the old saying "Fail to Plan then plan to fail". It is essential to decide early on what conditions are necessary for the thread to stop.

By terminating threads all sorts of issues can be introduced or can can occur depending on when the thread is killed so it really makes sense to plan how to end the threads aside from using terminate or kill.

There are a number of ways to stop a thread and we are going to cover the most important ones here

Watching for a thread to die
In Symbian the easiest way to watch for the thread dying is to use the RThread::Logon method.

You can use it as follows:
RThread thread;
thread.Open(myThreadId);
TRequestStatus watcher;
thread.Logon(watcher);

// start your thread shutdown here
// The below will block until the thread dies
// so make sure you shutdown quickly, otherwise you will
// need to use a wait dialog or something similar
User::WaitForRequest(watcher);

Note that until the thread dies, the thread that is executing the logon/WaitForRequest will be blocked, though you can encapsulate the logon inside an active object. In practice the time from the point where you request the thread to when it is shutdown is sub-second so its normally not worth the hassle.


Using a TRequestStatus to stop a thread
This is normally the "Symbian" preferred method.

Here you pass a pointer an TRequestStatus the thread and when you want to shut down the thread you just complete the TRequeststatus and wait for the thread to complete. Note that the TRequestStatus is "used" by the thread being shutdown and not be the calling thread. It looks intuitively wrong but works.

TRequestStatus* killer = NULL;
mythread.Create(_L("Thread"), MyThreadFunction, 1000,1000,2000, killer);
....
.... we now want to stop the thread
TRequestStatus waiter;
myThread.Logon(waiter); // watch the thread
User::RequestComplete(killer, KErrNone);  // tell the thread to stop
User::WaitForRequest(waiter); // when it closes the wait will end


LOCAL_C void MyThreadFunction(TAny* aAny)
  {
   // setup stuff like active scheduler etc.

   TRequestStatus threadstop = KRequestPending;
   TRequestStatus* sink = REINTERPRET_CAST(TRequestStatus*,aAny);
   sink = &threadstop;

   // do something e.g.
   TRequestStatus event;
   wsSession.EventReady(&event);
   // Note WaitForRequest allows two AO's!
   User::WaitForRequest(event, threadstop);

   if (threadstop != KRequestPending)
       {
          // this is called when the stop TRS was
          // signalled by the main app
          //
          // do what ever you need to stop the thread
          // e.g. shut handles, delete object etc.
          return;
       }

   // event was signalled so do some more processing
   // this was not the thread stop TRS so we can still run
   // as we have not been requested to shut down
   }


Using a global variable
In your main application you can create a global variable, say static TInt32 gShutdownFlag = 0. Of course it need to be static, you could pass a pointer across just as easily.

You can then set this from your main application and in your thread loop you can check to see if this flag is correctly. this is particularly suited to games as these generally do not use Active Objects heavily.

A source of concern might be that reading and writing data across threads is unsafe. Generally speaking read and writes are safe provided they are 32 bits wide. However in OS 9.x Symbian have introduced a new API in the User module, called SafeInc and SafeDec which guarantee that this is atomic.

Another method which I have'nt used is the Publish and Subscribe API. This API allows different processes to work with a common system wide global variable.

Creating links in a message box

Paul.Todd | 25 October, 2006 19:06

Todays topic is using CAknMessageQueryDialog to build popout links. It is really easy to provide a customized "more info" link embedded in your text. This is the same technique that the licence dialog uses.

For each link in the string we can provide a callback that will be called when the user moves the cursor over it and selects it.

Dialog with Link

One note: The factory function seems to be broken, the string parameter should have been const TDesC& but it has instead been declared as TDesC& - Doh! This means it needs a cast to work with LIT and other types when using NewL

So the first thing we do is construct our message dialog. Because I did'nt want to complicate things I am using the default resource as defined by AVKON.

So lets setup the strings: IRL we would use resources and StringLoader

_LIT(KHeader, "Example Link Box");
_LIT(KLink1, "Link 1");
_LIT(KLink2, "Link 2");
_LIT(KMessageboxText, "Click here for Link 1nhere for Link 2");

//Now we can setup the bits of the message box - NB note the cast in the NewL

void ShowLinkedMessageBox()
  {
  CAknMessageQueryDialog* dialog = CAknMessageQueryDialog::NewL(CONST_CAST(TDesC&, KMessageboxText()));
  CleanupStack::PushL(dialog);
  dialog->SetHeaderText(KHeader);

  // Now setup the text that will be linked and the action to be performed when they are selected.
  TCallBack callback1(CallbackText1);
  dialog->SetLink(callback1);
  dialog->SetLinkTextL(KLink1);

  TCallBack callback2(CallbackText2);
  dialog->SetLink(callback2);
  dialog->SetLinkTextL(KLink2);

  // Finally as its now leave-safe pop the dialog and run it
  CleanupStack::Pop(dialog);
  dialog->ExecuteLD(R_AVKON_MESSAGE_QUERY_DIALOG);
  }
Here are the two callback functions

LOCAL_C TInt CallbackText1(TAny* /*aAny*/)
    {
    CAknInformationNote* msg = new (ELeave) CAknInformationNote(ETrue);
    msg->ExecuteLD(_L("You clicked the first link"));
    return EFalse;
    }

LOCAL_C TInt CallbackText2(TAny* /*aAny*/)
    {
    CAknInformationNote* msg = new (ELeave) CAknInformationNote(ETrue);
    msg->ExecuteLD(_L("You clicked the second link"));
    return EFalse;
    }


Pretty simple for such a cool effect?

You now make it simpler by reading the documentation you can specify the link text by enclosing it between <AknMessageQuery Link> and </AknMessageQuery Link>
Then you only need to call setLink for each instance.
Email me (paul@toddsoftware.com) if you want the source for this as I hav'nt figured out how to upload zip files yet!

CStack and a little problem

Paul.Todd | 14 November, 2006 22:35

Someone emailed me a while back on how to use CStack. A quick search of the devkit with Copernic search showed no examples so I decided to post some code on how to use it.

The Symbian implemenent of a stack is a simple array where items are appended to the end of the array. This is both simple, elegant and unobtrusive.

First decide if the stack is to hold pointer or structures.
If the array is to hold pointers, then does the array own the pointer? Normally the answer is yes.

Lets say we want to create a stack of strings, then these strings would be implemented as HBufC's
so to make life easier, when a string is on the stack, the stack owns the the string.

So the stack can now be declared as (note the stack owns the data it contains!):
CStack<HBufC, ETrue>* myStack = new (ELeave) CStack<HBufC, ETrue>;

As we have declared it on the heap, we need to remember to push it onto the cleanup stack
so that if there is a leave it get cleans up nicely.
CleanupStack::PushL(myStack);

To add an item onto the stack is simple
_LIT(KFirstItemOntoTheStack, "My First Item");
myStack->PushL(KFirstItemOntoTheStack().AllocL());

Here we create a copy of KFirstItemOntoTheStack and then put it onto the stack, note
that at all times it is leave safe and nothing leaks if for example PushL fails.
This is because when we created the stack class we told it that it owns all the memory

Of course now we can get the top item, which returns an HBufC*
HBufC* top = myStack->Head();

and check to see if the stack is empty

if (myStack->IsEmpty())
       {
       // do something
       }

There are also the usual Pop to remove the top item (but not delete it!) and Head to get the top item,
which for some reason is not declared as Top, like my CS 101 class taught, go figure?

Now we have finished with the stack, we can delete it, this also deletes all the items
owned by the cleanup stack.
CleanupStack::PopAndDestroy(myStack);

This is a pretty good implementation and to be be quite honest the only thing I would really like to see
would be for it to be an R class derived from RPointerArray.

Now for a little quiz:
What is wrong with this code?

struct TMyVersionStruct
{
    TUint32        iVersion;
    TUint32        iItem1;
    TUint32        iItem2;
    TUint32        iItem3;
    TUint32        iItem4;
    TUint32        iItem5;
    TUint32        iItem6;
};

LOCAL_C void TestCrash()
    {
    // The production code reads a blob from disk and it looks correct
    // in the debugger but it itermittantly crashes,
    // here however we simulate what the read from disk as the problem
    // is not in the reading...

    HBufC8* item = HBufC8::NewL(sizeof(TMyVersionStruct));
    item->Des().SetLength(item->Des().MaxLength());

    TMyVersionStruct version;
    TPtr8 ver((TUint8*)&version, sizeof(version));
    ver = *item;

    delete item;

   // do something with version
    }

Its an interesting if very subtle problem that seems to come up when memory is very fragmented and is
fiendishly annoying.

Answers next week.....

Creating a message details dialog

Paul.Todd | 29 December, 2006 13:03

A while ago someone wanted a dialog that looked and acted like the dialog for
the message details in the messaging application (see)

What they were looking for is something like this... (thanks payu.sergey  for the picture)

Unfortunately I wrote an implementation for someone else that I could not reused so I looked
at it again over Xmas to see if there was anything that could be salvaged.

Message dialog example

I realized there are two ways to do this, one with a CAknSelectionListDialog and one with
a CAknPopupList. The second one which I missed in the original implementation is much
simpler.

We are only going to cover the second one as its a little over 70 lines of code in total.

The real trick to solving this problem is realizing how to tackle it.

First, what kind of listbox you are we looking at.
In this case it is a single heading style list box.

The single means that it takes one line and the heading means that the list box
is split into two halves, the one is the caption and the other is the text. Also, the
caption will be in the normal font and the text in bold font, which kind of helps us
decide that this is indeed a listbox.

Next is what to do if  we have a very long string we need to split it into
different lines and then make sure the line with the split text goes into the correct
column.


For example
HeadingtThis is some very long text
would split into two lines
HeadingtThis is some
       tvery long text
      
Finally the only thing we need to know is how big the column is for the heading and
the the text. We are lucky in that these can be obtained from the listbox, before the
listbox is displayed, allowing us to get the width and the font to be used based on the
LAF specification so it complies with the skins API and locale etc.

So now we know how to create the listbox, its container, the font , widths and how to
split the content.

Next time we will go through the code on how it was done.

Creating a message details dialog - the implementation

Paul.Todd | 07 January, 2007 16:41

As I outlined in the previous posting we will now cover the implementation of the message detail dialog

Basically what we want to do is create a popup list that contains a listbox. The class is the CAknPopupList
which is a control that looks like a dialog box but contains a listbox.

Before we can create the popup list however we need to create a listbox, and this listbox is one of the
standard AVKON listboxes, in particular, CAknSingleHeadingPopupMenuStyleListBox. This listbox takes a
heading in the first column and the text in the second column. The text is also bold in the second
column.

The only thing to realize is the tricky construction of the objects because they are mutually dependant.

In this case we first create a listbox, then the popup list and then initialize the listbox so the two
controls get linked together.

// Create the listbox
    CAknSingleHeadingPopupMenuStyleListBox* listPane = new ( ELeave ) CAknSingleHeadingPopupMenuStyleListBox;
    CleanupStack::PushL( listPane );

// Create the list pane
    CAknPopupList* popupList = CAknPopupList::NewL(listPane,R_AVKON_SOFTKEYS_OK_EMPTY, AknPopupLayouts::EMenuWindow );
    CleanupStack::PushL( popupList );
    popupList->SetTitleL( _L("Message Details:") );

// Now complete the construction of the listbox - note the view flag settings    `
    listPane->ConstructL( popupList, CEikListBox::ELeftDownInViewRect | CEikListBox::EViewerFlag);
    listPane->CreateScrollBarFrameL( ETrue );
    listPane->ScrollBarFrame()->SetScrollBarVisibilityL(CEikScrollBarFrame::EOff, CEikScrollBarFrame::EAuto );

// Finally get the metrics for the columns - all LAF based so no guessing
    const TSize appSize = ApplicationRect().Size();
    listPane->SetSize( appSize );
    const CFormattedCellListBoxItemDrawer* drawer = listPane->ItemDrawer();
    const CFormattedCellListBoxData* data = drawer->ColumnData();
    const TInt width = data->SubCellSize(1).iWidth;
    const CFont* textBoxFont = data->SubCellFont(1);

Now we can create the contents of the listbox as we know how wide the columns are and what font to use
    CDesCArray* array = CreateListBoxRowsL(width, *textBoxFont);

Finally set the listbox contents into the listbox
    CTextListBoxModel* model = listPane->Model();
    model->SetItemTextArray( array );
    model->SetOwnershipType( ELbmOwnsItemArray );
    CleanupStack::Pop(array);


And to finish off display the listbox in the popup list and get a selection
    popupList->ExecuteLD();

Now remember to pop and destroy the various bits
    CleanupStack::Pop(popupList);
    CleanupStack::PopAndDestroy(listPane);
   
   
All that is needed now is the code to build up the rows for the listbox.
So what we do is create an array to hold the data and then fill it based
on the heading and string supplied.
 
LOCAL_C CDesCArray* CreateListBoxRowsL(const TInt aWidth, const CFont& aFont)
    {
    _LIT(KTo, "To:");
    _LIT(KToFields, "dave@email.com, george@email.com");

    _LIT(KSubject, "Subject:");
    _LIT(KSubjectFields, "This is a very long subject to be displayed");

    CDesCArray* itemList = new (ELeave) CDesCArrayFlat(3);
    CleanupStack::PushL(itemList);

    SplitStringL(itemList, KTo, KToFields, aWidth, aFont);
    SplitStringL(itemList, KSubject, KSubjectFields, aWidth, aFont);

    CleanupStack::Pop(itemList);
    return itemList;
    }

There are a number of ways we can split the string, probably the simplist is to use the TextCount, but I decided to use
the WrapToArray for a change.
LOCAL_C void SplitStringL(CDesCArray* aList, const TDesC& aHeading, const TDesC& aString, const TInt aWidth, const CFont& aFont)
    {
    CArrayFix<TPtrC16>* lines = new (ELeave) CArrayFixFlat<TPtrC>(10);
    CleanupStack::PushL(lines);

    TPtr tmp = aString.AllocLC()->Des();
    AknTextUtils::WrapToArrayL(tmp, aWidth, aFont, *lines);

    for (TInt i=0; i < lines->Count(); i++)
        {
        TBuf<255> tmp;

        // If this is the first time we are adding to the array
        // we put the heading in, otherwise we just leave it empty
        // for the first column
        if (i == 0)
            tmp = aHeading;

        // Now add the second column
        tmp.Append(_L("t"));
        TPtrC theLine = lines->At(i);
        tmp.Append(lines->At(i));
        aList->AppendL(tmp);
        }

    CleanupStack::PopAndDestroy(2, lines);
    }

Here is the final result of it all. The elipses ("...") could be got rid of using the font to calculate the width of the text and not the test utils api.


Finished Message Detail Dialog


As usual, email for the complete code
1 2 3 4  Next»
 
 
Powered by LifeType