//----------------------------------------------------------------------------
// ObjectWindows
// Copyright (c) 1995, 1996 by Borland International, All Rights Reserved
//
/// \file
/// Implements TTreeViewCtrl, TTreeNode, TTvItem
//
//----------------------------------------------------------------------------
#include <owl/pch.h>
#include <owl/treeviewctrl.h>
#include <owl/system.h>
#include <tchar.h>
#if defined(__BORLANDC__)
# pragma option -w-ccc // Disable "Condition is always true/false"
#endif
using namespace std;
namespace owl {
OWL_DIAGINFO;
DIAG_DECLARE_GROUP(OwlCommCtrl); // Common controls diagnostic group
namespace
{
const uint NodeTextCacheSize = _MAX_PATH;
} // namespace
//
/// Construct a new node given the node's text
//
/// Parameters:
/// \arg \c \b tree A TTreeViewCtrl object to associate with the node.
/// \arg \c \b text A text label for the node.
///
/// Remarks:
/// - Initializes the TreeView data member to the address of tree.
/// - Calls the SetText function, passing it text. In 32-bit
/// applications, the text is not displayed in the Tree-View control until the the
/// TTreeNode object encapsulates an item (the ItemStruct.hItem data member is
/// valid) and the Tree-View control is updated (the SetItem function is called, for
/// example).
//
TTreeNode::TTreeNode(TTreeViewCtrl& tree, LPCTSTR text)
:
TreeView(&tree)
{
SetText(text, false);
}
//
/// Construct a new node given the node's text and image info
///
/// Parameters:
/// \arg \c \b tree A TTreeViewCtrl object to associate with the node.
/// \arg \c \b text A text label for the node.
/// \arg \c \b index The index of the image in the TTreeViewCtrl image list that corresponds to
/// the node's unselected state.
/// \arg selIndex The index of the image in the TTreeViewCtrl image list that corresponds
/// to the node's selected state.
///
/// Remarks:
/// - Initializes the TreeView data member to the address of tree.
/// - Calls the SetText function, passing it text. The text is not
/// displayed in the Tree-View control until the the TTreeNode object encapsulates
/// an item (the ItemStruct.hItem data member is valid) and the Tree-View control is
/// updated (the SetItem function is called, for example).
/// - Calls the SetImageIndex function, passing it index.
/// - Calls the SetSelectedImageIndex function, passing it selIndex.
//
TTreeNode::TTreeNode(TTreeViewCtrl& tree, LPCTSTR text, int index,
int selIndex)
:
TreeView(&tree)
{
SetText(text, false);
SetImageIndex(index, false);
SetSelectedImageIndex(selIndex, false);
}
//
/// Construct based on an item.
///
/// Parameters:
/// \arg \c \b tree A TTreeViewCtrl object to associate with the node.
/// \arg \c \b item A TVITEM object representing the item.
///
/// Remarks:
/// - Initializes the TreeView data member to the address of tree.
/// - Initializes the ItemStruct data member to item.
/// - If the node has a valid label (if the TVIF_TEXT flag of item.mask
/// is set), the SetText function is called (it is passed item.pszText).
//
TTreeNode::TTreeNode(TTreeViewCtrl& tree, TVITEM item)
:
ItemStruct(item),
TreeView(&tree)
{
if (item.mask & TVIF_TEXT) {
SetText(item.pszText);
}
}
//
/// Parameters:
/// \arg \c \b tree A TTreeViewCtrl object to associate with the node.
/// \arg \c \b hItem A handle to a TVITEM object representing the item, a Tree-View Insert
/// constant (32-bit applications), or a THowToInsert value.
///
/// Remarks:
/// - Initializes the TreeView data member to the address of tree.
/// - Displays a warning message if hItem is null.
/// - Initializes the ItemStruct.hItem data member to hItem.
/// - Initializes the ItemStruct.mask data member to TVIF_HANDLE.
//
TTreeNode::TTreeNode(TTreeViewCtrl& tw, HTREEITEM hItem)
:
TreeView(&tw)
{
WARNX(OwlCommCtrl, !hItem, 1, "Constructed TTreeNode passing a null hItem");
ItemStruct.hItem = hItem;
ItemStruct.mask = TVIF_HANDLE;
}
//
/// Copy constructor: create a new node by copying another node
/// Called implicitly by functions that return a TTreeNode by value;
/// otherwise, shouldn't be needed.
///
/// Parameters
/// \arg \c \b other A TTreeNode object to copy.
//
TTreeNode::TTreeNode(const TTreeNode& other)
{
CopyNode(other);
}
//
// Assignment operator
//
TTreeNode& TTreeNode::operator=(const TTreeNode& other)
{
CopyNode(other);
return *this;
}
//
// Reset node to make it a copy of another node
//
TTreeNode&
TTreeNode::CopyNode(const TTreeNode& node)
{
TreeView = node.TreeView;
ItemStruct = node.ItemStruct;
FlushCache();
// can't do "n.CacheText" on a const object (TAPointer prevents it)
//
TTreeNode& n = CONST_CAST(TTreeNode&, node);
if (n.CacheText) {
CacheText = new tchar[::_tcslen(n.CacheText) + 1];
::_tcscpy(CacheText, n.CacheText);
ItemStruct.pszText = CacheText;
}
return *this;
}
//
/// Construct the node neighboring a given node. The flag indicates
/// whether to create the next, previous, parent, or first child node.
///
/// Parameters:
/// \arg node A TTreeNode object having a relationship to the TTreeNode object to be
/// created.
/// \arg flag A Tree-View Get Next constant (32-bit applications) or a TNextCode value
/// specifying the relationship between node and the TTreeNode object to be created.
///
/// Remarks:
/// - Initializes the TreeView data member to node.TreeView.
/// - Initializes the ItemStruct data member to node.ItemStruct.
/// - In 32-bit applications, determines which item in the Tree-View
/// control to associate with the new TTreeNode object by sending a TVM_GETNEXTITEM
/// message to the control.
//
TTreeNode::TTreeNode(const TTreeNode& tn, uint32 flag)
:
ItemStruct(tn.ItemStruct),
TreeView(tn.TreeView)
{
TreeView->GetNextItem(flag, *this);
}
//
/// Inserts a new sibling item into the Tree-View (TreeView) control. (Adds the item above this item.)
//
/// Parameters
/// \arg \c \b node A TTreeNode object encapsulating an item that will be a sibling of the new
/// item.
///
/// Return Value
/// Returns a TTreeNode object encapsulating the new item. Returns NULL upon
/// failure. (In 32-bit applications, you can check for a NULL handle using the
/// HTREEITEM() operator.)
//
TTreeNode
TTreeNode::AddSibling(const TTvItem& item) const
{
return InsertItem(item);
}
TTreeNode
TTreeNode::AddSibling(const TTreeNode& node) const
{
return InsertItem( node );
}
//
// Inserts a child before the passed item.
//
// If the function fails, the handle in object returned is NULL. Here's how
// to check for errors:
//
// node = InsertChild(TTvItem("node text"), Last);
// if (!node) // the HTREEITEM() conversion operator kicks in
// DoErrorHandling();
//
// Incidentally, the node returned has only the TVIF_HANDLE mask bit
// set, even if the item passed in contained more attributes. All the
// attributes are correctly sent to the control, but only a handle is
// returned.
//
TTreeNode
TTreeNode::InsertChild(const TTvItem& item, THowToInsert how) const
{
TV_INSERTSTRUCT tvis;
tvis.hParent = *this;
tvis.hInsertAfter = (HTREEITEM)how;
tvis.item = (TVITEM&)item;
HTREEITEM hItem = TreeView->InsertItem(&tvis);
return TTreeNode(*TreeView, hItem);
}
//
// Inserts an item before this item.
//
TTreeNode
TTreeNode::InsertItem(const TTvItem& item) const
{
TV_INSERTSTRUCT tvis;
TTreeNode parent = GetParent();
tvis.hParent = parent;
tvis.hInsertAfter = *this;
tvis.item = (TVITEM&)item;
HTREEITEM hItem = TreeView->InsertItem(&tvis);
return TTreeNode(*TreeView, hItem);
}
//
// Create a temporary structure to store additional information for the
// comparison object.
//
struct TreeCompareThunk {
const TTvComparator* This;
LPARAM ItemData;
};
//
//
//
int CALLBACK
OwlTreeWindCompare(LPARAM itemData1, LPARAM itemData2, LPARAM lParam)
{
TreeCompareThunk* ct = reinterpret_cast<TreeCompareThunk*>(lParam);
return ct->This->Compare(itemData1, itemData2, ct->ItemData);
}
//
// Recursively sort the children of the nodes.
//
bool
TTreeNode::SortChildren(const TTvComparator& comparator, bool recurse, LPARAM lParam)
{
TreeCompareThunk ct;
ct.This = &comparator;
ct.ItemData = lParam;
return TreeView->SortChildren(OwlTreeWindCompare, *this, recurse, reinterpret_cast<LPARAM>(&ct));
}
//
//
//
bool
TTreeNode::GetState(uint& state, bool getNew)
{
PRECONDITION(ItemStruct.hItem || !getNew);
PRECONDITION(TreeView || !getNew);
// Send a request message to the control if a) the user asked for the
// message to be sent, or b) the ItemStruct doesn't yet contain
// the state data.
//
if(getNew || (ItemStruct.mask & TVIF_STATE)==0){
ItemStruct.mask |= TVIF_STATE;
if (!GetItem()) {
ItemStruct.mask &= ~TVIF_STATE;
return false;
}
}
state = ItemStruct.state;
return true;
}
//
//
//
bool
TTreeNode::SetState(uint state, bool sendNow /*=true*/ )
{
PRECONDITION(ItemStruct.hItem || !sendNow);
PRECONDITION(TreeView || !sendNow);
ItemStruct.state = state;
ItemStruct.stateMask |= state;
ItemStruct.mask |= TVIF_STATE;
if (sendNow)
if (!SetItem())
return false;
return true;
}
//
// Set the node's text.
// If sendNow is false, the text is merely cached. It will be
// sent to the control on the next call to SetItem.
//
bool
TTreeNode::SetText(LPCTSTR text, bool sendNow /*=true*/)
{
PRECONDITION(TreeView || !sendNow);
if( (uint)ItemStruct.cchTextMax < ::_tcslen(text) + 1 )
FlushCache();
if (!CacheText) {
CacheText = new tchar[::_tcslen(text) + 1];
}
size_t length = ::_tcslen(text) + 1;
_tcsncpy(CacheText, text, length);
ItemStruct.pszText = CacheText;
ItemStruct.cchTextMax = static_cast<int>(length);
ItemStruct.mask |= TVIF_TEXT;
return sendNow ? SetItem() : true;
}
//
// Get the node's text.
//
// The text is copied into the "text" buffer. The caller is responsible
// for creating and managing this buffer.
//
// If getNew if false, the text is simply retrieved from the cache.
// If true, GetText queries the control for the node's current text.
// If the TTreeNode doesn't yet have any text cached, it always ignores
// getNew and queries the control directly.
//
bool
TTreeNode::GetText(LPTSTR text, uint length, bool getNew /*=false*/)
{
// Update the cache if necessary
//
GetText(getNew);
if (CacheText) {
_tcsncpy(text, CacheText, length);
}
return CacheText != 0;
}
// Retrieve a pointer to the node's text string. The node object
// owns the buffer pointed to. The caller should not delete it.
// If GetText fails, it returns 0.
//
LPCTSTR
TTreeNode::GetText(bool getNew /*=false*/)
{
PRECONDITION(TreeView || !getNew);
// Send a request message to the control if a) the user asked for the
// message to be sent, or b) the ItemStruct hasn't yet received
// the text data.
//
if (getNew || !CacheText || !(ItemStruct.mask & TVIF_TEXT)) {
FlushCache();
CacheText = new tchar[NodeTextCacheSize];
ItemStruct.mask |= TVIF_TEXT;
ItemStruct.pszText = CacheText;
ItemStruct.cchTextMax = NodeTextCacheSize;
if (!GetItem()) {
ItemStruct.mask &= ~TVIF_TEXT;
return 0;
}
}
return CacheText;
}
//
// Set the node's image indexes.
//
bool
TTreeNode::SetImageIndex(int index, bool sendNow /*=true*/)
{
PRECONDITION(TreeView || !sendNow);
ItemStruct.mask |= TVIF_IMAGE;
ItemStruct.iImage = index;
return sendNow ? SetItem() : true;
}
//
//
//
bool
TTreeNode::SetSelectedImageIndex(int index, bool sendNow)
{
PRECONDITION(TreeView || !sendNow);
ItemStruct.mask |= TVIF_SELECTEDIMAGE;
ItemStruct.iSelectedImage = index;
return sendNow ? SetItem() : true;
}
//
// Set and Get the user-defined node data.
//
bool
TTreeNode::SetItemData(LPARAM data, bool sendNow)
{
PRECONDITION(TreeView || !sendNow);
ItemStruct.mask |= TVIF_PARAM;
ItemStruct.lParam = data;
return sendNow ? SetItem() : true;
}
//
//
//
bool
TTreeNode::GetItemData(LPARAM& data, bool getNew)
{
PRECONDITION(ItemStruct.hItem || !getNew);
PRECONDITION(TreeView || !getNew);
// Send a request message to the control if a) the user asked for the
// message to be sent, or b) the ItemStruct doesn't yet contain
// the lParam data.
//
if (getNew || (ItemStruct.mask & TVIF_PARAM)==0) {
ItemStruct.mask |= TVIF_PARAM;
if (!GetItem()) {
ItemStruct.mask &= ~TVIF_PARAM;
return false;
}
}
data = ItemStruct.lParam;
return true;
}
//
// Set and Get the node's HasChildren info (TVITEM.cChildren).
//
bool
TTreeNode::GetHasChildren(int& hasChildren, bool getNew)
{
PRECONDITION(ItemStruct.hItem || !getNew);
PRECONDITION(TreeView || !getNew);
// Send a request message to the control if a) the user asked for the
// message to be sent, or b) the ItemStruct doesn't yet contain
// the cChildren data.
//
if(getNew || (ItemStruct.mask & TVIF_CHILDREN)==0){
ItemStruct.mask |= TVIF_CHILDREN;
if(!GetItem()){
ItemStruct.mask &= ~TVIF_CHILDREN;
return false;
}
}
hasChildren = ItemStruct.cChildren;
return true;
}
//
//
//
bool
TTreeNode::SetHasChildren(int count, bool sendNow)
{
PRECONDITION(TreeView || !sendNow);
ItemStruct.mask |= TVIF_CHILDREN;
ItemStruct.cChildren = count;
return sendNow ? SetItem(&ItemStruct) : true;
}
//
// Return the selected node.
//
TTreeNode
TTreeViewCtrl::GetSelection()
{
return GetRoot().GetNextItem(TTreeNode::Caret);
}
//
// Return the drop target node.
//
TTreeNode
TTreeViewCtrl::GetDropHilite()
{
return GetRoot().GetNextItem(TTreeNode::DropHilite);
}
//
// Return the first visible node.
//
TTreeNode
TTreeViewCtrl::GetFirstVisible()
{
return GetRoot().GetNextItem(TTreeNode::FirstVisible);
}
//
// Retrieve an item's bounding rectangle
//
bool
TTreeNode::GetItemRect(TRect& rect, bool textOnly /*=true*/) const
{
PRECONDITION(TreeView);
PRECONDITION(ItemStruct.hItem);
// The control expects to receive the HTREEITEM in the LPARAM
//
// Expanded by Val Ovechkin 12:50 PM 6/3/98
void *p = ▭
*(REINTERPRET_CAST(HTREEITEM*, p)) = ItemStruct.hItem;
// *(REINTERPRET_CAST(HTREEITEM*, &rect)) = ItemStruct.hItem;
return TreeView->SendMessage(TVM_GETITEMRECT, TParam1(textOnly), TParam2(&rect));
}
//
/// Sets the node's checked state.
/// Assumes that the tree has the TVS_CHECKBOXES style, or has state images manually implemented
/// in a similar manner to represent checked state.
//
void
TTreeNode::Check(bool state)
{
PRECONDITION(TreeView);
PRECONDITION(ItemStruct.hItem);
WARNX(OwlCommCtrl, !TreeView->HasStyle(TVS_CHECKBOXES), 1, "Check called for tree without the TVS_CHECKBOXES style.");
TreeView_SetCheckState(TreeView->GetHandle(), GetHTreeItem(), state);
}
//
/// Retrieves the node's checked state.
/// Assumes that the tree has the TVS_CHECKBOXES style, or has state images manually implemented
/// in a similar manner to represent checked state.
//
bool
TTreeNode::IsChecked() const
{
PRECONDITION(TreeView);
PRECONDITION(ItemStruct.hItem);
WARNX(OwlCommCtrl, !TreeView->HasStyle(TVS_CHECKBOXES), 1, "IsChecked called for tree without the TVS_CHECKBOXES style.");
uint state = TreeView_GetCheckState(TreeView->GetHandle(), GetHTreeItem());
WARNX(OwlCommCtrl, state > 1, 0, "IsChecked: State not in [0, 1].");
return state == 1;
}
//
// Empty the node's text cache
//
void
TTreeNode::FlushCache()
{
CacheText = 0; // CacheText is a smart pointer
}
//
// Delete the item from the control.
//
bool
TTreeNode::Delete()
{
PRECONDITION(TreeView);
if (TreeView->Delete(*this)) {
ItemStruct.hItem = 0;
ItemStruct.mask &= ~TVIF_HANDLE;
return true;
}
else
return false;
}
//----------------------------------------------------------------------------
// TTreeViewCtrl
DEFINE_RESPONSE_TABLE1(TTreeViewCtrl, TControl)
EV_WM_KEYDOWN,
EV_WM_SYSKEYDOWN,
END_RESPONSE_TABLE;
//
// Dynamically create the window.
//
TTreeViewCtrl::TTreeViewCtrl(TWindow* parent, int id, int x, int y, int w, int h,
uint32 style, TModule* module)
:
TControl(parent, id, 0, x, y, w, h, module)
{
InitializeCommonControls(ICC_TREEVIEW_CLASSES);
SetStyle(WS_CHILD | WS_VISIBLE | style);
uint32 exStyle = GetExStyle();
SetExStyle(exStyle |= WS_EX_CLIENTEDGE);
}
//
// Create the TTreeViewCtrl object from a resource.
//
TTreeViewCtrl::TTreeViewCtrl(TWindow* parent, int resourceId, TModule* module)
:
TControl(parent, resourceId, module)
{
InitializeCommonControls(ICC_TREEVIEW_CLASSES);
}
//
// Destructor
//
TTreeViewCtrl::~TTreeViewCtrl()
{
/*
+ // GWC mods begin
+ // If we do not call DeleteAllItems() here, then the application tries
+ // to delete them at shutdown. That causes a crash because there is no HWND.
+ DeleteAllItems();
+ // GWC mods end
*/
}
//
// Sets the style of the control.
//
void
TTreeViewCtrl::SetStyle(uint32 style)
{
TWindow::SetStyle(WS_CHILD | WS_VISIBLE | style);
}
//
// Returns true if a particular style is set.
//
bool
TTreeViewCtrl::HasStyle(uint32 style)
{
return (GetStyle() & style) ? true : false;
}
//
// Returns the common control class name WC_TREEVIEW
//
auto TTreeViewCtrl::GetWindowClassName() -> TWindowClassName
{
return TWindowClassName{WC_TREEVIEW};
}
//
// Recursively sort the children nodes.
//
bool
TTreeViewCtrl::SortChildren(PFNTVCOMPARE func, HTREEITEM parent, bool recurse, LPARAM lParam)
{
TV_SORTCB cb;
cb.hParent = parent;
cb.lpfnCompare = func;
cb.lParam = lParam;
return ToBool(SendMessage(TVM_SORTCHILDRENCB, TParam1(recurse), TParam2(&cb)));
}
//
uint
TTreeViewCtrl::Transfer(void* /*buffer*/, TTransferDirection /*direction*/)
{
return 0;
}
//
// Overrides unwanted behaviour; for a tree with the TVS_CHECKBOXES style, the toggle key (space)
// enables the display of checkboxes for tree nodes that explicitly have checkboxes turned off
// (i.e. have a state image index of 0). We want the the toggle key to be ignored in this case.
//
void
TTreeViewCtrl::EvKeyDown(uint key, uint repeatCount, uint flags)
{
if (HasStyle(TVS_CHECKBOXES) && key == VK_SPACE) // Toggle key?
{
uint s = 0;
bool r = GetSelection().GetState(s, false);
CHECK(r); InUse(r);
if ((s & TVIS_STATEIMAGEMASK) == 0) // No checkbox?
return; // Don't process the toggle key for this node.
}
TControl::EvKeyDown(key, repeatCount, flags);
}
//
// Overrides unwanted behaviour; for a tree with the TVS_CHECKBOXES style, the control interprets
// the system key Alt+Space as a checkbox toggle command. We want Alt+Space to be ignored by the
// control and processed by the frame window in the normal way (i.e. open the system menu).
//
void
TTreeViewCtrl::EvSysKeyDown(uint key, uint repeatCount, uint flags)
{
if (HasStyle(TVS_CHECKBOXES) && key == VK_SPACE) // Alt+Space?
return; // Don't process this system key as a checkbox toggle request.
TControl::EvSysKeyDown(key, repeatCount, flags);
}
//
// Private Init function to zero out the data members.
//
void
TTvItem::Init()
{
memset(this, 0, sizeof(TVITEM));
}
//
// Default constructor.
//
TTvItem::TTvItem()
{
Init();
}
//
// Initialize based on an existing item.
//
TTvItem::TTvItem(TVITEM item)
{
Init();
*(TVITEM*)this = item;
}
//
// Construct using only text.
//
TTvItem::TTvItem(LPCTSTR text, int len)
{
Init();
SetText(text, len);
}
//
// Construct based on text, an image index, and a selected index.
//
TTvItem::TTvItem(LPCTSTR text, int index, int selIndex)
{
Init();
SetText(text);
SetImageIndex(index);
SetSelectedImageIndex(selIndex);
}
//
// Sets the text of the item.
//
void
TTvItem::SetText(LPCTSTR buffer, int size)
{
mask |= TVIF_TEXT;
pszText = CONST_CAST(LPTSTR, buffer);
cchTextMax = static_cast<int>((size < 0) ? _tcslen(buffer) + 1 : std::max(::_tcslen(buffer) + 1, static_cast<size_t>(size)));
}
//
// Returns the text of the item.
//
void
TTvItem::GetText(LPTSTR buffer, int size)
{
if (mask & TVIF_TEXT) {
_tcsncpy(buffer, pszText, size);
}
}
//
// Sets the magic cookie for the item.
//
void
TTvItem::SetHTreeItem(HTREEITEM item)
{
mask |= TVIF_HANDLE;
hItem = item;
}
//
// Returns the magic cookie of the item.
//
HTREEITEM
TTvItem::GetHTreeitem() const
{
return (mask & TVIF_HANDLE) ? hItem : 0;
}
//
// Sets the extra data of the item.
//
void
TTvItem::SetItemData(LPARAM data)
{
mask |= TVIF_PARAM;
lParam = data;
}
//
// Sets the image index of the item.
//
void
TTvItem::SetImageIndex(int index)
{
mask |= TVIF_IMAGE;
iImage = index;
}
//
// Sets the selected image index of the item.
//
void
TTvItem::SetSelectedImageIndex(int index)
{
mask |= TVIF_SELECTEDIMAGE;
iSelectedImage = index;
}
//
// Returns the extra data.
//
LPARAM
TTvItem::GetItemData() const
{
return (mask & TVIF_PARAM) ? lParam : 0;
}
#if OWL_PERSISTENT_STREAMS
IMPLEMENT_STREAMABLE1(TTreeViewCtrl, TControl);
//
// Reads an instance of TTreeViewCtrl from the passed ipstream
//
void*
TTreeViewCtrl::Streamer::Read(ipstream& is, uint32 /*version*/) const
{
ReadBaseObject((TControl*)GetObject(), is);
return GetObject();
}
//
// Writes the TTreeViewCtrl to the passed opstream
//
void
TTreeViewCtrl::Streamer::Write(opstream& os) const
{
WriteBaseObject((TControl*)GetObject(), os);
}
#endif
} // OWL namespace
/* ========================================================================== */
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: Style.
↑ V730 It is possible that not all members of a class are initialized inside the constructor. Consider inspecting: Style.