An accounting program is likely to be used for intensive data entry, so it is important to make typing easy for the end user.
Experienced users like to be able to work as much as possible without using the mouse: the mouse is useful for less experienced users but it slows down data entry.
An example is a dialog that contains a wxGrid and other controls: the user can move among controls using the TAB key (SHIFT-TAB to move backwards), but as soon as the focus goes to the wxGrid control is is trapped. It is possible to move from column to column using TAB but is is not possible to leave the grid any more: the user has to use the mouse to click another control.
This problem can be solved in a rather simple way, handling the wxEVT_KEY_DOWN event. Here is an example:
void fsGridBase::OnKeyDown( wxKeyEvent& event )
{
bool keyHandled = false;
if( event.GetKeyCode() == WXK_TAB ) {
if( event.ShiftDown() ) {
// shift-TAB: if we are in the first column move to the previous control
if( GetGridCursorCol() == 0 ) {
Navigate( wxNavigationKeyEvent::IsBackward );
keyHandled = true;
}
}
else {
// TAB: if we are in the last column move to the next control
if( GetGridCursorCol() == GetNumberCols() - 1 ) {
Navigate( wxNavigationKeyEvent::IsForward );
keyHandled = true;
}
}
}
if( !keyHandled )
event.Skip();
}
Using this code the TAB key moves from column to column, but when the cursor goes to the first or last column pressing TAB moves the focus to the previous or last control.
This code works with wxWidgets 2.9: I don't know if it works for 2.8 too.
Monday, January 24, 2011
Wednesday, January 12, 2011
Validation and the disappearing caret
A common requirement in accounting software is on-the-fly text validation: run some code in the lost focus event to elaborate the control's content. One example would be validating a VAT number, another is typing a few letters and showing a list of customers whose names start with the typed text.
All there situations have one thing in common: the code shows a new dialog from within the lost focus event handler of the control.
I just discovered a problem with wxWidgets: after closing the dialog the caret (the blinking cursor that shows where the next letter will be inserted) disappears so it is difficult to know which control has the focus. Tabbing to another control makes the caret reappear.
This is rather confusing for the user, so I made some searches: it looks like this is a well known problem and it looks like it is not going to be fixed soon.
A mail message also suggested a workaround: just set a flag in the event handler, then do what you need in the idle event handler. That handler is called just after the focus has moved from a control to another: in the idle handler check the value of the flag and execute the related code.
That is what I did and now the program is working well. The workaround does not even add too much complication to the code so it is satisfactory. It is only a pity that this problem is not mentioned in the documentation: it would save some headaches from time to time.
All there situations have one thing in common: the code shows a new dialog from within the lost focus event handler of the control.
I just discovered a problem with wxWidgets: after closing the dialog the caret (the blinking cursor that shows where the next letter will be inserted) disappears so it is difficult to know which control has the focus. Tabbing to another control makes the caret reappear.
This is rather confusing for the user, so I made some searches: it looks like this is a well known problem and it looks like it is not going to be fixed soon.
A mail message also suggested a workaround: just set a flag in the event handler, then do what you need in the idle event handler. That handler is called just after the focus has moved from a control to another: in the idle handler check the value of the flag and execute the related code.
That is what I did and now the program is working well. The workaround does not even add too much complication to the code so it is satisfactory. It is only a pity that this problem is not mentioned in the documentation: it would save some headaches from time to time.
Thursday, January 6, 2011
CMake - Install - Linux and OS X
As described in previous posts deploying a program that uses an embedded copy of Firebird requires shipping some database server files. For Windows this can be done by the software that creates the installation package.
For Linux and OS X it is possible to use the CMake INSTALL command. This command creates an INSTALL target in your development tool: buiding that target will copy files to the folder specified by the CMAKE_INSTALL_PREFIX variable.
This can be used to copy the compiled program, the Firebird runtime and other files as needed to the install folder. Then the install folder will contain a ready to run copy of the program, with all the needed files.
First use a variable to store the path of the files used by the Firebird embedded server. I keep them in a folder that has the same structure as the deployed files. Here is an example:
At the end of CMakeLists.txt add code like this:
install( TARGETS vvv DESTINATION . )
if( APPLE )
# copy the Firebird runtime
install( DIRECTORY ${FB_EMBEDDED_PATH}/ DESTINATION vvv.app/Contents/MacOS USE_SOURCE_PERMISSIONS )
install( FILES ${FB_EMBEDDED_PATH}/firebird/firebird.msg DESTINATION vvv.app/Contents/MacOS/firebird/bin/firebird )
# fix filename references in the runtime
set( FIXUP_COMMAND ${PROJECT_SOURCE_DIR}/MACOSX_fixup_bundle.sh " " ${CMAKE_INSTALL_PREFIX}/vvv.app )
install( CODE "execute_process( COMMAND ${FIXUP_COMMAND} )" )
endif( APPLE )
if( UNIX AND NOT APPLE )
# copy the Firebird runtime
install( DIRECTORY ${FB_EMBEDDED_PATH}/ DESTINATION . USE_SOURCE_PERMISSIONS )
# copy other files used only for the Linux version
install( FILES ${PROJECT_SOURCE_DIR}/linux_specific/readme.txt
${PROJECT_SOURCE_DIR}/linux_specific/License.txt
DESTINATION . )
install( FILES ${PROJECT_SOURCE_DIR}/linux_specific/vvv-start.sh
DESTINATION . PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE
GROUP_READ WORLD_READ WORLD_EXECUTE )
endif( UNIX AND NOT APPLE )
if( UNIX )
# set the installation path of the executable file and the resources (in OS X they are inside the bundle )
if( APPLE )
set( EXECUTABLE_INTALL_PATH vvv.app/Contents/MacOS )
set( RESOURCES_INTALL_PATH vvv.app/Contents/Resources )
else( APPLE )
set( EXECUTABLE_INTALL_PATH . )
set( RESOURCES_INTALL_PATH . )
endif( APPLE )
# copy other files to the installation path
install( FILES ${PROJECT_SOURCE_DIR}/vvv-struct-update.fdb
${PROJECT_SOURCE_DIR}/VVV.fbk
${PROJECT_SOURCE_DIR}/help/en/vvv.htb
DESTINATION ${EXECUTABLE_INTALL_PATH} )
endif( UNIX )
Under OS X CMake will execute a script named MACOSX_fixup_bundle.sh to patch the dylibs as described here. CMake has some support to automatically fix things like these: I struggled for some time but I was not able to understand how it works. Documentation is scarce to I gave up and I decided to directly run a script.
Here is the script used by CMake:
#!/bin/bash
# this script will fix the components of the Firebird runtime to make it run from any location
# the script receives the path of the bundle to fix
BUNDLEPATH=$*
# the following line contains the name of the executable file that will be patched
# it is the only line that should be changed when copying this file to another project
EXECFILE=${BUNDLEPATH}/Contents/MacOS/vvv
LIBPATH=${BUNDLEPATH}/Contents/MacOS/firebird
LIBBINPATH=${BUNDLEPATH}/Contents/MacOS/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}
You will need to edit the EXECFILE definition (near the file top) to change the name from "vvv" to your program's name.
For Linux and OS X it is possible to use the CMake INSTALL command. This command creates an INSTALL target in your development tool: buiding that target will copy files to the folder specified by the CMAKE_INSTALL_PREFIX variable.
This can be used to copy the compiled program, the Firebird runtime and other files as needed to the install folder. Then the install folder will contain a ready to run copy of the program, with all the needed files.
First use a variable to store the path of the files used by the Firebird embedded server. I keep them in a folder that has the same structure as the deployed files. Here is an example:
# look for the folder containing the firebird embedded files to ship with the program
find_path( FB_EMBEDDED_PATH firebird.conf ${PROJECT_SOURCE_DIR}/firebird_runtime ${PROJECT_SOURCE_DIR}/../../firebird_embedded_2.0.4_runtime )
message( STATUS "Embedded firebird files path: " ${FB_EMBEDDED_PATH} )
find_path( FB_EMBEDDED_PATH firebird.conf ${PROJECT_SOURCE_DIR}/firebird_runtime ${PROJECT_SOURCE_DIR}/../../firebird_embedded_2.0.4_runtime )
message( STATUS "Embedded firebird files path: " ${FB_EMBEDDED_PATH} )
At the end of CMakeLists.txt add code like this:
install( TARGETS vvv DESTINATION . )
if( APPLE )
# copy the Firebird runtime
install( DIRECTORY ${FB_EMBEDDED_PATH}/ DESTINATION vvv.app/Contents/MacOS USE_SOURCE_PERMISSIONS )
install( FILES ${FB_EMBEDDED_PATH}/firebird/firebird.msg DESTINATION vvv.app/Contents/MacOS/firebird/bin/firebird )
# fix filename references in the runtime
set( FIXUP_COMMAND ${PROJECT_SOURCE_DIR}/MACOSX_fixup_bundle.sh " " ${CMAKE_INSTALL_PREFIX}/vvv.app )
install( CODE "execute_process( COMMAND ${FIXUP_COMMAND} )" )
endif( APPLE )
if( UNIX AND NOT APPLE )
# copy the Firebird runtime
install( DIRECTORY ${FB_EMBEDDED_PATH}/ DESTINATION . USE_SOURCE_PERMISSIONS )
# copy other files used only for the Linux version
install( FILES ${PROJECT_SOURCE_DIR}/linux_specific/readme.txt
${PROJECT_SOURCE_DIR}/linux_specific/License.txt
DESTINATION . )
install( FILES ${PROJECT_SOURCE_DIR}/linux_specific/vvv-start.sh
DESTINATION . PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE
GROUP_READ WORLD_READ WORLD_EXECUTE )
endif( UNIX AND NOT APPLE )
if( UNIX )
# set the installation path of the executable file and the resources (in OS X they are inside the bundle )
if( APPLE )
set( EXECUTABLE_INTALL_PATH vvv.app/Contents/MacOS )
set( RESOURCES_INTALL_PATH vvv.app/Contents/Resources )
else( APPLE )
set( EXECUTABLE_INTALL_PATH . )
set( RESOURCES_INTALL_PATH . )
endif( APPLE )
install( FILES ${PROJECT_SOURCE_DIR}/vvv-struct-update.fdb
${PROJECT_SOURCE_DIR}/VVV.fbk
${PROJECT_SOURCE_DIR}/help/en/vvv.htb
DESTINATION ${EXECUTABLE_INTALL_PATH} )
endif( UNIX )
Under OS X CMake will execute a script named MACOSX_fixup_bundle.sh to patch the dylibs as described here. CMake has some support to automatically fix things like these: I struggled for some time but I was not able to understand how it works. Documentation is scarce to I gave up and I decided to directly run a script.
Here is the script used by CMake:
#!/bin/bash
# this script will fix the components of the Firebird runtime to make it run from any location
# the script receives the path of the bundle to fix
BUNDLEPATH=$*
# the following line contains the name of the executable file that will be patched
# it is the only line that should be changed when copying this file to another project
EXECFILE=${BUNDLEPATH}/Contents/MacOS/vvv
LIBPATH=${BUNDLEPATH}/Contents/MacOS/firebird
LIBBINPATH=${BUNDLEPATH}/Contents/MacOS/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}
You will need to edit the EXECFILE definition (near the file top) to change the name from "vvv" to your program's name.
Subscribe to:
Posts (Atom)