The latest version is from Aug 12, 2005.
You also need the files for symwiz, and example *.init files. Those you can find in this tarball, which you should be able to open either with Winzip or something similar, or from command line by calling gunzip -c symwiz.tar.gz | tar xvf - from the cygwin command line.
If you don't want to download and install Python, you can take this tarball that has symwiz.exe which works without Python. Instead of calling python symwiz.py, you just call it symwiz.exe.
Copy the helloworldbasic.init from the symwiz distribution to your working directory, and execute
python c:/Users/Kari/src/symbian/symwiz/symwiz.py helloworldbasic.init
or
python symwiz/symwiz.py helloworldbasic.init
etc., depending on where you opened the tarball.
Actually, since that is fairly cumbersome to type, I create myself an alias into my $HOME/.aliases
alias symwiz='python c:/Users/Kari/src/symbian/symwiz/symwiz.py'
and then I only need to type
symwiz helloworldbasic.init
Take a look at a log. A lot of directories and files should have now been created for you.
Now you can go down to swHelloBasic/group, run make or just type 'bldmake bldfiles' and 'abld build wins udeb' directly by yourself, see how the project is compiled and linked, just ignore the warnings, and you can try out the application in an emulator by typing 'epoc' and trying it out (or typing 'epoc &' so the emulator runs in the background and doesn't block your command line prompt).
Open the helloworldbasic.init file in an editor (e.g., emacs, or notepad, etc.). It's not very long, and there are some comments that explain what it does. You can see that the project name is given (swHelloBasic), based on that name the project directory is named, and the main files and classes have that as part of their name. On the next line you see the UID definition. All projects must have a unique UID. Read a short description in the SDK Help (Contents -> Symbian Developer Library -> Tools and utilities -> UIDs). Other than that, there's a simple menu structure with a couple of simple callbacks. Once you've tried the application once, you can experiment changing some of the menu texts, or the infonote string. When you recreate the project, an editor will pop up and show you the differences in the project that were caused by the edits. By default that's emacs, to change it to notepad, edit file preferences.py (you can also change there that you are using vc6 rather than vc7, that is, .NET). Here is a documentation sheet that collects *.init file structure in one place.
Take a quick look at the basic structure of symwiz. Open symwiz.py, scroll down towards the end (to THE MAIN SECTION). You'll see that first command line arguments are handled, then the key classes are initialized, the *.init file is read and its Python code is executed, the directory structure is created, and finally all the files are created.
In order to learn a bit more, and see how the help works, let's study the drawing part, which is the Draw function in AppView. Peeking into inc/swHelloBasicAppView.h we can find out the class CswHelloBasicAppView derives from. In SDK Help, in the Index tab, type in the class name (CCoeControl). We'll learn in which header file it is declared (coecntrl.h), and which library is needed (cone.lib). There's also a very succinct class description. We're interested in the Draw method, find that in the list of members and click the link. In the description it mentions that a graphics context is needed for drawing, suggests using SystemGC(), which indeed is what the Draw() implementation in swHelloBasicAppView.cpp does as the first thing. Find also the documentation for SystemGC() and Rect() (both are members of CCoeControl). One more hint about documentation: sometimes the header files (can be found in /epoc32/include, starting from the SDK root) contain some information or additional documentation that didn't make its way to the SDK Help.
These examples are modeled after the examples that come with the SDK. You can find them in the /Series60Ex/ directory. If you installed the 7610 Camera plugin, you'll find only two camera-specific examples, the rest are actually in the /Series60Ex/ directory of the basic 2.1 SDK which you had to install first. The SDK Help contains documentation for these examples.
Another new thing is that you can give the application a display name that is different from the project name. Then notice that two of the user events call a method in the AppView object. One of them, DrawNow, is already defined in CCoeControl (find it in SDK Help from Index -> DrawNow), but UserDraw is not defined. For that, we need to add a public method called UserDraw into the AppView class. In symwiz.py, an object called appview, of type CClass, is defined before the *.init file is read. By calling method add_public() in it we can add a public member to the AppView C++ class. In this case it is a constant method, that returns void, with name UserDraw, no arguments, and three lines of code. Experiment by removing that call and see the effects.
The appview class has a method for key events. That works in symwiz a bit like a simple menu, you call appview.keyevents() with a list of event - callback pairs. Since the infonotes used there require the inclusion of a header file <aknnotewrappers.h>, we add the include into the *.cpp file by adding it to the appview.inc_src list.
Finally, we want to enhance the Draw() method so it displays the current time. The default behavior, which gets the graphics context and clears the screen is fine, we don't want to replace it but add more calls after it. Every CClass, including appview, has a dictionary called members that is indexed by the member name. So we can access the method Draw by calling appview.methods['Draw']. The method again has an attribute called code, which is a string containing the code. We simply append to that string another string with the additional commands. In Python, multiline strings are delimited with triple quotes """.
appview.add_private( Var( 'CFbsBitmap*', 'iBitmap', 'The current image' ), destroy_var = True )the third argument of Var is optional, and yields the following in the header file:
/*! @var iBitmap The current image */ CFbsBitmap* iBitmap;where the comment is in the correct format for using Doxygen to generate documentation for your project.
Here we add a public method. The first argument is either a string (return type) or a tuple of two strings (return type, and a comment), the second argument is also optionally either the function name (string) or also a description of the function (tuple of two strings, like here). The third argument is a list of function arguments (none here). Fourth argument is a string encoding the C++ code for this method.
appview.add_public( Method( ('CFbsBitmap*', 'A pointer to the current bitmap'), ('GetBitmap', 'Return the current bitmap'), [], 'return iBitmap;' ) )produces the following code in the header file
/*! @function GetBitmap @discussion Return the current bitmap @result A pointer to the current bitmap */ CFbsBitmap* GetBitmap( void );and
appview.add_public( Method( 'void', ('SetBitmap', 'Set new value for iBitmap'), [Arg('CFbsBitmap*', 'aBitmap', 'The new bitmap')], """\ if (iBitmap) { delete (iBitmap); iBitmap = NULL; } if (aBitmap) { iBitmap = aBitmap; }""" ) )yields
/*! @function SetBitmap @discussion Set new value for iBitmap @param aBitmap The new bitmap */ void SetBitmap( CFbsBitmap* aBitmap );If you are feeling lazy, instead of giving an name-document tuple, just give the name
appview.add_public( Method( 'CFbsBitmap*', 'GetBitmap', [], 'return iBitmap;' ) )yields
/*! @function GetBitmap @discussion <NO DESCRIPTION> @result <NO DESCRIPTION> */ CFbsBitmap* GetBitmap( void );Long comments are broken into separate indented lines so they remain readable, e.g.
/*! @function SetBitmap @discussion Set new value for iBitmap. Yadda Yadda Yadda Yadda Yadda Yadda Yadda Yadda Yadda Yadda Yadda Yadda Yadda Yadda Yadda Yadda Yadda @param aBitmap The new bitmap. Yadda Yadda Yadda Yadda Yadda Yadda Yadda Yadda Yadda Yadda Yadda */
Two new libraries are needed, fbscli.lib for bitmaps, and ws32.lib for flushing the window server session.
Many of the menu actions call ErrorNotifyL() which shows an error notification in case there was an error. Since especially during development it is very useful to show such notifications, the symwiz class CClass contains a convenience method called add_notify(). Take a look how that is done in the file symwiz.py. First, it adds a method that returns a string describing a given error. Then it creates another C++ member function, ErrorNotifyL(), that uses the previous method to print an error message, and finally a third function for infonotifications. The menu handling happens in AppUi, so we add these functions there (appui.add_notify()).
The rest three entries that affect appui all deal with including the videorecorder header file to the appui header file, declaring a private variable that points to a CVideoRecorder class instance (and destroying that pointer in the destructor), and constructing that instance in the second phase constructor (ConstructL).
AppView gets only one modification. It creates a new DrawImage() method, using which the VideoRecorder can draw a captured picture on the display. The method obtains a graphics context, the window of the AppView is associated with the graphics context and they are activated, the bitmap is blitted in the center of the screen, graphics context is deactivated, and finally flushes the window server session so the image is actually displayed.
The rest of the init file deals with the VideoRecorder class. First, we create an instance of a symwiz CClass object, and the argument gives it a name and a documentation string (you'll find the doc string in the VideoRecorder.h). The engine derives from CBase (the base of all Symbian classes, hence quite important; read the class description in the SDK Help).
videoeng.use_camera() is implemented in CClass. It includes the ecam.h header file and ecam.lib library, and makes the class to inherit from MCameraObserver. Symbian only allows a class to have a single concrete class as a parent, in this case that class is CBase. MCameraObserver is a mixin class, a pure virtual class that only defines an interface that its descendants have to implement. Take a look in the SDK Help class description, you have to implement this class (i.e., its methods) in order to use the camera API. However, use_camera() adds those methods for you as well, with some error checking. Info and error notes are added, a member variable CCamera* iCamera is created and destroyed in the destructor, and if the class doesn't already have an iAppView pointer, one is created. Other methods that are added include StartViewFinder() and StopViewFinder(), and AppView gets a method DrawImage().
The real work of constructing a class usually happens in the so-called second phase constructor, ConstructL. Symbian programming guidelines are really picky about not creating memory leaks, and being able to recover gracefully even when you run out of memory. You could think that if you run out of memory, that's it, there's nothing you can do (except fix your code). And while you're developing your system you in most cases that is precisely so. However, once your application is ready and others use it, and they run out of resources, one should be able to recover, then release some resources by, e.g., closing other applications, and try again, without having caused any memory leaks that would soon force a reboot. There are a couple of conventions that are used throughout in the Symbian examples and libraries. Even if you want to live dangerously, and just use the basic C++ memory handling, you should get familiar with the basic conventions so you can follow what's going on. Further, Symbian doesn't support the C++ catch, throw, and try mechanisms but implements its own mechanism (trapping and leaving). So read now SDK Help Contents -> Series 60 Developer Guides -> Symbian OS: Coding Conventions in C++, sections 3.2 - 3.3. It's only eight pages, so won't take that long. I really recommend reading the whole document, but those two sections will do for now. use_engine() calls another CClass convenience function, two_phase, which creates the two-phase construction functions, including creation of iCamera in the ConstructL.
The camera processing starts in ConstructL, where the camera is reserved (iCamera->Reserve()). Later, as the asynchronous reserve completes, CVideoRecorder::ReserveComplete() is called. If there was no error, iCamera->PowerOn() follows, which again calls CVideoRecorder::PowerOnComplete(). Again if there's no error, that function calls iCamera->StartViewFinderBitmapsL(), which calls CVideoRecorder::ViewFinderFrameReady() when those frames are ready. That function simply passes the bitmap to the DrawImage function in AppView for display.
Actual video recording begins when the user calls CVideoRecorder::StartVideoCaptureL() from the menu. There we first call iCamera->PrepareVideoCaptureL() with arguments that give image size, etc., and there the camera reserves resources. Then iCamera->StartVideoCapture() starts the process. When each buffer has been filled with the required number of frames (as set in the PrepareVideoCaptureL), FrameBufferReady() is called, and it just checks that the frame is grabbed OK, but in this simple example doesn't do anything with the frame. StopVideoCapture() merely calls a similarly named method of the camera.
The rest of the class mostly concerns changing the camera settings, such as brightness, digital zoom factor, etc., from the menu callbacks. videoeng.intro_src contains several variables that are global to this class, these are simply copied verbatim after the inclusion of header files.
The rest of the file deals with appui and appview. Appview is the simpler one, it just adds functionality to have a private variable for the bitmap, and drawing routines that display it.
In this example almost all of the real work is done in appui. It is appui that now calls use_camera and hence inherits from MCameraObserver, etc. The difference to the previous example is that the chain of setting things up is not automatic, but is done interactively from the menus. When ViewFinderFrameReady() is called, the image is just sent to appview for display, when ImageReady() is called the bitmap is also stored into appview. A major new functionality is the ability to save a captured image, which starts at SaveCapturedImageL(). It fetches the bitmap from appview, uses a helper function GetNextFileName() (also defined here, scans the file names in the image directory, makes an assumption of a naming convention basename + number + file ending, and finds the next unused name) and saves the bitmap using the file name. Another useful piece of functionality can be found in LaunchMediaGallery(). This function hard codes the UID used be the MediaGallery (image viewing and browsing application), and activates the application on the same directory that it uses to store the images.
The guiengine.init file uses a new function, use_engine(), on its third line. In this case it's given two arguments, the name of the main class of the engine, and a UID for the engine DLL. If you scroll down the file you see that there is a variable 'engine' that isn't initialized; this variable was created by the use_engine() function. Another thing that happens is that the document class creates an instance of the engine, stores it to a private variable, and adds a method called Model() that returns that pointer. iDocument->Model() is then used for example in the keyevent for pressing the 5-way navikey (EKeyDevice3). The engine has a two-phase constructor and an empty method Clear() that can be called to (re)initialize the engine.
We've seen already before how the list of project libraries can be extended (resource.libraries += [ 'lib1', 'lib2', ... ]); the engine can have its own set of libraries as well (resource.eng_libs += [ 'lib1', 'lib2', ... ]).
The actual application draws circles and rectangles on the screen. appview has a variable to hold the state which should be drawn (circle or rectangle), allows a cursor to be moved around with the navikey, and calls the model to add the shape that was last selected when the navikey is pressed (see appview.keyevents()). In the appview's Draw() method the code is replaced. A graphicscontext is obtained, a pen color and size are set, a simple crosshair is drawn for the cursor, and the shapes are drawn one at a time by fetching them using iDocument->Model()->GetNextShape(), which the engine implements, and calling the shape's own Draw() method.
The rest of the *.init file consists of adding methods such as AddShapeL(), GetNextShape(), and Clear() to the ShapeListMrg engine. Another class in the engine is TShape. Calling 'shape.dll = True' tells symwiz that the TShape class should go together with the engine, rather than the GUI framework. It is also a pure virtual class that defines interfaces for drawing a shape and getting its bounding box. Two classes, TCircle and TRectangle, derive from TShape and implement the missing functions. One more thing to notice is that all these shape classes are under a common namespace called NShapes, and the Shape file defines the Enum TShapeType inside the namespace but outside the class.
Active objects are the preferred mechanism to do this is in Symbian OS. An event-handling thread has a single active scheduler which is responsible for deciding the order in which events are handled. The tasks that the scheduler deals with are wrapped in an active object, derived from the CActive
class. The active object must add itself into the active scheduler, this is typically done in the (second stage) constructor. It also needs to implement one or more methods that request a service. That function should first check that there are no pending active requests for that object (call IsActive()
derived from CActive
), then request the service, typically passing a reference to derived member iStatus
, and finally marking itself active (call SetActive()
).
There are two additional functions that an active object needs to implement: RunL()
and DoCancel()
. When the server has served the request, it sends a message that wakes up the thread if it is sleeping, and notifies the active scheduler that one of its active objects is ready to run. The scheduler then calls RunL
, and the program can study the contents of iStatus
to see whether the service was completed successfully. DoCancel()
implements the user code that may be necessary if the active object is cancelled.
Symwiz CClass
provides a method make_active()
that creates a two-phase constructor to the class, makes the class to derive from CActive
, adds itself into the active scheduler inside ConstructL
, and adds the RunL()
and DoCancel()
methods. For example bmpmanip.init provides an example on the use of make_active()
.