Saturday, February 26, 2011

Printing text with wxWidgets

A common requirement for an accounting program is printing some text, often for reports.

One possible solution is creating an HTML file and printing it using the wxHtmlEasyPrinting. I used it and it is a very simple and quick solution, but it does not give you precise control on the text layout.

For those who want to print using a more traditional approach there is the wxWidgets printing framework: it shields some of the low level tasks but it leaves you full control on the page layout.
I spent a lot of time trying to understand how to use it to print text lines and I found it difficult to understand the documentation and the printing sample (as usual, the wxWidgets samples are the best place to look for inspiration). Samples and documentation are more geared towards printing graphics, not text.
In the end I found a satisfactory solution and I will describe it here.

To use the printing framework you need to derive your own class from wxPrintout, and at least to implement the OnPrintPage() and HasPage() functions.

The OnPrintPage() function is called by the printing framework for each page that will be printed. It will contain the actual print code.
The problem, in the print code, is that each function that prints something requires coordinates to tell it where to print in the page. Those coordinates are not in millimeters or inches, so we need to find a way to convert millimeters (for example) to the units used by the device context. I used the following code to determine a conversion factor from millimeters to device units. The code is based on the printing sample.

    wxDC *dc = GetDC();
    if( dc == NULL || !dc->IsOk() ) {

        // report error and...
        return false;
    }

    // Get the logical pixels per inch of screen and printer
    int ppiScreenX, ppiScreenY;
    GetPPIScreen(&ppiScreenX, &ppiScreenY);
    int ppiPrinterX, ppiPrinterY;
    GetPPIPrinter(&ppiPrinterX, &ppiPrinterY);

    // This scales the DC so that the printout roughly represents the the screen
    // scaling. The text point size _should_ be the right size but in fact is
    // too small for some reason. This is a detail that will need to be
    // addressed at some point but can be fudged for the moment.
    float scale = (float)((float)ppiPrinterX/(float)ppiScreenX);

    // Now we have to check in case our real page size is reduced (e.g. because
    // we're drawing to a print preview memory DC)
    int pageWidth, pageHeight;
    int w, h;
    dc->GetSize(&w, &h);
    GetPageSizePixels(&pageWidth, &pageHeight);

    // If printer pageWidth == current DC width, then this doesn't change. But w
    // might be the preview bitmap width, so scale down.
    float overallScale = scale * (float)(w/(float)pageWidth);
    dc->SetUserScale(overallScale, overallScale);

    // Calculate conversion factor for converting millimetres into logical
    // units. There are approx. 25.4 mm to the inch. There are ppi device units
    // to the inch. Therefore 1 mm corresponds to ppi/25.4 device units. We also
    // divide by the screen-to-printer scaling factor, because we need to
    // unscale to pass logical units to DrawLine.
    float logUnitsFactor = (float)(ppiPrinterX/(scale*25.4));



now we can, for example, convert 20 millimeters to device units by computing 20*logUnitsFactor.
Font size in points will be respected, so the code for printing some text lines will be the following.

    dc->SetFont( m_ReportFont );
    dc->SetBackgroundMode( wxTRANSPARENT );
    dc->SetTextForeground( *wxBLACK );
    dc->SetTextBackground( *wxWHITE );

    // line height
    float charH = dc->GetCharHeight();

    // coordinates of the first line
    float x = m_MarginLeft*logUnitsFactor;
    float y = m_MarginTop*logUnitsFactor;
   
    // print lines
    dc->DrawText( "First line", x, y );
    y += charH;
    dc->DrawText( "Second line", x, y );
    y += charH;

One problem of the printing framework when printing reports from a database if that it seems that it requires the previous knowledge of how many pages will be printed. Of course this is not the case for a database report: the most efficient solution is reading the rows while printing the report, so there is no previous knowledge of how many pages will be printed.
The documentation says that you must provide an implementation of the GetPageInfo() function, telling the framework how many pages you are going to print. This is not completely true: if you do not provide that function the printing framework will assume a default of 32000 pages.
You can start with that default, then you can use your implementation of the HasPage() function to stop printing when it will be time. Just return false when you have printed all the needed rows.

