Everyone is familiar with tree controls: every time you open Windows Explorer, you see file system hierarchy as a tree. Another kind of tree is used by some programs, such as installers, to allow you to select options to install. When all options of a particular subtree are selected, you will see checkbox with a check mark (); when no options in a subtree are selected, the checkbox next to the subtree root displays an unchecked checkbox (); and when only some of the options in a subtree are selected, you will see what is known as tri-state checkbox ().
Surprisingly, there is no direct support for tri-state checkboxes in MFC's CTreeCtrl. The closest you can get is to create a tri-state image list for your tree. While this will work, it does not give you the complete visual effect of XP themed checkboxes, such as hot-state appearance () when the mouse is hovering.
While I was researching tree controls and how to add tri-state checkboxes, I came to understand that the tree control - like the list control - can be enhanced with many useful features and nice UI effects by mechanism of custom draw. This led me to take next step: adding HTML support to tree items. It was easy to see how this would be possible, since I had just completed my XHtmlDraw article.
Before long I sketched out requirements of my new tree control:
|Checkboxes with theme support - I wanted notification messages sent to parent when checkbox was toggled, and full theme support; this means hover (hot) support, with checkboxes drawn exactly like themed XP checkboxes. Of course, on Vista, checkboxes should have Vista appearance:
|Smart Checkboxes - this is a term I started using (but did not invent) to describe how checking parent item will also checkmark the children of that item - and, conversely, unchecking a child item will display parent with tri-state checkbox, if any other children are still checkmarked. Here is quick demo of how it works:
Of course, Smart Checkboxes are optional - you can have checkboxes without using Smart Checkboxes.
|HTML - I wanted to include XHtmlDraw functionality, including support for web links and APP: links. |
|Enhanced tree navigation and status - reading Zafir Anjum's articles gave me ideas for improving the programmatic interface of the tree control, and I also wanted to implement better management features, like determining how many items are checked, how many children an item has, etc., and sending notification message to parent when item is expanded. |
|Loading and saving tree data using XML - this is a common requirement as XML is adopted in more applications. |
To begin with, let me show you the demo app:
This dialog allows you to choose source of data for tree. It can be text file or XML file (files are stored as resources, see my XResFile article for details). There are two choices for each file type - one is complete list, the second is partial list with ten items in each main node, to make debugging easy.
When you click on button, here is what you see:
Here are main features:
- The Options checkboxes allow to display tree with various options that are tree-wide in scope - i.e., they affect all items.
- The Show Checked button displays modeless dialog that shows list of currently checked items:
|Image has been reduced in size. |
To refresh list, just click button again.
- The Find button displays Find dialog:
This dialog uses built-in
CXHtmlTree::FindItem() function to search for item text. An option on dialog lets you checkmark all matching items, which you can observe using Show Checked dialog.
- The Colors button displays Colors dialog:
Note that background color will be set as background of the entire tree control (via
SetBkColor()). However, individual items can have their backgrounds set independently of this, either by using
SetItemTextBkColor(), or by using HTML:
- These are indicators of scrolling speed for drag & drop scrolling.
- These are three buttons that allow you to toggle checkmark of all items, expand all items, and collapse all items.
- The tree control shows one item checkmarked. Note that its parent (Collies) is shown with tri-state checkmark, indicating one of its children - but not all - is checked. Its parent (Herding Dogs) also has tri-state checkmark.
- The edit box contains item text, which you can modify, and then use Update button to write new text to tree item.
- The Tree Info displays information about entire tree.
- The Item Info displays information about an item - either the currently selected item, or item that was just expanded.
- Tree breadcrumb trail is displayed by using
GetItemPath() function and XBreadCrumbBar. When you click on active breadcrumb, that tree node will be selected.
- The Log displays information when notification messages (
WM_XHTMLTREE_ITEM_EXPANDED) are received from control. You can enable or disable the Log with checkbox.
To see full list of supported HTML tags, including how to use web links and APP: links, please refer to my XHtmlDraw article.
Colors and Text Attributes
The following functions support item and tree colors and text attributes:
|COLORREF GetBkColor()||tree control||Get current background color for tree|
|BOOL GetItemBold(HTREEITEM hItem)||item||Get item bold state (TRUE = item is bold)|
|COLORREF GetItemTextBkColor(HTREEITEM hItem)||item||Get item background color for text|
|COLORREF GetItemTextColor(HTREEITEM hItem)||item||Get item text color|
|LOGFONT * GetLogfont()||tree control||Get pointer to current LOGFONT|
|COLORREF GetTextColor()||tree control||Get current text color for tree|
|BOOL GetUseLogfont()||tree control||Get current state of LOGFONT (TRUE = use specified LOGFONT|
|COLORREF SetBkColor(COLORREF rgb)||tree control||Set tree background color|
|BOOL SetItemBold(HTREEITEM hItem, BOOL bBold)||item||Set bold state for item|
|COLORREF SetItemTextBkColor(HTREEITEM hItem, COLORREF rgb)||item||Set background color for item text|
|COLORREF SetItemTextColor(HTREEITEM hItem, COLORREF rgb)||item||Set text color for item|
|CXHtmlTree& SetLogfont(LOGFONT * pLogFont)||tree control||Set new LOGFONT for tree|
|COLORREF SetTextColor(COLORREF rgb)||tree control||Set text color for tree|
|CXHtmlTree& SetUseLogfont(BOOL bFlag)||tree control||Set flag to indicate whether LOGFONT struct should be used (TRUE = use LOGFONT)|
You can enable checkboxes for tree (using
SetHasCheckBoxes()) independently of enabling Smart Checkboxes (using
SetSmartCheckBox()). When checkboxes are enabled, an internal state image list is created for various checkbox states. This creation is performed in
CreateCheckboxImageList() (see CreateCheckboxImageList.cpp). For each possible state, a bitmap is created (using David Zhao's excellent CVisualStylesXP class), for both cold and hot states. Obviously this implies modal processing, but since I can use
CTreeCtrl::SetItemState(), keeping track of item state is not too complicated. Furthermore, I have designed image list so that common state transitions are simple. In following table, you can see that going from "cold" state to "hot" state involves OR'ing with 8, and going from "normal" state to "disabled" state involves OR'ing with 4.
|XHtmlTree Checkbox States|
Here are functions implemented to support XHtmlTree checkboxes:
|void CheckAll(BOOL bCheck)||Sets the checkbox for all items to the bCheck state|
|BOOL GetCheck(HTREEITEM hItem)||Returns TRUE if item is checked. Returns FALSE if item is not checked, or is in tri-state.|
|int GetCheckedCount()||Returns total count of checked items in tree|
|int GetChildrenCheckedCount(HTREEITEM hItem)||Returns checked count for children|
|HTREEITEM GetFirstCheckedItem()||Returns first checked item, or NULL|
|BOOL GetHasCheckBoxes()||Returns TRUE if tree has checkboxes|
|HTREEITEM GetNextCheckedItem(HTREEITEM hItem)||Returns next checked item, or NULL|
|HTREEITEM GetPrevCheckedItem(HTREEITEM hItem)||Returns previous checked item, or NULL|
|BOOL GetSelectFollowsCheck()||Returns TRUE if checking an item will also cause it to be selected|
|BOOL GetSmartCheckBox()||Return TRUE if Smart Checkboxes are enabled|
|BOOL IsChecked(HTREEITEM hItem)||Returns TRUE if item is checked. Returns FALSE if item is not checked, or is in tri-state.|
|CXHtmlTree& SetCheck(HTREEITEM hItem, BOOL fCheck = TRUE)||Sets item checked state to fCheck|
|CXHtmlTree& SetCheckChildren(HTREEITEM hItem, BOOL fCheck)||Sets checked state of children to fCheck|
|CXHtmlTree& SetHasCheckBoxes(BOOL bHasCheckBoxes)||Enable checkboxes for tree if bHasCheckBoxes is TRUE|
|CXHtmlTree& SetSelectFollowsCheck(BOOL bFlag)||If bFlag is TRUE, checking an item will also cause it to be selected|
|CXHtmlTree& SetSmartCheckBox(BOOL bFlag)||Enable Smart Checkboxes if bFlag is TRUE|
XHtmlTree supports standard Windows keyboard navigation techniques:
|Down and Up Arrow Keys||Moves selection to next/previous item; no expand|
|Right Arrow Key||Expands the current selected item (if it is collapsed), selects the first child item|
|Left Arrow Key||Collapses the current selected item (if it is expanded), selects the parent item|
|Page Down||Moves selection to last item in tree window; pages items as necessary|
|Page Up||Moves selection to first item in tree window; pages items as necessary|
|Ctrl+Page Down||Scrolls to last item in tree window; pages items as necessary; selection is not changed|
|Ctrl+Page Up||Scrolls to first item in tree window; pages items as necessary; selection is not changed|
|Backspace||Moves selection to parent item|
|End||Moves selection to last item; no expand|
|Home||Moves selection to first item; no expand|
|Ctrl+End||Scrolls to last; selection is not changed, no expand|
|Ctrl+Home||Scrolls to first; selection is not changed, no expand|
|Asterisk (*) on numeric keypad||Expands all items under the selected item|
|Plus Sign (+) on numeric keypad||Expands the selected item|
|Minus Sign (-) on numeric keypad||Collapses the selected item|
Tree Navigation and Management
Here are functions implemented to support XHtmlTree navigation:
|HTREEITEM GetLastItem(HTREEITEM hItem)||Retrieve the last item in the tree|
|HTREEITEM GetNextCheckedItem(HTREEITEM hItem)||Retrieve the next item that is checked|
|HTREEITEM GetNextItem(HTREEITEM hItem)||Retrieve the next item in the tree. This traverses the tree top-to-bottom, just like the tree appears visually when it is fully expanded.|
|HTREEITEM GetNextItem(HTREEITEM hItem, UINT nCode)||Retrieve the item that has the specified relationship, indicated by the nCode parameter, to hItem|
|HTREEITEM GetPrevCheckedItem(HTREEITEM hItem)||Retrieve the previous item that is checked|
|HTREEITEM GetPrevItem(HTREEITEM hItem)||Retrieve the previous item in the tree. This traverses the tree top-to-bottom, just like the tree appears visually when it is fully expanded.|
Initializing the Tree
It is easy to initialize XHtmlTree in one statement:
XHtmlTree supports standard tooltips via CToolTipCtrl, and also supports HTML tooltips, displayed with Eugene Pustovoyt's excellent CPPToolTip class. Here is example of how HTML tooltips can be used:
Here is HTML used for this tooltip:
<td><bmp idres=133 cx=110 cy=92 mask></td>
<td width=180>The Golden Retriever is a popular breed of dog, originally developed to retrieve game during hunting. It is one of the most common family dogs as it is naturally very friendly and amenable to training.</td>
This is all standard HTML, except for the <bmp idres=133 cx=110 cy=92 mask>, which specifies bitmap to be read from resource id 133.
OK, so where is HTML coming from, since it is obviously not the item text? XHtmlTree provides a new
SetItemNote() function, which attaches optional note text to an item. The note text (when it exists) is used instead of item text for tooltips. This is true for both standard tooltips and HTML tooltips. However, keep in mind that standard tooltips have hard limit of 80 characters.
To enable HTML tooltips, make sure this line in XHtmlTree.h is not commented out:
If you include HTML tooltips, you must include additional files in your project - see How To Use section below for details.
One final point about tooltips: you have option of dynamically changing a tooltip when it is about to be displayed. A notification message (
WM_XHTMLTREE_DISPLAY_TOOLTIP - see Notification Messages below) gives you chance to use
SetItemNote() right before tooltip is displayed. To give example, in XHtmlTreeTestDlg.cpp, you will see this code (condensed here):
LRESULT CXHtmlTreeTestDlg::OnDisplayTreeToolTip(WPARAM wParam, LPARAM)
XHTMLTREEMSGDATA *pData = (XHTMLTREEMSGDATA *)wParam;
HTREEITEM hItem = pData->hItem;
CString strText = m_Tree.GetItemText(hItem);
if (_tcsncmp(strText, _T("Galgo"), 5) == 0)
_T("This is alternate text. ")
_T("For standard tooltip, it is limited to 80 characters."),
In the demo app, this is what you will see:
For all notification messages, the
wParam parameter is pointer to
HWND hCtrl; // hwnd of XHtmlTree
UINT nCtrlId; // id of XHtmlTree
HTREEITEM hItem; // current item
lParam parameter depends on specific message. The following messages are sent to XHtmlTree parent:
|WM_XHTMLTREE_CHECKBOX_CLICKED||Sent when a checkbox is clicked||New checkbox state (TRUE = checked)|
|WM_XHTMLTREE_DISPLAY_TOOLTIP||Sent when a tooltip is about to be displayed||Pointer to tooltip control (|
|WM_XHTMLTREE_INIT_TOOLTIP||Sent when tooltip control is being initialized||Pointer to tooltip control (|
|WM_XHTMLTREE_ITEM_EXPANDED||Sent when an item has been expanded or collapsed||New item state (TRUE = expanded)|
Drag & Drop
Starting with version 1.4, XHtmlTree supports drag & drop. The following drag & drop facilities and operations are implemented:
- There is new compile-time symbol to enable inclusion of drag & drop code:
Defining (or not defining) this symbol also takes care of setting
TVS_DISABLEDRAGDROP style correctly.
- Drag & drop is implemented using
CTreeCtrl::SetInsertMark() API. This is what is used, for example, for FireFox bookmarks. As an item is being dragged, what you will see is a solid thin bar - called the insert mark - that is displayed between items in the tree. The insert mark indicates where item will be inserted into the tree if it is dropped.
- By design, traditional OLE-style drag image animation has not been implemented, and there are no current plans to do so.
- In addition to display of insert mark, you have option of displaying (via
SetDropCursors() function) three custom cursors: the no-drop cursor, the drop move cursor, and the drop copy cursor. The demo app includes examples of each:
|No drop||Used to indicate a no-drop zone; either an area outside the tree control, or an item that is not an allowed drop target.|
|Drop Move||Used to indicate the dragged item will be moved when left mouse button is released.|
|Drop Copy||Used to indicate the dragged item will be copied when left mouse button is released.|
- Drop Copy vs. Drop Move - There are several ways to control whether dragged item will be copied or moved:
|The user can use Ctrl key to toggle between copy and move, before or during drag. |
|The application can control whether Ctrl key is recognized by setting/resetting |
XHTMLTREE_DO_CTRL_KEY drag operations flag.
|The application can change default behavior of Ctrl key by setting/resetting |
XHTMLTREE_DO_COPY_DRAG drag operations flag.
- The application can control each stage of a drag by handling following notification messages:
WM_XHTMLTREE_BEGIN_DRAG - This message is sent when a drag is initiated, and includes the dragged item and the state of the
XHTMLTREE_DO_COPY_DRAG flag bit. Note that in demo app, attempting to drag Longdog in Sight Hounds group will be rejected (see demo Log).
WM_XHTMLTREE_END_DRAG - This message is sent when a drag is terminated, either by a drop or by other user action (such as hitting the ESC key, right-clicking the mouse, dropping an item back onto itself, or dropping outside tree control). If the drag ends because of a drop on a valid tree item (other than itself), this message will include the proposed drop target. If the drop terminates for any other reason, a value of 0 will be sent as
lParam parameter. Note that in demo app, dropping on Longdog in Sight Hounds group will be rejected (see demo Log).
WM_XHTMLTREE_DROP_HOVER - This message is sent when the cursor is over a tree item, and includes the tree item that could be a drop target, Note that in demo app, hovering over Longdog in Sight Hounds group will display a "no-drop" cursor.
For all above messages, a pointer to
XHTMLTREEDRAGMSGDATA struct is sent as
lParam parameter (this might be NULL in the case of
HTREEITEM hItem; // item being dragged
HTREEITEM hNewParent; // proposed new parent
HTREEITEM hAfter; // drop target - item being dragged will
// either sequentially follow this item,
// or hAfter specifies the relationship
// (TVI_FIRST, TVI_LAST, etc.) the
// dragged item will have with hNewParent.
// Note that TVI_xxxx constants are all
// defined as 0xFFFFnnnn, with the 16
// high-order bits set.
BOOL bCopyDrag; // TRUE = dropped item will be copied;
// FALSE = dropped item will be moved
The application may respond to these messages with either
FALSE (indicating that proposed action may continue), or
TRUE, indicating that proposed action is not permitted. When sent in response to
WM_XHTMLTREE_END_DRAG messages, a return code of
TRUE will terminate drag. When sent in response to
WM_XHTMLTREE_DROP_HOVER message, a return code of
TRUE will cause the no-drop cursor to be displayed.
Since the default return code - in the absence of a message handler - is 0 or
FALSE, it is not necessary to implement handlers for any of these messages in order to enable drag & drop.
- By default, during a drag the tree will auto-scroll when cursor approaches top or bottom of control. The tree scrolls at three different speeds, depending on how close the cursor is to the edge. In addition, the application can select one of two overall speed settings - normal or fast - and can also completely disable drag scrolling. Drag scrolling is controlled by drag operations flags
XHTMLTREE_DO_SCROLL_FAST. If both of these flag bits are 0, no scrolling will occur.
- By default, during drag the tree will auto-expand a node when cursor hovers over it. This behavior may be disabled with drag operations flag bit
- By using
SetInsertMark() API in conjunction with auto-expand, the user may drop an item (or branch) after any existing node. However, these two mechanisms do not allow for drop-under, where the drop results in creation of a child item. To accommodate this, the user may use Shift key before or during drag. Holding down Shift key causes drop to become drop-under: the insert mark is removed, and instead the drop target (the proposed new parent) is highlighted. This behavior may be disabled with drag operations flag bit
- Drag operations involving disabled or read-only items are not explicitly disallowed by XHtmlTree. If you want to prevent dragging of disabled or read-only items, you should set up handler for
WM_XHTMLTREE_BEGIN_DRAG message. See XHtmlTreeTestDlg.cpp for example.
- There is known problem that when Ctrl key is down, the ESC key will not terminate drag.
Starting with version 1.6, XHtmlTree
supports item separators. Separators are simply visual indicators that are meant to set apart or divide tree items. In the following screenshot, the three "Australian" dogs are enclosed in two separators:
To try out separators yourself, you can use right-click menu in demo app:
The following separator facilities and operations are implemented:
- The function
InsertSeparator() inserts a separator after the specified item. There is no limit on the number of separators.
- The function
IsSeparator() allows you to test whether a tree item is separator.
- You can change the color of individual separators by using
SetItemTextColor(), or you can use
SetSeparatorColor() to change the color of all separators. The default separator color is that returned by
- Separators can be selected just like any other tree item.
- Separators can be dragged just like any other tree item.
- Separators cannot have text.
- Separators cannot have children. Attempting to drop an item on a separator will always cause item to be inserted after separator ("drop under" is not allowed). However, it is allowed to drop a separator under a non-separator item.
- Separators cannot be edited via
- Separators are included in count of children, but have no effect on Smart Checkboxes.
- You can specify separators to be loaded from XML file by using attribute separator=1.
How To Use
To integrate XHtmlTree into your own app, you first need to add following files to your project:
Files marked with
† must be set in Visual Studio to Not Using Precompiled headers.
If you want to use HTML tooltips, then you must also include these files:
And finally, if you want to include XML functions, then you must include these two files:
Then declare variable for
CTreeCtrl object, and change its type to
Now you are ready to use
CXHtmlTree in your project.
XHtmlTree Compile-time Options
There are three compile-time options that control what XHtmlTree features are included. These options may be selected by editing XHtmlTree.h, by including them as
#define statements (for example, in stdafx.h), or by defining them via IDE (in VS2005, go to Project | Properties | Configuration Properties | C/C++ | Preprocessor | Preprocessor Definitions; be sure to do this for All Configurations).
XHTMLHTML - when defined, this option will enable use of HTML in tree items.
XHTMLTOOLTIPS - when defined, this option will enable use of HTML tooltips.
XHTMLXML - when defined, this option will enable loading/saving XML data.
XHTMLDRAGDROP - when defined, this option will enable drag & drop.
The following table shows what source modules need to be included for each compile-time option (including the case where no options are selected, which is what is used in MinDialog demo):
|XHtmlTree Compile-time Options|
Files marked with
† must be set in Visual Studio to Not Using Precompiled headers.
The files needed for drag & drop (
XHTMLDRAGDROP) are the same ones listed under None.
|You can load tree the way you are currently doing it, or - if you define |
XHTMLXML in XHtmlTree.h - you can use
LoadXmlFromXXXX functions - see XHtmlTreeTestDlg.cpp for example. The XML parser used by XHtmlTree is extremely simple, and does not perform any strict error checking. In this condensed snippet of XML from dogs.xml, note that HTML tags must be escaped with character entities:
Click to enlarge.
Here are attribute names expected by
- name - this is the item text that will be inserted in the tree. It can include HTML formatting.
- checked - gives the item an initial checked state (if "1") or unchecked state ("0"). Default is unchecked.
- enabled - gives the item an initial enabled state (if "1") or unchecked state ("0"). Default is enabled.
- separator - specifies that item is separator (if "1") or not a separator ("0"). Default is not a separator.
- image - associates image from image list with item. Use
SetImageList() to set the image list.
- text-color - text color of item; overrides the global tree text color.
- text-background-color - text background color of item; overrides the global tree text background color.
- note-width - tooltip width for this item. Default of 0 means that a heuristic will be used to determine appropriate width.
- note - this text will be displayed instead of the standard tooltip text. This can include HTML tags, if
XHTMLTOOLTIPS has been defined (see XHtmlTree.h).
You can load XML from a resource, a file, or a memory buffer, and you can save XML to a file.
Here are functions implemented to support XML:
|BOOL ConvertBuffer(const BYTE * inbuf, DWORD inlen, BYTE ** outbuf, DWORD& outlen, ConvertAction eConvertAction = NoConvertAction) ||Convert XML buffer to/from Unicode|
|CString GetXmlText(HTREEITEM hItem, LPCTSTR lpszElem)||Retrieve the XML for item|
|BOOL LoadXmlFromBuffer(const BYTE * pBuf, DWORD len, ConvertAction eConvertAction) ||Loads XML from memory buffer |
|BOOL LoadXmlFromFile(LPCTSTR lpszFile, ConvertAction eConvertAction) ||Loads XML from file |
|BOOL LoadXmlFromResource(HINSTANCE hInstance, LPCTSTR lpszResId, LPCTSTR lpszResType, ConvertAction eConvertAction) ||Loads XML from resource |
|BOOL SaveXml(HTREEITEM hItem, LPCTSTR lpszFileName, BOOL bSaveAsUTF16) ||Saves XML to file |
Here are links I have mentioned in this article. I have also included links to my articles on CodeProject, which I have used in demo app.
Version 1.6 - 2007 December 19
- Added support for separators.
- Fixed bug with multiple roots - dropping a root node always created child node instead of root node.
- Added multiple-root sample to demo app.
Version 1.5 - 2007 November 7
- Fixed bug with XML input (crash if no image list); reported by Berni Slootbeek.
- Updated to XHtmlDraw v1.2.
Version 1.4 - 2007 November 4
- Added support for drag & drop, suggested by David McMinn. Thanks for your help, David!
Version 1.3 - 2007 October 16
- Added demo project for "minimum dialog". This project shows how to use XHtmlTree with Smart Checkboxes, but without HTML, XML, or HTML tooltips. This reduces size of executable by about 130Kb.
Version 1.2 - 2007 October 14
- Tweaked display performance.
- Minor changes to demo app.
- Added handler for
WM_CONTEXTMENU to demo app.
CXHtmlTree::EnableBranch(HTREEITEM hItem, BOOL bEnabled)
- Fixed problem with using spacebar to check item, reported by David McMinn.
- Fixed problem with in-place edit box not closing, reported by David McMinn.
- Fixed several problems with in-place editing, using code supplied by David McMinn.
Version 1.1 - 2007 October 10
- Added demo project for MDI app.
- Eliminated flickering of desktop icons, reported by Greg Cadmes.
- Fixed version problem with system color definitions, reported by Graham Shanks.
- Fixed compile problem with
CVisualStylesXP::UseVisualStyles(), reported by Graham Shanks.
- Smart checkboxes now default to off (FALSE); suggested by Graham Shanks.
- Added color support for disabled tree, suggested by Graham Shanks.
CheckAll() to handle multiple root nodes, reported by Rolando E. Cruz-Marshall.
- Fixed problem with
SetCheck(), reported by Graham Shanks.
Get/SetReadOnly() functions, suggested by Graham Shanks. The
SetReadOnly() function toggles all checkboxes between active (read/write) and inactive (read-only), and also allows/prevents in-place editing. When set to read-only, there is no automatic visual indication that the tree is read-only. You can use the
SetBkColor() function to set the background of read-only tree to indicate read-only state. Also added option to demo app to set read-only state.
- Added Shih Tzu to dog list, suggested by bolivar123.
Version 1.0 - 2007 August 9
This software is released into the public domain. You are free to use it in any way you like, except that you may not sell this source code. If you modify it or extend it, please to consider posting new code here for everyone to share. This software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software may cause.