tag:blogger.com,1999:blog-70988351754386929352024-03-05T07:18:02.811-08:00Accounting++Developing an accounting program with open source toolsFulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.comBlogger35125tag:blogger.com,1999:blog-7098835175438692935.post-3271676883448738082012-12-27T13:31:00.001-08:002012-12-27T13:41:39.872-08:00Printing to a PDF fileA common requirement for an accounting program is the ability to "print" something to a PDF file instead of printing it on paper.<br />
<br />
It is possible to install some software that creates a virtual printer, so that everything is printed to that printer is converted to a PDF file. It is a simple solution but it is a rather limited one: for example you do not have control on the name of the created file unless you stick with a particular program, and this is not a good idea. If that program is abandoned you are out of luck. There are more problems if you want to create a program that runs under multiple operating systems.<br />
<br />
A much better solution is to create the PDF file from the printing program, so you do not have external dependencies.<br />
For wxWidgets programs there is a very good library: <a href="http://wxcode.sourceforge.net/components/wxpdfdoc/">wxPdfDocument</a>. It can be used to create PDF files with custom commands, but it also contains the <span style="font-family: "Courier New",Courier,monospace;">wxPdfDc</span> class: it is a device context derived from <span style="font-family: "Courier New",Courier,monospace;">wxDC</span>, so it can be used to print to a PDF file using the same code used to print to paper. This is a huge advantage, of course.<br />
<br />
I have been able to print to a PDF file with very small code changes. I will describe those changes making a reference to a previous post that described <a href="http://accountingplusplus.blogspot.it/2011/02/printing-text-with-wxwidgets.html" target="_blank">how to print from wxWidgets</a>: you should read it before reading the rest of this post.<br />
<br />
That post contained some code used to compute a variable called <span style="font-family: "Courier New",Courier,monospace;">logUnitsFactor</span>, used to convert millimeters to logical units. That code did not work with <span style="font-family: "Courier New",Courier,monospace;">wxPdfDc<span style="font-family: inherit;"><span style="font-family: inherit;"></span></span></span> so I had to change it. First add the include file:<br />
<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0hnpV0-mDWd5-pk4_gGodaTIIZjtaWADFqmBp1A7ODm64guVfJa0sOeMe1HcIipJibgVxo1EUYmuhzpePs5-o-M-6lZQsUeiAr1SbFFzYbyNjznlX7nttMUyoltPHRkFSGoh5HgaOO0vU/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> #include "wx/pdfdc.h"
</code></pre>
<br />
Then use the following code to compute <span style="font-family: "Courier New",Courier,monospace;">logUnitsFactor</span> instead of the code used in the older post:<br />
<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0hnpV0-mDWd5-pk4_gGodaTIIZjtaWADFqmBp1A7ODm64guVfJa0sOeMe1HcIipJibgVxo1EUYmuhzpePs5-o-M-6lZQsUeiAr1SbFFzYbyNjznlX7nttMUyoltPHRkFSGoh5HgaOO0vU/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> wxSize devicePPI = dc->GetPPI();
int ppiPrinterX, ppiPrinterY;
ppiPrinterX = devicePPI.GetWidth();
ppiPrinterY = devicePPI.GetHeight();
int ppiScreenX, ppiScreenY;
wxScreenDC sdc;
ppiScreenX = sdc.GetPPI().GetWidth();
ppiScreenY = sdc.GetPPI().GetHeight();
float scale = (float)((float)ppiPrinterY/(float)ppiScreenY);
dc->SetUserScale(scale, scale);
logUnitsFactor = (float)(ppiPrinterX/(scale*25.4));
</code></pre>
<br />
where <span style="font-family: "Courier New",Courier,monospace;">dc</span> is a pointer to a <span style="font-family: "Courier New",Courier,monospace;">wxPdfDc</span> object.<br />
If you are writing some generic code you can use this test to know if you are working with a <span style="font-family: "Courier New",Courier,monospace;">wxPdfDc</span>:<br />
<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0hnpV0-mDWd5-pk4_gGodaTIIZjtaWADFqmBp1A7ODm64guVfJa0sOeMe1HcIipJibgVxo1EUYmuhzpePs5-o-M-6lZQsUeiAr1SbFFzYbyNjznlX7nttMUyoltPHRkFSGoh5HgaOO0vU/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> if( dc->IsKindOf(CLASSINFO(wxPdfDC)) ) {
// custom code if we are using wxPdfDC
}
else {
// standard code
}
</code></pre>
<br />
Now it's time to print something. The following code can be used to print to a PDF file with a given name. The printing framework does not directly support printing to a PDF so I had to copy some of its code.<br />
<br />
<pre style="background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0hnpV0-mDWd5-pk4_gGodaTIIZjtaWADFqmBp1A7ODm64guVfJa0sOeMe1HcIipJibgVxo1EUYmuhzpePs5-o-M-6lZQsUeiAr1SbFFzYbyNjznlX7nttMUyoltPHRkFSGoh5HgaOO0vU/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> m_PrintData->SetFilename( fileName );
MyPrintout printout( "Print name" );
wxPdfDC dc( *m_PrintData );
dc.SetResolution( 600 );
printout.SetDC( &dc );
printout.OnPreparePrinting();
printout.OnBeginPrinting();
int minPageNum = 1;
int maxPageNum = 9999;
if( printout.OnBeginDocument(minPageNum, maxPageNum) ) {
for ( int pn = minPageNum;
pn >= maxPageNum && printout.HasPage(pn);
pn++ )
{
dc.StartPage();
bool cont = printout.OnPrintPage(pn);
dc.EndPage();
if ( !cont ) break;
}
printout.OnEndDocument();
}
printout.OnEndPrinting();
</code></pre>
<br />
With this code the same wxPrintout object used to print to paper can be used to print to a PDF file.<br />
<br />
<br />Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com2tag:blogger.com,1999:blog-7098835175438692935.post-48621635936920771522012-12-01T08:29:00.000-08:002012-12-01T08:30:37.497-08:00CMake: using an external libraryThere are plenty of software libraries designed to accomplish useful tasks, so using them is a common requirement. This is not particularly difficult to do, and after some testing I have developed the following solution. It works for any library, not only for wxWidgets ones, but for this example I will show how to use wxPdfDocument, an excellent library for PDF files creation.<br />
<br />
Here is the CMakeFiles.txt code:<br />
<br />
<pre nbsp="nbsp" style="background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> # look for wxPdfDocument
# ...include
find_path( WXPDFDOC_INCLUDE_PATH pdfdc29.h DOC "wxPdfDocument include files path" )
if( NOT WXPDFDOC_INCLUDE_PATH )
message( FATAL_ERROR "Unable to find wxPdfDocument include path" )
else( NOT WXPDFDOC_INCLUDE_PATH )
# up une folder to include <wx/....h>
set( WXPDFDOC_INCLUDE_PATH ${WXPDFDOC_INCLUDE_PATH}/.. )
message( STATUS "wxPdfDocument include path: " ${WXPDFDOC_INCLUDE_PATH} )
endif( NOT WXPDFDOC_INCLUDE_PATH )
# ...libraries
if(WIN32)
find_file( WXPDFDOC_LIB_PATH_DEBUG wxcode_msw29ud_pdfdoc.lib DOC "Path to the DEBUG wxPdfDocument library" )
find_file( WXPDFDOC_LIB_PATH_RELEASE wxcode_msw29u_pdfdoc.lib DOC "Path to the RELEASE wxPdfDocument library" )
if( NOT WXPDFDOC_LIB_PATH_DEBUG OR NOT WXPDFDOC_LIB_PATH_RELEASE )
message( FATAL_ERROR "Unable to find wxPdfDocument library files" )
else ( NOT WXPDFDOC_LIB_PATH_DEBUG OR NOT WXPDFDOC_LIB_PATH_RELEASE )
message( STATUS "wxPdfDocument debug library: " ${WXPDFDOC_LIB_PATH_DEBUG} )
message( STATUS "wxPdfDocument release library: " ${WXPDFDOC_LIB_PATH_RELEASE} )
endif ( NOT WXPDFDOC_LIB_PATH_DEBUG OR NOT WXPDFDOC_LIB_PATH_RELEASE )
else(WIN32)
find_library( WXPDFDOC_LIB_PATH libwxcode_gtk2u_pdfdoc-2.9.a )
if( NOT WXPDFDOC_LIB_PATH )
message( FATAL_ERROR "Unable to find wxPdfDocument library file" )
else( NOT WXPDFDOC_LIB_PATH )
message( STATUS "wxPdfDocument library: " ${WXPDFDOC_LIB_PATH} )
endif( NOT WXPDFDOC_LIB_PATH )
endif(WIN32)
# link and include
if(WIN32)
target_link_libraries( GeASt debug ${WXPDFDOC_LIB_PATH_DEBUG} optimized ${WXPDFDOC_LIB_PATH_RELEASE} )
else(WIN32)
target_link_libraries( GeASt ${WXPDFDOC_LIB_PATH} )
endif(WIN32)
include_directories( ${WXPDFDOC_INCLUDE_PATH} )
</code></pre>
<br />
<br />
The code looks for the include files and the library file(s), then it uses those information to set the include path and to link the libraries.<br />
<br />
The behaviour is different for Windows and for *nix operating systems. Under Windows there are two different libraries, one for the debug and one for the release build. Under Linux and Mac there is a single library file.<br />
<br />
An important note for wxWidgets users: always link the wxWidgets libraries AFTER all other libraries, otherwise you might get linker errors (undefined symbols). This happened to me under Linux.<br />
<br />
This means that the<br />
<br />
<pre nbsp="nbsp" style="background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> target_link_libraries(GeASt ${wxWidgets_LIBRARIES})
</code></pre>
<br />
<br />
line must be the last TARGET_LINK_LIBRARIES line.<br />
<br />
<br />Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-66391176446436249642012-06-21T07:56:00.000-07:002012-06-21T07:56:59.716-07:00Firebird: connecting to an embedded serverAs you can read in a <a href="http://accountingplusplus.blogspot.it/2012/02/firebird-choosing-owner-for-database.html">recent post</a> I have discovered that using the SYSDBA user to connect to a deployed database from a program is not a good idea.<br />
Since my main development platform is Windows I have used a Windows database manager program to add a new user with its own password, and that user is the owner of the database and its objects. The new username and password are stored in the security2.fdb file in the Firebird installation.<br />
<br />
If the program connects to a server it uses those username and password and everything works as expected.<br />
The program can also use and embedded server for smaller customers where a single user is enough. The Windows embedded server will allow any username and password, without checking them, but you must use the correct username or you will not be able to open the database tables. So I ended up using an empty password for embedded connections and it worked under Windows.<br />
<br />
As soon as I tested it on a Mac I discovered that I could not connect to the database: wrong username or password.<br />
It happens that it is possible to set up an embedded server under Linux and Mac, as I described in older posts, but these servers will still use security2.fdb to authenticate the user.<br />
<br />
The Firebird server stores usernames and passwords in the security2.fdb file, so I solved the problem copying the security2.fdb file from my Windows server (the one containing the default username and password to use for non-SYSDBA access) and using it for the Linux and Mac embedded servers.<br />
Of course I also had to use the right password for embedded access.<br />
<br />
My program has an option to let the user connect to a server or use an embedded one.<br />
If he connects to a server he will be able to enter a username and a password.<br />
If he chooses an embedded server the program will always connect with the username and password used to develop the database. Shipping that security2.fdb file with the Linux and Mac embedded servers will make sure that the connection will be successful.Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com4tag:blogger.com,1999:blog-7098835175438692935.post-69981554235882538672012-04-28T10:37:00.000-07:002012-04-28T10:37:24.134-07:00C++: rounding a double to a fixed number of decimalsAn accounting program handles money amounts, so it is important to avoid computing errors.<br />
<br />
The problem is that, using a double, most values cannot be stored exactly but there is a small error. Numbers are stored in base 2, and math tells us that only the decimal numbers obtained dividing some integer value by a power of two will have an exact representation in that base.<br />
So 2.5, 5.25, 3.125 and similar numbers will be stored exactly, while all other number will be represented by a periodic number.<br />
What happens is similar to representing 1/3 in base 10: the result is 0.333333333... with an infinite number of decimals.<br />
<br />
A double value has a finite size so it cannot contain infinite decimals: the result is a small error.<br />
There other ways to store decimal values exactly, using a custom class, but I prefer to use a standard type so I use doubles.<br />
Doubles have a precision of about 15 digits, so there is plenty of room to handle money amounts. Rounding errors will be much smaller than the printed values (we will print few decimals) so there should be no problems if computations are handled correctly.<br />
<br />
This is true, but I have found a situation where rounding errors lead to a wrong result. I was testing the program and it computed the VAT for an invoice: the right value was 3.16 but the program printed 3.15.<br />
<br />
A quick investigation showed that the exact value was 3.155: rounding to two decimals leads to 3.16.<br />
The debuggers showed me that the approximated value contained in the double variable was 3.1549999999999998. The error is very small, but is is enough to cause rounding to 3.15.<br />
<br />
I made some quick searches but I did not find anything about this problem, so I had to find a solution by myself.<br />
In the end I decided to modify the function that I use to round to a certain number of decimals: now, before rounding, I add a very small amount (say 0.00000000001) to the number that will be rounded. The value is very small but it is enough to make the number (in the previous example) to become a little more than 3.155 so now rounding is correct.<br />
<br />
I don't know if there are better solution to this problem, but I think that adding such a small value to a number that represents a money amount is not likely to introduce errors (at least I have not found any), and it solves the problem.<br />
<br />Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com7tag:blogger.com,1999:blog-7098835175438692935.post-82211522016807942412012-02-24T14:05:00.000-08:002012-02-24T14:05:50.032-08:00Firebird: choosing an owner for database deploymentLike most users when I started using Firebird I connected using the SYSDBA username. That is the default username for server administration: every server has it.<br />
<br />
It looked like a good idea because I did not have to care with users management, but I have now realized that using SYSDBA for database development can cause problems when the database is deployed to the customer's computer.<br />
<br />
A Firebird database and all its tables have an owner: it is the username that was used for connecting to the server when the object was created. Tables can have a different owner than the database, but this an advanced topic. The simplest solution is having a single owner for the database and all the tables.<br />
<br />
Both the owner and SYSDBA have full access to the database, so you need to connect to the server as the owner or as SYSDBA. Of course things can be more complicated about database security, but I want to keep things simple.<br />
<br />
The server stores usernames and passwords in a special database (security2.fdb). When a user connects to the server the username and the password are checked using the security database. If the username does not exist or the password is incorrect the connection is refused. When a user is connected to a database he can open a table only if he is the owner or if he is SYSDBA.<br />
<br />
I am targeting small customers, so most of them will use the embedded server. The embedded server works differently about security: it will not check the username and the password (there is no security2.fdb) so you will always be able to connect with any username/password couple. Nevertheless Firebird will still check that the username is the owner or SYSDBA before opening a table. Other usernames can connect to the server but cannot open tables.<br />
<br />
Customers that will require a multi-user solution will need to install the full server, but they will probably have no experience with Firebird or other database servers. Most of them will use Firebird only for our software, but there is a chance that some of them will already have a Firebird server installed and used by other software.<br />
<br />
Deploying a database owned by SYSDBA will cause problems to those users. The only way to connect to such a database is using the SYSDBA username, and since the server has been installed by other people users might not know the SYSDBA password. A database administrator would not like to let a software connect as SYSDBA, since that user has full access to all the databases handled by the server.<br />
<br />
For this reason a much better solution is creating a new username and connecting with that username to create and develop the database, so that the owner is not SYSDBA. Then, after database deployment, the software can connect in different ways for different needs.<br />
<br />
The software can connect to an embedded server using the SYSDBA username and any password. It will be able to access data without problems. Users will not not be asked for a username.<br />
<br />
When connecting to a full server the software will ask for a username and a password. There are two options:<br />
<ul><li>If the server will only host our database the simplest option is connecting as SYSDBA. It will be possible to access the database with minimal effort.</li>
<li>If using SYSDBA is not an option somebody must create a new user named as the database owner (the same username used for database development) using any password he likes. The software will connect using that username and password so it will be able to access the database without administrator privileges. The username must be the same as the owner, but the password does not need to be the same used for development. Remember that the password is only used to connect to the server: once you are connected Firebird only checks the username to grant access to tables.</li>
</ul>If you already have a database owned by SYSDBA and you want to change its owner you can save a script to recreate the database using some administration tool ("Extract metadata" or similar). Then create a new user that will became the database owner, connect to Firebird using that username and create a new database. Then execute the script to recreate the database structure.<br />
<br />
If you have a lot of data and you need to keep them you can try <a href="http://blog.upscene.com/thomas/index.php?m=07&y=08&entry=entry080730-233217">FBOwnerMigrator</a> by Thomas Steinmaurer.Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com3tag:blogger.com,1999:blog-7098835175438692935.post-89895627710266034882012-01-17T23:46:00.000-08:002012-01-17T23:46:32.325-08:00wxGrid editors and the arrow keysWhen editing a wxGrid cell the standard behaviour is not very user-friendly: if you press one of the arrow keys you can move the caret in the editor, but you cannot select another cell. For example, a user would expect that pressing the up key would close the cell editor and move the selection to the cell above, but nothing happens.<br />
<br />
It is possible to add this behaviour by handling the EVT_GRID_EDITOR_CREATED event for the grid. For example:<br />
<br />
<pre style="background: url("https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0hnpV0-mDWd5-pk4_gGodaTIIZjtaWADFqmBp1A7ODm64guVfJa0sOeMe1HcIipJibgVxo1EUYmuhzpePs5-o-M-6lZQsUeiAr1SbFFzYbyNjznlX7nttMUyoltPHRkFSGoh5HgaOO0vU/s320/codebg.gif") repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> EVT_GRID_EDITOR_CREATED( fsGridBase::OnEditorCreated )
</code></pre><br />
The OnEditorCreated() function simply sets an event handler for the EVT_KEY_DOWN event for the editor control:<br />
<br />
<pre style="background: url("https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0hnpV0-mDWd5-pk4_gGodaTIIZjtaWADFqmBp1A7ODm64guVfJa0sOeMe1HcIipJibgVxo1EUYmuhzpePs5-o-M-6lZQsUeiAr1SbFFzYbyNjznlX7nttMUyoltPHRkFSGoh5HgaOO0vU/s320/codebg.gif") repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> void fsGridBase::OnEditorCreated( wxGridEditorCreatedEvent& event )
{
event.GetControl()->Bind( wxEVT_KEY_DOWN, &fsGridBase::OnGridEditorKey, this );
event.Skip();
}
</code></pre><br />
The OnGridEditorKey() function can handle the EVT_KEY_DOWN event as desired by the developer. The basic rule is the following: call <span style="font-family: "Courier New",Courier,monospace;">event.Skip()</span> to send the event to the editor control, getting the usual behaviour.<br />
Call <span style="font-family: "Courier New",Courier,monospace;">GetEventHandler()->ProcessEvent(event)</span> to send the event directly to the grid, bypassing the cell editor. This choice will let the arrow keys move to another cell.<br />
<br />
One example could be the following:<br />
<br />
<pre style="background: url("https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0hnpV0-mDWd5-pk4_gGodaTIIZjtaWADFqmBp1A7ODm64guVfJa0sOeMe1HcIipJibgVxo1EUYmuhzpePs5-o-M-6lZQsUeiAr1SbFFzYbyNjznlX7nttMUyoltPHRkFSGoh5HgaOO0vU/s320/codebg.gif") repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> void fsGridBase::OnGridEditorKey( wxKeyEvent& event )
{
switch ( event.GetKeyCode() ) {
case WXK_DOWN:
case WXK_UP:
GetEventHandler()->ProcessEvent( event );
break;
case WXK_LEFT:
{
wxGridCellEditor* editor = GetCellEditor( GetGridCursorRow(), GetGridCursorCol() );
wxControl* ctrl = editor->GetControl();
if( ctrl->IsKindOf(CLASSINFO(wxTextCtrl)) ) {
wxTextCtrl* tp = dynamic_cast<wxTextCtrl*>( ctrl );
if( tp->GetInsertionPoint() == 0 ) {
// we are at the beginning of the text control so let's move to the previous column
GetEventHandler()->ProcessEvent( event );
}
else {
event.Skip();
}
}
editor->DecRef();
}
break;
case WXK_RIGHT:
{
wxGridCellEditor* editor = GetCellEditor( GetGridCursorRow(), GetGridCursorCol() );
wxControl* ctrl = editor->GetControl();
if( ctrl->IsKindOf(CLASSINFO(wxTextCtrl)) ) {
wxTextCtrl* tp = dynamic_cast<wxTextCtrl*>( ctrl );
if( tp->GetInsertionPoint() == tp->GetLastPosition() ) {
// we are at the end of the text control so let's move to the next column
GetEventHandler()->ProcessEvent( event );
}
else {
event.Skip();
}
}
editor->DecRef();
}
break;
default:
event.Skip();
}
}
</code></pre><br />
The up and down arrows will always move to another cell. The left and right arrows will move the caret in the control as usual, but if the caret is at the beginning or at the end of the control the key will move to another cell.<br />
<br />
The example above is a good starting point, but it will cause problems if the grid uses wxGridCellChoiceEditors because that editor would not work well with keyboard keys. It will work well with the mouse, anyway.Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-77795540400038280992012-01-09T01:42:00.000-08:002012-01-09T01:42:02.745-08:00Easily adding lines to a wxGridwxGrid is a good control, but users expect a behaviour more similar to a spreadsheet, so I derived my own control from wxGrid, with a number of customizations.<br />
<br />
The first is the ability to easily add a new row at the end of the grid. With some simple code it is possible to always have an empty row at the end of the grid, so users can simply go to that line and start typing data: a new row will pop up at the end of the grid and so on...<br />
<br />
All we need to do is handling the EVT_GRID_SELECT_CELL event:<br />
<br />
<pre style="background: url("https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0hnpV0-mDWd5-pk4_gGodaTIIZjtaWADFqmBp1A7ODm64guVfJa0sOeMe1HcIipJibgVxo1EUYmuhzpePs5-o-M-6lZQsUeiAr1SbFFzYbyNjznlX7nttMUyoltPHRkFSGoh5HgaOO0vU/s320/codebg.gif") repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> void fsGridBase::OnSelectCell( wxGridEvent& event )
{
int row = event.GetRow();
int lastRow = GetTable()->GetNumberRows() - 1;
if( row == lastRow ) {
AppendRows( 1 );
}
event.Skip();
}
</code></pre><br />
as soon as a cell is selected in the last row a new row is appended. This makes the grid behaviour more similar to the one of a spreadsheet.Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-25158773636991787032011-11-06T08:23:00.000-08:002011-11-06T08:23:04.837-08:00Hiding and showing controls in a dialogOften a single dialog can be used for different purposes, with slight variations. Sometimes a control is not needed if the uses chooses a certain option, but it is needed if he chooses a different option.<br />
<br />
If disabling the control is enough the simplest solution is to use UPDATE_UI events, but those events cannot be used to hide and show controls.<br />
<br />
Dynamically hiding a control requires a few lines of code, but this feature is not very well documented.<br />
Suppose that <span style="font-family: "Courier New",Courier,monospace;">m_Control</span> is a pointer to the control to be hidden, and that <span style="font-family: "Courier New",Courier,monospace;">m_Sizer</span> is a pointer to the sizer that contains that control. The following code, in a method of a wxDialog-derived window, hides the control at run time, rearranging the other controls and the window to reuse the resulting empty space:<br />
<br />
<div style="font-family: "Courier New",Courier,monospace;"><span style="font-size: small;"> m_Sizer->Hide( m_Control );</span></div><span style="font-size: small;"><span style="font-family: "Courier New",Courier,monospace;"> m_Sizer->Layout();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> GetSizer()->SetSizeHints(this);</span></span>Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-54207914769280554752011-09-26T00:31:00.000-07:002011-09-26T00:31:55.397-07:00Directly printing to a certain printerWhen users have more than one printer available, sometimes a requirement is being able to print many documents to a certain printer. That printer is not necessarily the default printer.<br />
Think of printing a set of invoices, for example. This is not a trivial task in wxWidgets.<br />
<br />
The standard code to print a document is the following (see also <a href="http://accountingplusplus.blogspot.com/2011/02/printing-text-with-wxwidgets.html">this post</a>):<br />
<br />
<span style="font-size: x-small;"><span><span style="font-family: "Courier New",Courier,monospace;"> wxPrintDialogData printDialogData(* m_PrintData);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> wxPrinter printer( &printDialogData );</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> bool success = printer.Print( NULL, &printout, true );</span></span></span><br />
<br />
The code above shows a printer selection dialog, then it prints to the printer selected by the user.<br />
<br />
After printing you can get the name of the selected printer with this code:<br />
<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> <span style="font-size: x-small;">wxString pName = printer.GetPrintDialogData().GetPrintData().GetPrinterName();</span></span><span style="font-size: x-small;"><br style="font-family: "Courier New",Courier,monospace;" /></span></span><br />
<br />
Then you can use the printer name to print again directly to that printer:<br />
<br />
<br />
<span style="font-size: x-small;"><span><span style="font-family: "Courier New",Courier,monospace;"> m_PrintData->SetPrinterName( pName );<br />
wxPrintDialogData printDialogData(* m_PrintData);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> wxPrinter printer( &printDialogData );</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> bool success = printer.Print( NULL, &printout, false );</span></span></span><br />
<div style="font-family: inherit;"><br />
</div><div style="font-family: inherit;"><span style="font-size: small;"><span><span style="font-family: inherit;"> </span>The code above sets the printer name and prints without showing a printer selection dialog.</span></span></div><div style="font-family: inherit;"><span style="font-size: small;"><span>As you can see the solution a a simple one, but I had problems finding it because nobody seems to have this problem and I had to look hard in the documentation.</span></span></div><span style="font-size: xx-small;"><br style="font-family: "Courier New",Courier,monospace;" /></span>Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-64385434304055451592011-09-09T06:39:00.000-07:002011-09-09T06:39:57.798-07:00Printing images with wxWidgetsPrinting an image, mixed with text, is a common requirement. Just think of printing a customer's logo on an invoice.<br />
<br />
Printing an image at a given size in wxWidgets has been a rather difficult task, mainly for the lack of documentation or examples. After some trial and error I found a working solution and I will describe it here. This solution is based on what I described for <a href="http://accountingplusplus.blogspot.com/2011/02/printing-text-with-wxwidgets.html">printing text</a>. You will need to read that post too.<br />
<br />
1 - Load the image in a wxImage object, resize it, convert it to a wxBitmap. You need to resize the image so that you can print it pixel by pixel and obtain a printed image of the desired size. To define the image size you need to know the desired printed size or the image DPI. You can know the printer's resolution using <span style="font-family: "Courier New",Courier,monospace;">wxDC::GetPPI()</span>.<br />
Starting from the original image size and the printer's resolution you can compute the new image size and rescale it.<br />
If you know the image's DPI you can compute the new image size in pixels using this formula: <br />
<div style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">newWidth_pixels = printerPPI / imageDPI * originalWidth_pixels</span></div><div style="font-family: "Courier New",Courier,monospace;"><br />
</div>If you know the printed image's width (in mm) that you want to obtain you can compute the new image size in pixels using this formula:<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">newWidth_pixels = printedWidth_mm * PPI / 25.4</span></span><br />
<br />
2 - Print the bitmap, making sure that wxWidgets does not resize it. This is the difficult part, since I ended up with a much larger image than what I wanted. The problem is that I set up a user scale with <span style="font-family: "Courier New",Courier,monospace;">wxDC::SetUserScale()</span> and that scale was also applied to the image, enlarging it. The solution is, obviously, to set the user scale to 1 before drawing the bitmap. Here is a function that I use for the purpose (x and y are the print coordinates in mm):<br />
<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">void PrintBitmap( wxDC *dc, const float logUnitsFactor, wxBitmap &bitmap, const float x, const float y )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">{</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> double xScale, yScale;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> dc->GetUserScale( &xScale, &yScale );</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"></span><span style="font-family: "Courier New",Courier,monospace;"> dc->SetUserScale( 1, 1 );</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> dc->DrawBitmap( bitmap, x*logUnitsFactor*xScale, y*logUnitsFactor*yScale, true );</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> dc->SetUserScale( xScale, yScale );</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">}</span></span>Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-56399856593705463382011-07-21T10:13:00.000-07:002011-07-21T10:13:23.734-07:00Tab order in dialogsAccounting programs often show dialogs that contain many controls for data entry.<br />
The mouse is an easy way to move from one control to the other while entering data, but this is a very slow solution. Users that need to input a lot of data quickly learn to use the TAB key to move from one control to the other.<br />
<br />
This is a much faster solution, but pressing TAB moves among controls in a fixed sequence (usually called <b>tab order</b>), so this sequence must be the best possible one from a user's viewpoint. To optimize space in the dialog sometimes the tab order must be different from the usual left-to-right, top-to-bottom layout.<br />
<br />
Visual Basic and other RAD tools controls have a specific property to set the tab order of each control, but wxWidgets does not have anything similar. In wxWidgets the default tab order is the order of creation of the controls: this is usually leads to the left-right, top-bottom order.<br />
<br />
The functions <span style="font-family: "Courier New",Courier,monospace;">wxWindow::MoveAfterInTabOrder ()</span> and <span style="font-family: "Courier New",Courier,monospace;">wxWindow::MoveBeforeInTabOrder()</span>can be used to change the tab order of a control. They work but they have some drawbacks.<br />
<ul><li>You must write this code by hand. DialogBlocks, for example, does not do it.</li>
<li>If you want to jump to a control and then move to the ten controls following it, you need to write this code for ALL the controls. Suppose that you have the following controls: A, B, C, D, E, F, G. Moving D after A will cause the following tab order: A, D, B, C, E, F, G. SO, to obtain something like A, D, E, F, G, B, C you will need to call the function a lot of times.</li>
</ul>Using wxGridBagSizer can be a simpler solution to set tab order. Using this sizer you can obtain the same layout even if you create controls in a different order. Since the default tab order is the creation order you can change the creation order to get the desired tab order without changing the visual layout of the dialog.<br />
Using DialogBloks this can be obtained moving controls up or down in the left pane.Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-86805528652594348532011-03-19T01:40:00.000-07:002011-07-21T09:37:38.494-07:00Complex dialogs with wxWidgetsMost RAD tools, like the venerable Visual Basic, let users put controls in a dialog using a fixed layout: just drag each control where you want to see it. This is a very simple approach, but it has some shortcomings, especially for applications that will run on different operating systems.<br />
The problem is that the layout is fixed and it does not adapt to the size of the contained text. Widows uses a default font that is smaller than the one used in Linux or OS X, and even in Windows users can set larger fonts.<br />
This means that if a dialog has been designed in Windows with default fonts then the text will no be completely visible if the used font is larger.<br />
The same happens with translations: if the translated text is longer it will not be completely visible.<br />
<br />
To solve these problems wxWidgets (like other frameworks) uses sizers for dialogs layout. Sizers change dynamically the size of controls so that their content will always be visible. This is very useful but it look rather complicated at first: you need some time to become used to sizers if you are used to fixed layouts.<br />
<br />
Accounting programs often need to show many controls in a dialog, so it is important to use the available space in the best possible way. This sometimes means arranging controls in an irregular way to avoid wasting space.<br />
Sizers can cause troubles in this situation: one of the more powerful sizer ix wxFlexGridSizer: it arranges controls in a regular grid, with rows and columns of different sizes to accommodate controls. This works reasonably well, but each column (for example) has the width of the widest control so if it contains a very wide and a very narrow control a lot of space will be wasted, and the dialog will not look very well.<br />
<br />
A few days ago I had a similar situation, so I tried <b><span style="font-family: "Courier New",Courier,monospace;">wxGridBagSizer</span></b>: there is not much information about it but it looked promising. This is very complicated to use at the source code level, but I discovered that DialogBlocks has great support for this sizer. wxGridBagSizer arranges controls in a layout that is similar to HTML tables: controls lie in a grid, but a grid cell can span over more than one row or column. This solves most of the problems and the resulting dialog looks much better and uses the available space very well.<br />
<br />
Here is an example of a dialog using wxGridBagSizer in DialogBlocks: you can see how some controls span over more than one row or column:<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhp5HW4n-cwfxmK9z48CQYcEMAS6BSzvubkJCgX8dfJ7InjUPeNPQtyCqETpZDfq00hF8xo5KjdIEGItTb_M24y0v4Z7_VhfY66FVirMKROvIUCU4s6Xow0aXDg5muAfKJSvF1y2wE6P24/s1600/wxGridBagSizer.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="252" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhp5HW4n-cwfxmK9z48CQYcEMAS6BSzvubkJCgX8dfJ7InjUPeNPQtyCqETpZDfq00hF8xo5KjdIEGItTb_M24y0v4Z7_VhfY66FVirMKROvIUCU4s6Xow0aXDg5muAfKJSvF1y2wE6P24/s320/wxGridBagSizer.png" width="320" /></a></div>Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-39133901503154950362011-03-08T23:50:00.000-08:002011-03-19T01:12:15.711-07:00Printing text with wxWidgets II<div style="font-family: inherit;"><b>Printing to the default printer</b></div><br />
A common requirement is printing directly to the default printer, without showing any printer selection dialog.<br />
Referring to the code of the previous post this can be accomplished by changing one line of code from<br />
<br />
<span style="font-size: xx-small;"><span style="font-family: "Courier New",Courier,monospace;">bool success = printer.Print( NULL, &printout, true );</span></span><br />
<br />
<span style="font-size: xx-small;"><span style="font-family: "Courier New",Courier,monospace;"><span style="font-size: small;"><span style="font-family: inherit;">to</span></span> </span></span><br />
<br />
<span style="font-size: xx-small;"><span style="font-family: "Courier New",Courier,monospace;">bool success = printer.Print( NULL, &printout, false );</span></span><span style="font-size: xx-small;"><span style="font-family: "Courier New",Courier,monospace;"> </span></span><br />
<br />
According to the documentation this should work, but the code (at the moment of writing this post) does not print anything.<br />
Some debugging shows that there is a bug in the wxWidgets code: the bug will be fixed, but at the moment the problem can be solved adding one line of code.<br />
So working code looks like the this:<br />
<br />
<span style="font-size: xx-small;"><span style="font-family: "Courier New",Courier,monospace;"> wxPrintDialogData printDialogData(* m_PrintData);</span></span><br />
<span style="font-size: xx-small;"><span style="font-family: "Courier New",Courier,monospace;">printDialogData.SetToPage( printDialogData.GetMaxPage() ); </span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">wxPrinter printer( &printDialogData );</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">bool success = printer.Print( NULL, &printout, true );</span></span><br />
<br />
Setting the <b style="font-family: "Courier New",Courier,monospace;">To</b> page to the <b style="font-family: "Courier New",Courier,monospace;">Max</b> page fixes the problem.<br />
<br />
<b>Centering text (and similar)</b><br />
<br />
Another common requirement is comparing the width of some text to the width of the page, for example to center the text, but it is not very clear how to obtain the desired sizes in the same units, so that they can be compared in a correct way.<br />
<br />
The previous post shows how to compute a variable (<span style="font-family: "Courier New",Courier,monospace;">logUnitsFactor</span>) to convert from millimeters to logical units.<br />
The page size in millimeters can be obtained by calling <span style="font-family: "Courier New",Courier,monospace;">wxPrintout::GetPageSizeMM()</span>, then the size in millimeters can be converted to logical units multiplying it by <span style="font-family: "Courier New",Courier,monospace;">logUnitsFactor</span>.<br />
<br />
The width of a text can be obtained calling <span style="font-family: "Courier New",Courier,monospace;">wxDC::GetTextExtent()</span>: the result is in logical units that can be compared to the previously computed page width.<br />
For example (code inside a wxPrintout-derived object):<br />
<br />
<div style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"> // compute the available width in logical units<br />
int pageWidthMM, pageHeightMM;<br />
GetPageSizeMM(&pageWidthMM, &pageHeightMM);<br />
int availableWidth = (pageWidthMM - m_MarginLeft - m_MarginRight)*logUnitsFactor;<br />
wxCoord ew, eh;<br />
dc->GetTextExtent( "Some text", &ew, &eh, NULL, NULL );</span></div><div style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"> // now we can compare "ew" and "availableWidth"</span></div>Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com1tag:blogger.com,1999:blog-7098835175438692935.post-55355996651475812742011-02-26T00:21:00.000-08:002011-02-26T01:04:12.858-08:00Printing text with wxWidgetsA common requirement for an accounting program is printing some text, often for reports.<br />
<br />
One possible solution is creating an HTML file and printing it using the <span style="font-family: "Courier New",Courier,monospace;">wxHtmlEasyPrinting</span>. I used it and it is a very simple and quick solution, but it does not give you precise control on the text layout.<br />
<br />
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.<br />
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.<br />
In the end I found a satisfactory solution and I will describe it here.<br />
<br />
To use the printing framework you need to derive your own class from <span style="font-family: "Courier New",Courier,monospace;">wxPrintout</span>, and at least to implement the <span style="font-family: "Courier New",Courier,monospace;">OnPrintPage()</span> and <span style="font-family: "Courier New",Courier,monospace;">HasPage()</span> functions.<br />
<br />
The <span style="font-family: "Courier New",Courier,monospace;">OnPrintPage()</span> function is called by the printing framework for each page that will be printed. It will contain the actual print code.<br />
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.<br />
<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> wxDC *dc = GetDC();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if( dc == NULL || !dc->IsOk() ) {</span></span><br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> // report error and...</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"></span><span style="font-family: "Courier New",Courier,monospace;"> return false;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // Get the logical pixels per inch of screen and printer</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int ppiScreenX, ppiScreenY;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> GetPPIScreen(&ppiScreenX, &ppiScreenY);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int ppiPrinterX, ppiPrinterY;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> GetPPIPrinter(&ppiPrinterX, &ppiPrinterY);</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // This scales the DC so that the printout roughly represents the the screen</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // scaling. The text point size _should_ be the right size but in fact is</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // too small for some reason. This is a detail that will need to be</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // addressed at some point but can be fudged for the moment.</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> float scale = (float)((float)ppiPrinterX/(float)ppiScreenX);</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // Now we have to check in case our real page size is reduced (e.g. because</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // we're drawing to a print preview memory DC)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int pageWidth, pageHeight;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> int w, h;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> dc->GetSize(&w, &h);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> GetPageSizePixels(&pageWidth, &pageHeight);</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // If printer pageWidth == current DC width, then this doesn't change. But w</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // might be the preview bitmap width, so scale down.</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> float overallScale = scale * (float)(w/(float)pageWidth);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> dc->SetUserScale(overallScale, overallScale);</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // Calculate conversion factor for converting millimetres into logical</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // units. There are approx. 25.4 mm to the inch. There are ppi device units</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // to the inch. Therefore 1 mm corresponds to ppi/25.4 device units. We also</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // divide by the screen-to-printer scaling factor, because we need to</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // unscale to pass logical units to DrawLine.</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> float logUnitsFactor = (float)(ppiPrinterX/(scale*25.4));</span></span><br />
<br />
<br />
now we can, for example, convert 20 millimeters to device units by computing <span style="font-family: "Courier New",Courier,monospace;">20*logUnitsFactor</span>.<br />
Font size in points will be respected, so the code for printing some text lines will be the following.<br />
<div style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"><br />
</span></div><div style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"> dc->SetFont( m_ReportFont );<br />
dc->SetBackgroundMode( wxTRANSPARENT );<br />
dc->SetTextForeground( *wxBLACK );<br />
dc->SetTextBackground( *wxWHITE );<br />
<br />
// line height</span></div><div style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"> float charH = dc->GetCharHeight();<br />
<br />
// coordinates of the first line<br />
float x = m_MarginLeft*logUnitsFactor;<br />
float y = m_MarginTop*logUnitsFactor;<br />
<br />
// print lines</span></div><div style="font-family: "Courier New",Courier,monospace;"> <span style="font-size: x-small;"> dc->DrawText( "First line", x, y );<br />
y += charH;</span> </div><div style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"> dc->DrawText( "Second line", x, y );<br />
y += charH;</span> </div><div style="font-family: "Courier New",Courier,monospace;"><br />
</div>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.<br />
The documentation says that you must provide an implementation of the <span style="font-family: "Courier New",Courier,monospace;">GetPageInfo()</span> 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.<br />
You can start with that default, then you can use your implementation of the <span style="font-family: "Courier New",Courier,monospace;">HasPage()</span> function to stop printing when it will be time. Just return <span style="font-family: "Courier New",Courier,monospace;">false</span> when you have printed all the needed rows.<br />
<br />
Once you have created your custom printout class just use something like the following code to print your pages.<br />
<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> MyPrintout printout( "Print name" );</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> </span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> wxPrintDialogData printDialogData(* m_PrintData);</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> wxPrinter printer( &printDialogData );</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> bool success = printer.Print( NULL, &printout, true );</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if( !success ) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if (wxPrinter::GetLastError() == wxPRINTER_ERROR) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> CUtils::MsgErr( "Printing error" );</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> else {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // print has been canceled</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span></span><br />
That code will show a common dialog to select the desired printer, then it will print to that printer.Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-73412733001334362612011-01-24T23:59:00.000-08:002011-01-24T23:59:53.185-08:00wxGrid, TAB and the trapped focusAn accounting program is likely to be used for intensive data entry, so it is important to make typing easy for the end user.<br />
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.<br />
<br />
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.<br />
<br />
This problem can be solved in a rather simple way, handling the wxEVT_KEY_DOWN event. Here is an example:<br />
<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">void fsGridBase::OnKeyDown( wxKeyEvent& event )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">{</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> bool keyHandled = false;</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if( event.GetKeyCode() == WXK_TAB ) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if( event.ShiftDown() ) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // shift-TAB: if we are in the first column move to the previous control</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if( GetGridCursorCol() == 0 ) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Navigate( wxNavigationKeyEvent::IsBackward );</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> keyHandled = true;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> else {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> // TAB: if we are in the last column move to the next control</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if( GetGridCursorCol() == GetNumberCols() - 1 ) {</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> Navigate( wxNavigationKeyEvent::IsForward );</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> keyHandled = true;</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> }</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if( !keyHandled )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> event.Skip();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">}</span></span><br />
<br />
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.<br />
This code works with wxWidgets 2.9: I don't know if it works for 2.8 too.Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-63802002972619270232011-01-12T06:59:00.000-08:002011-01-12T06:59:24.262-08:00Validation and the disappearing caretA 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.<br />
<br />
All there situations have one thing in common: the code shows a new dialog from within the lost focus event handler of the control.<br />
<br />
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.<br />
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.<br />
<br />
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.<br />
<br />
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.Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-89794738904007140632011-01-06T01:34:00.000-08:002011-01-06T01:34:36.339-08:00CMake - Install - Linux and OS XAs 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.<br />
<br />
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.<br />
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.<br />
<br />
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:<br />
<br />
<div style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"> # look for the folder containing the firebird embedded files to ship with the program<br />
find_path( FB_EMBEDDED_PATH firebird.conf ${PROJECT_SOURCE_DIR}/firebird_runtime ${PROJECT_SOURCE_DIR}/../../firebird_embedded_2.0.4_runtime )<br />
message( STATUS "Embedded firebird files path: " ${FB_EMBEDDED_PATH} )</span></div><br />
At the end of CMakeLists.txt add code like this:<br />
<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">install( TARGETS vvv DESTINATION . )</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">if( APPLE )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> # copy the Firebird runtime</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> install( DIRECTORY ${FB_EMBEDDED_PATH}/ DESTINATION vvv.app/Contents/MacOS USE_SOURCE_PERMISSIONS )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> install( FILES ${FB_EMBEDDED_PATH}/firebird/firebird.msg DESTINATION vvv.app/Contents/MacOS/firebird/bin/firebird )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> # fix filename references in the runtime</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> set( FIXUP_COMMAND ${PROJECT_SOURCE_DIR}/MACOSX_fixup_bundle.sh " " ${CMAKE_INSTALL_PREFIX}/vvv.app )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> install( CODE "execute_process( COMMAND ${FIXUP_COMMAND} )" )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">endif( APPLE )</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">if( UNIX AND NOT APPLE )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> # copy the Firebird runtime</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> install( DIRECTORY ${FB_EMBEDDED_PATH}/ DESTINATION . USE_SOURCE_PERMISSIONS )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> # copy other files used only for the Linux version</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> install( FILES ${PROJECT_SOURCE_DIR}/linux_specific/readme.txt </span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ${PROJECT_SOURCE_DIR}/linux_specific/License.txt </span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> DESTINATION . )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> install( FILES ${PROJECT_SOURCE_DIR}/linux_specific/vvv-start.sh </span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> DESTINATION . PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> GROUP_READ WORLD_READ WORLD_EXECUTE )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">endif( UNIX AND NOT APPLE )</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">if( UNIX )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> # set the installation path of the executable file and the resources (in OS X they are inside the bundle )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> if( APPLE )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> set( EXECUTABLE_INTALL_PATH vvv.app/Contents/MacOS )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> set( RESOURCES_INTALL_PATH vvv.app/Contents/Resources )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> else( APPLE )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> set( EXECUTABLE_INTALL_PATH . )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> set( RESOURCES_INTALL_PATH . )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> endif( APPLE )</span></span><br />
<span style="font-size: x-small;"></span><br />
<div style="font-family: "Courier New",Courier,monospace;"><br />
</div><span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;"> # copy other files to the installation path</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> install( FILES ${PROJECT_SOURCE_DIR}/vvv-struct-update.fdb </span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ${PROJECT_SOURCE_DIR}/VVV.fbk</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ${PROJECT_SOURCE_DIR}/help/en/vvv.htb</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> DESTINATION ${EXECUTABLE_INTALL_PATH} )</span></span><br />
<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">endif( UNIX ) </span><br style="font-family: "Courier New",Courier,monospace;" /></span><br />
Under OS X CMake will execute a script named <span style="font-family: "Courier New",Courier,monospace;">MACOSX_fixup_bundle.sh</span> to patch the dylibs as described <a href="http://accountingplusplus.blogspot.com/2010/06/firebird-embedded-os-x.html">here</a>. 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.<br />
Here is the script used by CMake:<br />
<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">#!/bin/bash</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"># this script will fix the components of the Firebird runtime to make it run from any location</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"># the script receives the path of the bundle to fix</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">BUNDLEPATH=$*</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"># the following line contains the name of the executable file that will be patched</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"># it is the only line that should be changed when copying this file to another project</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">EXECFILE=${BUNDLEPATH}/Contents/MacOS/vvv</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">LIBPATH=${BUNDLEPATH}/Contents/MacOS/firebird</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">LIBBINPATH=${BUNDLEPATH}/Contents/MacOS/firebird/bin</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"># path of library files relative to the main executable</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">NEWLIBPATH="@executable_path/firebird"</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"># path of library files relative to other library files</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">NEWLIBPATH_FOR_LIBS="@loader_path"</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"># path of library files relative to executables in the "firebird/bin" folder</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">NEWLIBPATH_FROM_BIN="@loader_path/.."</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">OLDLIBPATH="/Library/Frameworks/Firebird.framework/Versions/A/Libraries"</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">OLDLIBFBEMBEDFILENAME="/Library/Frameworks/Firebird.framework/Versions/A/Firebird"</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"># change the references in the files contained in the "firebird" folder</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">for TARGET in libfbembed.dylib libicudata.dylib libicui18n.dylib libicuuc.dylib ; do</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> LIBFILE=${LIBPATH}/${TARGET}</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> OLDTARGETID=${OLDLIBPATH}/${TARGET}</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> NEWTARGETID=${NEWLIBPATH}/${TARGET}</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> NEWTARGETID_FOR_LIBS=${NEWLIBPATH_FOR_LIBS}/${TARGET}</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> install_name_tool -id ${NEWTARGETID_FOR_LIBS} ${LIBFILE}</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> install_name_tool -change ${OLDTARGETID} ${NEWTARGETID} ${EXECFILE}</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> for POSSIBLECALLERNAME in libfbembed.dylib libicudata.dylib libicui18n.dylib libicuuc.dylib ; do</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> POSSIBLECALLERFILE=${LIBPATH}/${POSSIBLECALLERNAME}</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> install_name_tool -change ${OLDTARGETID} ${NEWTARGETID_FOR_LIBS} ${POSSIBLECALLERFILE}</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> done</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">done</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"># change the references in the files contained in the "firebird/bin" folder</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">for TARGET in gbak isql ; do</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> FILE=${LIBBINPATH}/${TARGET}</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> for POSSIBLECALLEDNAME in libfbembed.dylib libicudata.dylib libicui18n.dylib libicuuc.dylib ; do</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> OLDTARGETID=${OLDLIBPATH}/${POSSIBLECALLEDNAME}</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> NEWTARGETID=${NEWLIBPATH_FROM_BIN}/${POSSIBLECALLEDNAME}</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> install_name_tool -change ${OLDTARGETID} ${NEWTARGETID} ${FILE}</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> done</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> # change the reference to libfbembed into the program, that contains a reference to a different name (the framework name)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> NEWTARGETID=${NEWLIBPATH_FROM_BIN}/libfbembed.dylib</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> install_name_tool -change ${OLDLIBFBEMBEDFILENAME} ${NEWTARGETID} ${FILE}</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">done</span><br style="font-family: "Courier New",Courier,monospace;" /><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"># change the reference to libfbembed into the caller program, that contains a reference to a different name (the framework name)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">NEWTARGETID=${NEWLIBPATH}/libfbembed.dylib</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">install_name_tool -change ${OLDLIBFBEMBEDFILENAME} ${NEWTARGETID} ${EXECFILE}</span></span><br />
<br />
<br />
You will need to edit the EXECFILE definition (near the file top) to change the name from "vvv" to your program's name.Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-44608838117947303892010-12-20T23:16:00.000-08:002010-12-20T23:16:05.314-08:00CMake - Build an OS X BundleOS X applications are structured as <a href="http://developer.apple.com/library/mac/#documentation/CoreFoundation/Conceptual/CFBundles/Introduction/Introduction.html#//apple_ref/doc/uid/10000123i-CH1-SW1">bundles</a>. A bundle is a folder with a predefined structure that contains the program and other files.<br />
It is possible to create an application bundle with CMake using specific commands.<br />
<br />
First we must specify the files containing the application icons. For example:<br />
<br />
<span style="font-size: x-small;"><span style="font-family: "Courier New",Courier,monospace;">#--------------------------------------------------------------------------------</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"># For Apple set the icns file containing icons</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">if(APPLE)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> # icon files to copy in the bundle</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> set( OSX_ICON_FILES ${CMAKE_CURRENT_SOURCE_DIR}/graphics/vvv.icns ${CMAKE_CURRENT_SOURCE_DIR}/graphics/vvv-document.icns ) </span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> # set where in the bundle to put the icns files</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> set_source_files_properties( ${OSX_ICON_FILES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> # include the icns files in the target</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> set( SRCS ${SRCS} ${OSX_ICON_FILES} )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">ENDIF(APPLE)</span></span><br />
<div style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;"><br />
</span></div><div style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">add_executable( vvv WIN32 MACOSX_BUNDLE ${SRCS} )</span></div><br />
This example defines two icons, one for the application and one for the application documents. The <span style="font-family: "Courier New",Courier,monospace;">SRCS</span> variable already contains the list of source files and this code adds the icons to the files list.<br />
The <span style="font-family: "Courier New",Courier,monospace;">ADD_EXECUTABLE</span> command in the last line tells CMake to create an OS X bundle.<br />
<br />
The following code will tell CMake where to find the Info.plist that will be copied into the bundle:<br />
<br />
<div style="font-family: "Courier New",Courier,monospace;"><span style="font-size: x-small;">if(APPLE)<br />
# configure CMake to use a custom Info.plist<br />
set_target_properties( vvv PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${PROJECT_SOURCE_DIR}/vvv-Info.plist )<br />
ENDIF(APPLE)</span></div><br />
Using the code above CMake will be able to create an OS X application bundle.Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-88553199199404265152010-12-05T13:31:00.000-08:002010-12-05T13:31:58.399-08:00CMake - Build with Boost and FirebirdIn the previous post I explained how to use wxWidgets with CMake.<br />
Now we will look at the steps to successfully build a program that uses Firebird and the Boost library.<br />
<br />
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:<br />
<br />
<div style="font-family: "Courier New",Courier,monospace;"># try find BOOST (we need headers only)<br />
find_package( Boost 1.37.0 )<br />
if( Boost_FOUND)<br />
include_directories( ${Boost_INCLUDE_DIRS} )<br />
endif( Boost_FOUND)</div><br />
more information in <b style="font-family: "Courier New",Courier,monospace;">C:\Program Files\CMake 2.8\share\cmake-2.8\Modules\FIndBoost.cmake</b>. I am sure that you will figure out the correct path for your computer.<br />
<br />
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:<br />
<br />
<div style="font-family: "Courier New",Courier,monospace;"># definitions for IBPP<br />
if( WIN32 )<br />
add_definitions( -DIBPP_WINDOWS )<br />
elseif( APPLE )<br />
add_definitions( -DIBPP_DARWIN )<br />
else( WIN32 )<br />
add_definitions( -DIBPP_LINUX )<br />
endif( WIN32 )</div><br />
Under Windows the Firebird client library is loaded dynamically so there are no othe rrequirements.<br />
Under Linux and OS X the program must link with the client library. Here is the CMake code:<br />
<br />
<div style="font-family: "Courier New",Courier,monospace;">if( UNIX )<br />
# link to the Firebird client library<br />
# the last path is used for OS X where I have a local copy (not installed)<br />
find_library( FB_LIB fbembed PATHS /usr/lib /usr/local/lib /opt/firebird/lib ${PROJECT_SOURCE_DIR}/firebird_runtime/firebird )<br />
if( NOT FB_LIB )<br />
message( FATAL_ERROR "Unable to find firebird interface" )<br />
else( NOT FB_LIB )<br />
message( STATUS "Firebird interface: " ${FB_LIB} )<br />
endif( NOT FB_LIB )<br />
<br />
target_link_libraries( MyTarget ${FB_LIB} )<br />
endif( UNIX )</div><br />
the <b style="font-family: "Courier New",Courier,monospace;">find_libary</b> 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.<br />
<br />
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.Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-59310539674087538932010-12-03T08:11:00.000-08:002010-12-03T08:11:52.624-08:00CMake - wxWidgetsCMake 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.<br />
<br />
For example, let's see how to look for the wxWidgets library. We can use the <b>FIND_PACKAGE</b> command for this purpose: CMake already knows how to handle wxWidgets.<br />
<br />
Just add the following lines to CMakeLists.txt:<br />
<br />
<div style="font-family: "Courier New",Courier,monospace;">FIND_PACKAGE(wxWidgets REQUIRED html adv core base net aui xrc qa richtext )<br />
INCLUDE(${wxWidgets_USE_FILE})</div><span style="font-family: "Courier New",Courier,monospace;">TARGET_LINK_LIBRARIES(myTarget ${wxWidgets_LIBRARIES})</span><br />
<br />
<br />
The first line tells CMake to look for wxWidgets. The REQUIRED clause says that you require the specified modules.<br />
The FIND_PACKAGE command executes some code and sets a number of variables if it finds the library.<br />
The second line uses one of those variables to tell CMake where to look for include files.<br />
The third line tells CMake how to link a target with wxWidgets.<br />
<br />
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.<br />
<br />
<b>Linux</b><br />
<br />
I create two build folders, one for the debug configuration and one for the release one.<br />
<br />
You need to look at the following variables:<br />
<ul><li><b>CMAKE_BUILD_TYPE</b>: set it to <b>Debug</b> for a build folder and to <b>Release</b> for the other.</li>
<li><b>wxWidgets_CONFIG_EXECUTABLE</b>: it is the path to the wx-config script for the chosen copy of wxWidgets. For example, <b>/usr/local/bin/wx-config</b> for a library that has been installed, or <b>/home/fulvio/wxSVN/buildgtk/wx-config</b> 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).</li>
<li><b>wxWidgets_USE_DEBUG</b>: check it if you want to use a debug version of the library.</li>
<li><b>wxWIDGETS_USE_STATIC</b>: check it if you ant to use a static version of the library.</li>
<li><b>wxWidgets_USE_UNICODE</b>: 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.</li>
<li><b>wxWidgets_wxrc_EXECUTABLE</b>: this is probably a wxrc setting. Since I do not use it I left its value set to NOTFOUND.</li>
</ul><b>OS X</b> <br />
<br />
I create two build folders, one for the debug configuration and one for the release one.<br />
<br />
You need to look at the following variables:<br />
<ul><li><b>CMAKE_BUILD_TYPE</b>: set it to <b>Debug</b> for a build folder and to <b>Release</b> for the other.</li>
<li><b>CMAKE_OSX_ARCHITECTURES</b>: I set it to <b>i386</b>.</li>
<li><b>CMAKE_OSX_DEPLOYMENT_TARGET</b>: I leave this blank.</li>
<li><b>wxWidgets_CONFIG_EXECUTABLE</b>: it is the path to the wx-config script for the chosen copy of wxWidgets. For example <b>/Users/fulvio/wxMac-2.8.10/buildgtk/wx-config</b> 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).</li>
<li><b>wxWidgets_wxrc_EXECUTABLE</b>: this is probably a wxrc setting. Since I do not use it I left its value set to NOTFOUND. </li>
</ul><b>Windows</b><br />
<br />
In Windows I create a single build folder. It will contain a Visual Studio project file with both a debug and a release configuration.<br />
<br />
You need to look at the following variables:<br />
<ul><li><b>wxWidgets_CONFIGURATION</b>: set to <b>mswud</b>, <b>mswu</b> or <b>mswd</b>. This is not very clear to me, but I set it to <b>mswu</b> and everything works well.</li>
<li><b>wxWidgets_ROOT_DIR</b>: it is the path to the root folder of the wxWidgets installation, for example <b>E:/wxWidgets-2.8.10</b>. If CMake does not find the path you need to set it manually and configure again.</li>
<li><b>wxWidgets_LIB_DIR</b>: 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. </li>
<li><b>wxWidgets_USE_REL_AND_DBG</b> 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.</li>
<li><b>wxWidgets_wxrc_EXECUTABLE</b>: this is probably a wxrc setting. Since I do not use it I left its value set to NOTFOUND.</li>
</ul>As you can see configuring CMake to use your copy of wxWidgets is an easy task.<br />
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.Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-75575721442888292602010-12-02T07:37:00.000-08:002010-12-02T07:37:59.169-08:00CMakeMy purpose is creating a program that can be compiled for Linux, OS X and Windows. If you have read older posts you already know that I was successful and I am already able to do it.<br />
<br />
After some time spent developing a program for all these operating system it becomes obvious that handling the build files is a great problem. I have the Visual Studio project file for Windows, the KDevelop files for Linux and the XCode project for OS X. Maintaining all these files takes time and it is prone to errors. Moreover there are other kind of problems: I am still using Visual Studio 2003: if another developer checks out the source code and opens the project with a newer version of Visual Studio the project cannot be read by my older version any more. I could manually maintains different project files for different VS versions, but this is clearly a crazy thing to do.<br />
Last but not least, creating the files for a new project is difficult: you have to copy the files from another project and carefully edit them.<br />
<br />
In short, there must be a better way.<br />
<br />
Some time ago I started looking for that way and I found a good solution: <a href="http://www.cmake.org/">CMake</a>. Maybe there are others, but I am very satisfied with this one.<br />
<br />
The idea behind CMake is simple: use a single text file (CMakeLists.txt) in each folder to describe how the project must be built. Then let CMake create the build files for your preferred build tool.<br />
<br />
You usually create a folder inside your project and instruct CMake to create the build files in that folder: all the build and compiled files will be in that folder, so your source code tree will be clean. You can even create different build folders for different build configurations.<br />
<br />
There are plenty on information about CMake basic usage, so I will not write about it. I found little information about advanced usage, and I will post what I discovered with some work and experiments.<br />
<br />
Using CMake is simple: just run the GUI program and it will show up its main window.<br />
In the upper part you select your project's root folder (the upper one that contains a CMakeLists.txt file) and the build folder, where the build files will be created.<br />
<br />
Below these paths there is grid that contains a list of couples: a variable and its value. You must check the values and set the missing or wrong ones. Here lies the magic: setting those variables tells CMake where to look for libraries or how to configure the build in your computer.<br />
<br />
You must run the CMake GUI when you create the build folder and you will run it again when you change something, most often because you have added new source files.<br />
<br />
In the lower part of the window there is a <b>Configure</b> button. When you press it CMake processes all the CMakeLists.txt files: if there are problems one or more rows in the grid will be colored in red. You need to fix the problem, for example editing the value of a variable, and press <b>Configure</b> again.<br />
If everything is OK the <b>Generate</b> button is enabled: press it to create your build files, then move to the build folder and open the build files with your development tool.<br />
<br />
Handling the project becomes a much easier task. For example, if you create some new source files just add the file names to the CMakeLists.txt file.<br />
Then just run CMake in each operating system and configure again.<br />
<br />
To create the build files CMake processes the CMakeLists.txt files found in each folder. Those files are written in the CMake language: the language is very powerful so the difficult part is learning it. I discovered that I used a relatively low number of features, but it took me a lot of work to learn how to use them correctly.Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-27533036026374189702010-11-25T06:43:00.000-08:002010-11-25T06:43:27.773-08:00Compiling Firebird in Linux<div style="font-family: inherit;">Firebird is available as a precompiled binary for Linux, but it is compiled using very old software, probably to make sure that it can run in any distribution.</div><div style="font-family: inherit;">The software is compiled using libstdc++.so.5 while current compilers use libstdc++.so.6.</div><div style="font-family: inherit;"><br />
</div><div style="font-family: inherit;">This is OK for normal use but it causes problems when my program links to the Firebird library. I just compiled a 64 bit version of a program and I had the same problems again. As usual the solution was building Firebird from source. This was not difficult but it took some time to figure out how to proceed, so this time I took notes for the future.</div><div style="font-family: inherit;"><br />
</div><div style="font-family: inherit;">Here are the steps to build Firebird 2.0.x. I did not test other versions, but things should not be very different.</div><div style="font-family: inherit;"><br />
</div><div style="font-family: inherit;">The following packages are required to build the software:</div><span style="font-family: courier new;"><br />
</span><br />
<div style="font-family: "Courier New",Courier,monospace;">bison<br />
automake1.4<br />
libtool<br />
libncurses5-dev</div><span style="font-family: courier new;"><br />
</span><br />
<div style="font-family: inherit;">After installing the packages open a terminal window, move to the root folder of the downloaded code and enter the following commands:</div><span style="font-family: courier new;"><br />
</span><br />
<div style="font-family: "Courier New",Courier,monospace;">./autogen.sh</div><div style="font-family: "Courier New",Courier,monospace;">./configure --prefix=/media/disco2/firebird-2.0.6-installed<br />
make<br />
make install</div><span style="font-family: courier new;"><br />
</span><br />
<div style="font-family: inherit;">The "configure" line is needed only if you want to change the default install path, like in my example.</div><div style="font-family: inherit;">"make install" is needed to strip the files that otherwise would be very large.</div><span style="font-family: courier new;"><br />
</span>Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-54971461718551177002010-11-24T13:42:00.000-08:002010-11-24T13:42:33.643-08:00Firebird embedded and KDevelopIn Linux I am using KDevelop as my development tool. Now that I <a href="http://accountingplusplus.blogspot.com/2010/11/firebird-embedded-linux-update.html">set environment variables in code</a> I had problems running the program from KDevelop. The release version can be copied to a folder with all the needed Firebird files before running it, so this is not a problem.<br />
The problem is with the debug version, since I sometimes need to run it in the debugger to see what is going on. Now the program sets the environment variables so it cannot find Firebird and it does not work.<br />
After some trial and error I decided that the simplest solution was to copy the <a href="http://accountingplusplus.blogspot.com/2010/06/firebird-embedded-linux.html">Firebird embedded files</a> to the folder where the executable is created, so that the program can find the needed files. Then, in KDevelop, I opened the <b>Project/Project Options</b> menu, I chose <b>Run Options</b> and selected <b>Run from the directory where the executable is</b>.<br />
<br />
Now I can run the program in the debugger.Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-5969772830508893842010-11-22T13:52:00.000-08:002010-12-08T01:43:05.970-08:00Firebird embedded - Linux (update)This post contained information about creating a Linux program that uses Firebird embedded as <a href="http://accountingplusplus.blogspot.com/2010/06/firebird-embedded-linux.html">described here</a>. <br />
<br />
It was a way to avoid using a shell script to set the needed environment variables before starting the program. Before posting I tested the solution in the same computer and it worked.<br />
Unfortunately I discovered in a second time that the program does not work on other computers, so I have removed the previous content. I still need a shell script to correctly start the program.<br />
<br />
I keep this post because it is linked from other web pages. <br />
<br />
Sorry for posting incorrect information.Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0tag:blogger.com,1999:blog-7098835175438692935.post-37172112329593750412010-06-16T09:44:00.000-07:002010-06-16T09:44:43.907-07:00A grid to select rowsI want to use a grid to select, for example, a customer from the customers table.<br />
<br />
I need a grid with the following features:<br />
<ul><li>read-only</li>
<li>when I click the grid I want to select the whole row.</li>
</ul>Solving this problem was harder than expected. First I tried the new <b><span style="font-family: "Courier New",Courier,monospace;">wxDataViewListCtrl</span></b> 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.<br />
<br />
Then I tried <b><span style="font-family: "Courier New",Courier,monospace;">wxDataListCtrl</span></b> 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.<br />
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.<br />
The second is that a bitmap shown in a column is always left-aligned, while it would often be useful to have it centered.<br />
Both limitations derive from the native control used, so they are not wxWidgets' fault.<br />
<br />
So I was left only with the venerable <b style="font-family: "Courier New",Courier,monospace;">wxGrid</b>, 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.<br />
<br />
I derived a new class from wxGrid, with the following modifications:<br />
<br />
The constructor calls the following code to set the visual properties of the grid:<br />
<span style="font-family: "Courier New",Courier,monospace;">SetUseNativeColLabels();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">HideRowLabels();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">EnableEditing( false );</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">SetCellHighlightPenWidth( 0 ); // disable highlight</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">EnableGridLines( false );</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">DisableDragGridSize();</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">ShowScrollbars( wxSHOW_SB_DEFAULT, wxSHOW_SB_ALWAYS );</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">// select the first row</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;">if( GetNumberRows() > 0 )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> SelectRow( 0 );</span><br />
<br />
the <b><span style="font-family: "Courier New",Courier,monospace;">wxEVT_GRID_SELECT_CELL</span></b> handler calls<br />
<div style="font-family: "Courier New",Courier,monospace;">SelectRow( event.GetRow() );</div><br />
the <b><span style="font-family: "Courier New",Courier,monospace;">wxEVT_GRID_RANGE_SELECT</span></b> handler uses this code<br />
<span style="font-family: "Courier New",Courier,monospace;">if( event.Selecting() && (event.GetTopRow() != event.GetBottomRow()) ) {</span><br />
to see if it needs to call<br />
<span style="font-family: "Courier New",Courier,monospace;"> SelectRow( GetGridCursorRow() );</span><br />
to select the current row only.<br />
<br />
the <b><span style="font-family: "Courier New",Courier,monospace;">wxEVT_GRID_LABEL_LEFT_CLICK</span></b> handler calls<br />
<div style="font-family: "Courier New",Courier,monospace;">SelectRow( GetGridCursorRow() );</div><br />
<br />
the <b><span style="font-family: "Courier New",Courier,monospace;">wxEVT_GRID_ROW_SIZE</span></b> handler calls<br />
<div style="font-family: "Courier New",Courier,monospace;">SelectRow( GetGridCursorRow() );</div><br />
<br />
the <b><span style="font-family: "Courier New",Courier,monospace;">wxEVT_GRID_COL_SIZE</span></b> handler calls<br />
<div style="font-family: "Courier New",Courier,monospace;">SelectRow( GetGridCursorRow() );</div><br />
the <b><span style="font-family: "Courier New",Courier,monospace;">wxEVT_KEY_DOWN</span></b> handler ignores events that would make the cursor go outside the grid, making the selection disappear. For example:<br />
<span style="font-family: "Courier New",Courier,monospace;">if( event.GetKeyCode() == WXK_RIGHT && GetGridCursorCol() == GetNumberCols() - 1 )</span><br style="font-family: "Courier New",Courier,monospace;" /><span style="font-family: "Courier New",Courier,monospace;"> ignore = true;</span><br style="font-family: "Courier New",Courier,monospace;" /><br />
<br />
The resulting grid is very satisfactory for my needs.Fulvio Senorehttp://www.blogger.com/profile/08431327146202932326noreply@blogger.com0