Wednesday, June 16, 2010

A grid to select rows

I want to use a grid to select, for example, a customer from the customers table.

I need a grid with the following features:
  • read-only
  • when I click the grid I want to select the whole row.
Solving this problem was harder than expected. First I tried the new wxDataViewListCtrl control. It has a lot of interesting functionalities, so I spent some time trying to use it. Unfortunately I found a number of bugs that showed how this control was too young for production code.

Then I tried wxDataListCtrl in report mode. I already used it in other projects so I had some experience with it, and it looked like a good solution. After writing some test code I again found a couple of problems.
The first is that it is not possible to have the first column right aligned, so it is not possible to show numbers in the first column.
The second is that a bitmap shown in a column is always left-aligned, while it would often be useful to have it centered.
Both limitations derive from the native control used, so they are not wxWidgets' fault.

So I was left only with the venerable wxGrid, and I tried it. I have been able to obtain a good result, and I learned something about wxGrid that will be useful in the future.

I derived a new class from wxGrid, with the following modifications:

The constructor calls the following code to set the visual properties of the grid:
SetUseNativeColLabels();
HideRowLabels();
EnableEditing( false );
SetCellHighlightPenWidth( 0 );  // disable highlight
EnableGridLines( false );
DisableDragGridSize();
ShowScrollbars( wxSHOW_SB_DEFAULT, wxSHOW_SB_ALWAYS );
// select the first row
if( GetNumberRows() > 0 )
    SelectRow( 0 );

the wxEVT_GRID_SELECT_CELL handler calls
SelectRow( event.GetRow() );