Once you have created your custom printout class just use something like the following code to print your pages.

    MyPrintout printout( "Print name" );
   
    wxPrintDialogData printDialogData(* m_PrintData);
    wxPrinter printer( &printDialogData );
    bool success = printer.Print( NULL, &printout, true );

    if( !success ) {
        if (wxPrinter::GetLastError() == wxPRINTER_ERROR) {
            CUtils::MsgErr( "Printing error" );
        }
        else {
            // print has been canceled
        }
    }

That code will show a common dialog to select the desired printer, then it will print to that printer.

Monday, January 24, 2011

wxGrid, TAB and the trapped focus

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.

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.

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:

  # 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} )

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.

Monday, December 20, 2010

CMake - Build an OS X Bundle

OS X applications are structured as bundles. A bundle is a folder with a predefined structure that contains the program and other files.
It is possible to create an application bundle with CMake using specific commands.

First we must specify the files containing the application icons. For example:

#--------------------------------------------------------------------------------
# For Apple set the icns file containing icons
if(APPLE)
  # icon files to copy in the bundle
  set( OSX_ICON_FILES ${CMAKE_CURRENT_SOURCE_DIR}/graphics/vvv.icns ${CMAKE_CURRENT_SOURCE_DIR}/graphics/vvv-document.icns )
  # set where in the bundle to put the icns files
  set_source_files_properties( ${OSX_ICON_FILES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
  # include the icns files in the target
  set( SRCS ${SRCS} ${OSX_ICON_FILES} )
ENDIF(APPLE)


add_executable( vvv WIN32 MACOSX_BUNDLE ${SRCS} )

This example defines two icons, one for the application and one for the application documents. The SRCS variable already contains the list of source files and this code adds the icons to the files list.
The ADD_EXECUTABLE command in the last line tells CMake to create an OS X bundle.

The following code will tell CMake where to find the Info.plist that will be copied into the bundle:

if(APPLE)
  # configure CMake to use a custom Info.plist
  set_target_properties( vvv PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${PROJECT_SOURCE_DIR}/vvv-Info.plist )
ENDIF(APPLE)

Using the code above CMake will be able to create an OS X application bundle.

Sunday, December 5, 2010

CMake - Build with Boost and Firebird

In the previous post I explained how to use wxWidgets with CMake.
Now we will look at the steps to successfully build a program that uses Firebird and the Boost library.

Boost is a well know and widely used library. Most of it does not require linking to a library file, just include some headers. That is what I do in my program, and here is the CMake code:

# try find BOOST (we need headers only)
find_package( Boost 1.37.0 )
if( Boost_FOUND)
  include_directories( ${Boost_INCLUDE_DIRS} )
endif( Boost_FOUND)

more information in C:\Program Files\CMake 2.8\share\cmake-2.8\Modules\FIndBoost.cmake. I am sure that you will figure out the correct path for your computer.

I use the IBPP library as a data access layer, and it requires a preprocessor definition to specify the operating system. Here is the CMake code:

# definitions for IBPP
if( WIN32 )
  add_definitions( -DIBPP_WINDOWS )
elseif( APPLE )
  add_definitions( -DIBPP_DARWIN )
else( WIN32 )
  add_definitions( -DIBPP_LINUX )
endif( WIN32 )

Under Windows the Firebird client library is loaded dynamically so there are no othe rrequirements.
Under Linux and OS X the program must link with the client library. Here is the CMake code:

if( UNIX )
  # link to the Firebird client library
  # the last path is used for OS X where I have a local copy (not installed)
  find_library( FB_LIB fbembed PATHS /usr/lib /usr/local/lib /opt/firebird/lib ${PROJECT_SOURCE_DIR}/firebird_runtime/firebird )
  if( NOT FB_LIB )
    message( FATAL_ERROR "Unable to find firebird interface" )
  else( NOT FB_LIB )
    message( STATUS "Firebird interface: " ${FB_LIB} )
  endif( NOT FB_LIB )

  target_link_libraries( MyTarget ${FB_LIB} )
endif( UNIX )

the find_libary command accepts some paths as parameters. They are used as suggestions to find the file. The example contains my paths, you will probably change them.

With this code in CMakeLists.txt it should be possible to successfully build the program. The next problem will be make it run using an embedded copy of Firebird.

Friday, December 3, 2010

CMake - wxWidgets

CMake is released with a large set of source code that can be used to accomplish common tasks. One of this tasks is looking for a package used by your project.

For example, let's see how to look for the wxWidgets library. We can use the FIND_PACKAGE command for this purpose: CMake already knows how to handle wxWidgets.

Just add the following lines to CMakeLists.txt:

FIND_PACKAGE(wxWidgets REQUIRED html adv core base net aui xrc qa richtext )
INCLUDE(${wxWidgets_USE_FILE})
TARGET_LINK_LIBRARIES(myTarget ${wxWidgets_LIBRARIES})


The first line tells CMake to look for wxWidgets. The REQUIRED clause says that you require the specified modules.
The FIND_PACKAGE command executes some code and sets a number of variables if it finds the library.
The second line uses one of those variables to tell CMake where to look for include files.
The third line tells CMake how to link a target with wxWidgets.

Now let's see what happens when we run CMake from its GUI. Press Configure and you will probably see a number of red rows that need to be fixed. The rows content changes with the operating system.

Linux

I create two build folders, one for the debug configuration and one for the release one.

You need to look at the following variables:
  • CMAKE_BUILD_TYPE: set it to Debug for a build folder and to Release for the other.
  • wxWidgets_CONFIG_EXECUTABLE: it is the path to the wx-config script for the chosen copy of wxWidgets. For example, /usr/local/bin/wx-config for a library that has been installed, or /home/fulvio/wxSVN/buildgtk/wx-config for a library that has not been installed. In the library has not been installed you must point to the wx-config file in the folder with the right configuration (for example debug or not).
  • wxWidgets_USE_DEBUG: check it if you want to use a debug version of the library.
  • wxWIDGETS_USE_STATIC: check it if you ant to use a static version of the library.
  • wxWidgets_USE_UNICODE: check it if you want to use the Unicode version of the library. If you are using version 2.9 or later this setting does not make sense any more (the llibrary is only Unicode): I leave it checked and everything works well.
  • wxWidgets_wxrc_EXECUTABLE: this is probably a wxrc setting. Since I do not use it I left its value set to NOTFOUND.
OS X

I create two build folders, one for the debug configuration and one for the release one.

You need to look at the following variables:
  • CMAKE_BUILD_TYPE: set it to Debug for a build folder and to Release for the other.
  • CMAKE_OSX_ARCHITECTURES:  I set it to i386.
  • CMAKE_OSX_DEPLOYMENT_TARGET: I leave this blank.
  • wxWidgets_CONFIG_EXECUTABLE: it is the path to the wx-config script for the chosen copy of wxWidgets. For example /Users/fulvio/wxMac-2.8.10/buildgtk/wx-config for a library that has not been installed. In the library has not been installed you must point to the wx-config file in the folder with the right configuration (for example debug or not).
  • wxWidgets_wxrc_EXECUTABLE: this is probably a wxrc setting. Since I do not use it I left its value set to NOTFOUND.
Windows

In Windows I create a single build folder. It will contain a Visual Studio project file with both a debug and a release configuration.

You need to look at the following variables:
  • wxWidgets_CONFIGURATION: set to mswud, mswu or mswd. This is not very clear to me, but I set it to mswu and everything works well.
  • wxWidgets_ROOT_DIR: it is the path to the root folder of the wxWidgets installation, for example E:/wxWidgets-2.8.10. If CMake does not find the path you need to set it manually and configure again.
  • wxWidgets_LIB_DIR: it is the path of the folder that contains the libraries that will be linked to your program. Usually CMake can find this path by itself if it knows the root dir.
  • wxWidgets_USE_REL_AND_DBG is a boolean value. Check it if you want to have a project with both a debug and a release configuration. You will probably check it.
  • wxWidgets_wxrc_EXECUTABLE: this is probably a wxrc setting. Since I do not use it I left its value set to NOTFOUND.
As you can see configuring CMake to use your copy of wxWidgets is an easy task.
It is also easy to use different versions of wxWidgets with your project. Just build the different versions in different folders (do not install them in *nix), then create different build folders for the different wxWidgets versions. For each folder configure CMake to look for wxWidgets in the right folder, manually setting the required variables to the right value.