the wxEVT_GRID_RANGE_SELECT handler uses this code
if( event.Selecting() && (event.GetTopRow() != event.GetBottomRow()) ) {
to see if it needs to call
SelectRow( GetGridCursorRow() );
to select the current row only.

the wxEVT_GRID_LABEL_LEFT_CLICK handler calls
SelectRow( GetGridCursorRow() );


the wxEVT_GRID_ROW_SIZE handler calls
SelectRow( GetGridCursorRow() );


the wxEVT_GRID_COL_SIZE handler calls
SelectRow( GetGridCursorRow() );

the wxEVT_KEY_DOWN handler ignores events that would make the cursor go outside the grid, making the selection disappear. For example:
if( event.GetKeyCode() == WXK_RIGHT && GetGridCursorCol() == GetNumberCols() - 1 )
    ignore = true;


The resulting grid is very satisfactory for my needs.

Saturday, June 12, 2010

Entering numbers

Users of an accounting program need to enter number very often, so I need a bulletproow way to do it.

Numbers are usually entered in text controls, and wxWidgets ha poor native support for this need. I could have written a custom control for my purpose, but I decided that validators are a better solution.
A validator links a C++ variable and the corresponding control. It automatically transfers data between the variable and the control, and it can also validate the input, for example allowing only some characters.

The standard wxTextValidator class links a text control and a wxString variable. An optional wxFILTER_NUMERIC style can be used to only allow numeric input. This looks interesting but it is not enough: the validator only checks keypresses against a static list, so you can type something like "123-34" which is obviously wong. Moreover the handled variable is a wxString that should be manually converted to and from a numerical one.

So I wrote a new validator class, modifying the existing ones. This validator has many enhancements:
  • It links an int or double variable to the text control, with automatic conversion.
  • Keypresses are checked in a way that will always result in a correct numeric value. For example a minus character is only allowed at the beginning of the number.
  • For double variables it is possible to specify a fixed number of decimals.
  • Decimal separator. In my country the decimal separator is the comma (','), but the validator accepts the dot ('.') and converts it to a comma. This is handy because, for example, the numeric keypad does not have a comma key.
Implementing the validator was a relatively straightforward task. In the OnChar() event handler check the arriving keypress, get the control's insertion point with wxTextEntry::GetInsertionPoint() and see how the control's text would look like if the new character were allowed. Decide if the new char can be accepted or not.

The conversion from dot to comma is done swallowing the keypress event and using wxTextTentry::WriteText() to insert the comma.

Wednesday, June 9, 2010

Adapting wxWidgets

wxWidgets is a great framework, but it is an all-purpose one, so many things useful to an accounting program are missing. I am working to add some useful features, and I will report the results.
The needed improvements can be grouped in two main areas:
  • Data entry: users will do a lot of data entry, so there is the need for tools that make it easy for both the user and the programmer. Mainly, I need a good way to enter numbers and dates. "Good way" means that the user must find it easy and he should not have way to enter wrong values. Handling those values must be simple for the programmer too.
  • Grid: I plan to use grids a lot, in two different ways. The first is showing many rows and let the user choose one, for example choosing a customer from a list. This kind of grid must be read-only. The second is using the grid for data entry: think of entering the rows of an invoice like in a spreadsheet.
wxWidgets has basic controls in these two areas, but they are far from optimal so I had to work at this, and I am still working.

Monday, June 7, 2010

Firebird embedded - OS X

Deploying an embedded Firebird database in an OS X bundle is similar to deploying it for Linux, but there are more problems to solve. This might not be the best solution, but it works so it can be at least a good starting point.

First I copied the files that I needed from the Firebird Framework folders to another folder. For example, let's call it /Users/fulvio/firebird_runtime.
Then I renamed the Firebird file to libfbembed.dylib: this is probably not necessary but I feel better using the same filename as in other platforms.

Files structure

I create the application as a bundle. The executable file is stored in the folder Contents/MacOS inside the bundle. In this same folder i also store:

firebird.conf
firebird (a folder)

..firebird.msg

..libfbembed.dylib
..libicudata.dylib
..libicui18n.dylib
..libicuuc.dylib
..security2.fdb
..bin (a folder)
....fb_lock_mgr..
....gbak
....isql
....firebird (a folder)
......firebird.msg
..intl (a folder)
....fbintl.conf
....fbintl.dylib

I used dots instead of spaces to indent because the blog editor keeps ignoring leading spaces.
Indented files are stored it the folder above them so, for example, gbak is stored in the bundle as Contents/MacOS/firebird/bin/gbak.

I added some "copy files" build phase in XCode to copy the Firebird files from /Users/fulvio/firebird_runtime to the bundle.

Linking

I link the program against /Users/fulvio/firebird_runtime/libfbembed.dylib adding the following text to Other linker flags:

-L/Users/fulvio/firebird_runtime

and adding the Firebird .dylib files to the "Link Binary With Libraries" build phase in XCode.

Environment variables

The next step is to set the FIREBIRD environment variable to the executable folder.
The command line is "export FIREBIRD=."
The same result can be obtained adding

<key>LSEnvironment</key>
<dict>
<key>FIREBIRD</key>
<string>.</string>
</dict>

to the Info.plist file contained in the bundle.

firebird.conf

Open the "firebird.conf" file and look for #RootDirectory. The leading pound sign means that the line is commented. Change it to:

RootDirectory = ./firebird

note that the line is not commented any more.

Patching

The bundle still doesn't work because the program looks for the libraries in the original framework's location, not in the bundle.
.dylib files contain their original full path and the original full path of all the used libraries, and executable files do the same. You can verify this running the otool -L and otool -D commands.
I solved this problem using the install_name_tool command that changes the file reference contained in libraries and executables. It only works if the new paths are not longer than the old ones, but this is not a problem for us. I use a "Run Script" build phase in XCode to execute the following script:

#!/bin/bash
EXECFILE=${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}
LIBPATH=${BUILT_PRODUCTS_DIR}/${EXECUTABLE_FOLDER_PATH}/firebird
LIBBINPATH=${BUILT_PRODUCTS_DIR}/${EXECUTABLE_FOLDER_PATH}/firebird/bin
# path of library files relative to the main executable
NEWLIBPATH="@executable_path/firebird"
# path of library files relative to other library files
NEWLIBPATH_FOR_LIBS="@loader_path"
# path of library files relative to executables in the "firebird/bin" folder
NEWLIBPATH_FROM_BIN="@loader_path/.."
OLDLIBPATH="/Library/Frameworks/Firebird.framework/Versions/A/Libraries"
OLDLIBFBEMBEDFILENAME="/Library/Frameworks/Firebird.framework/Versions/A/Firebird"

# change the references in the files contained in the "firebird" folder
for TARGET in libfbembed.dylib libicudata.dylib libicui18n.dylib libicuuc.dylib ; do
LIBFILE=${LIBPATH}/${TARGET}
OLDTARGETID=${OLDLIBPATH}/${TARGET}
NEWTARGETID=${NEWLIBPATH}/${TARGET}
NEWTARGETID_FOR_LIBS=${NEWLIBPATH_FOR_LIBS}/${TARGET}
install_name_tool -id ${NEWTARGETID_FOR_LIBS} ${LIBFILE}
install_name_tool -change ${OLDTARGETID} ${NEWTARGETID} ${EXECFILE}
for POSSIBLECALLERNAME in libfbembed.dylib libicudata.dylib libicui18n.dylib libicuuc.dylib ; do
POSSIBLECALLERFILE=${LIBPATH}/${POSSIBLECALLERNAME}
install_name_tool -change ${OLDTARGETID} ${NEWTARGETID_FOR_LIBS} ${POSSIBLECALLERFILE}
done
done

# change the references in the files contained in the "firebird/bin" folder
for TARGET in gbak isql ; do
FILE=${LIBBINPATH}/${TARGET}
for POSSIBLECALLEDNAME in libfbembed.dylib libicudata.dylib libicui18n.dylib libicuuc.dylib ; do
OLDTARGETID=${OLDLIBPATH}/${POSSIBLECALLEDNAME}
NEWTARGETID=${NEWLIBPATH_FROM_BIN}/${POSSIBLECALLEDNAME}
install_name_tool -change ${OLDTARGETID} ${NEWTARGETID} ${FILE}
done
# change the reference to libfbembed into the program, that contains a reference to a different name (the framework name)
NEWTARGETID=${NEWLIBPATH_FROM_BIN}/libfbembed.dylib
install_name_tool -change ${OLDLIBFBEMBEDFILENAME} ${NEWTARGETID} ${FILE}
done

# change the reference to libfbembed into the caller program, that contains a reference to a different name (the framework name)
NEWTARGETID=${NEWLIBPATH}/libfbembed.dylib
install_name_tool -change ${OLDLIBFBEMBEDFILENAME} ${NEWTARGETID} ${EXECFILE}

I learned something about .dlybs patching from

http://ynniv.com/blog/2006/02/deploying-app-that-use-dylibs-on-mac.html

but this link does not seem to work any more.

Friday, June 4, 2010

Firebird embedded - Linux

A great feature of the Firebird database is the ability to ship an embedded database with you program. No need for a separate installation and no conflicts with other installations, just copy some files.
The embedded model is officially supported only for Windows. For this operating system everything is simple: just download the embedded server and follow the documentation.

It is possible to deploy an embedded Firebird database also under Linux, even if this is not officially documented. I started from information from Milan Babuskov, currently available here:

http://www.firebirdfaq.org/Firebird-Embedded-Linux-HOWTO.html

That page is about version 1.5: I made some simple changes and used version 2.0.x. It should also work with version 2.1.

First I downloaded the classic server (superserver does not work). For an embedded server you need to link to libfbembed.so, which contains a full server. libfbclient.so contains only the client library: it is smaller but it can only connect to a full server.
At first I linked to the files that I downloaded from the Firebird site, but I got warnings because the Firebird library used libstdc++.so.5 and my compiler used libstdc++.so.6. It turns out that Firebird is compiled with a very old compiler version for better portability.
This is not a problem when the server is used in the normal way, but it causes those warnings when I link a program to the Firebird library. I do not know if this could be a real problem, but I ended up compiling Firebird from source. It is a rather simple task (I have been able to do it!), just remember to run 'make install' to strip the resulting files. Here are some info about the compilation:

http://firebird.1100200.n4.nabble.com/Size-problem-compiling-Firebird-2-0-3-in-Linux-tt1120181.html#none

To ship an embedded Firebird database with your program you need to deploy the following files and subfolders in the folder that contains the program itself:

fulvio@fulvio-ubuntu:~/Desktop/VVV-Release-Pack$ ls --format=single-column -R
.:
firebird
firebird.conf
vvv (main program)
vvv-start.sh

./firebird:
bin
firebird.msg
intl
libfbembed.so
libfbembed.so.2
libfbembed.so.2.0.4
libicudata.so
libicudata.so.30
libicudata.so.30.0
libicui18n.so
libicui18n.so.30
libicui18n.so.30.0
libicuuc.so
libicuuc.so.30
libicuuc.so.30.0
security2.fdb

./firebird/bin:
fb_lock_mgr
gbak
isql

./firebird/intl:
fbintl
fbintl.conf

Notice that many .so files are symbolic links.

You will need to edit the firebird.conf file. Look for the line containing "RootDirectory" and change it in this way:
RootDirectory = ./firebird
Notice that lines starting with '#' are comments: you must remove that character from this line.

Now you must create a script to start your program. In the example above it is called vvv-start.sh. The file must contain the following lines:

export LD_LIBRARY_PATH=./firebird
export FIREBIRD=.
./vvv

where the last line contains the name if the program to run, in this case vvv.

Now you should be able to start your program executing the script. You cannot directly execute the program because it needs the environment variables set by the script.

Thursday, June 3, 2010

Building a Firebird program

Building a program that connects to a Firebird server with IBPP is a rather simple task.
The IBPP site recommends to compile the IBPP source code together with your program: I put it in a subfolder and followed the recommendation. Everything worked well and I had a program that could connect to the server.

Under Windows there are no other requirements. Under Linux and OS X you also need to link to the Firebird client library, named fbclient or fbembed. This requires adding something like

-lfbembed

to the linker command line.