//----------------------------------------------------------------------------
// ObjectWindows
// Copyright (c) 1995, 1996 by Borland International, All Rights Reserved
//
// Portions Copyright (C) 1997, 98 by Dieter Windau from TCheckListBox class
// EMail: dieter.windau@kvwl.de
// Web:   http://www.members.aol.com/SoftEngage
//
// Added Constructors to work with TCheckListItemArray   --  07/16/1998
// Merged with OWL class and revised by Yura Bidus       --  06/17/1998,
//
/// \file
/// Implements TCheckList and TCheckListItem
//----------------------------------------------------------------------------
#include <owl/pch.h>
 
#include <owl/checklst.h>
#include <owl/commctrl.h>
#include <owl/uihelper.h>
#include <string.h>
 
namespace owl {
 
OWL_DIAGINFO;
DIAG_DECLARE_GROUP(OwlControl);
 
#define MULTIPLESEL    (LBS_MULTIPLESEL | LBS_EXTENDEDSEL)
 
 
#  if defined(BI_COMP_BORLANDC)
#    pragma warn -inl
#  endif
TCheckListData::TCheckListData()
{
  SelIndices = new TIntArray;  // Contains State of every item
}
//-----------------------------------------------------------------------------
TCheckListData::~TCheckListData()
{
  delete SelIndices;
}
#  if defined(BI_COMP_BORLANDC)
#    pragma warn .inl
#  endif
//-----------------------------------------------------------------------------
int
TCheckListData::AddString(const tstring& str, bool isSelected)
{
  int index = Items.Add(new TCheckListItem(str));
  if(isSelected)
    SelIndices->Add(index);
  return index;
}
//-----------------------------------------------------------------------------
int
TCheckListData::AddStringItem(const tstring& str, UINT_PTR itemData, bool isSelected)
{
  TCheckListItem* item = new TCheckListItem(str);
  item->SetData(itemData);
  int index = Items.Add(item);
  if(isSelected)
    SelIndices->Add(index);
  return index;
}
//-----------------------------------------------------------------------------
void
TCheckListData::Clear(bool del)
{
  Items.Flush(del);
  ResetSelections();
}
//-----------------------------------------------------------------------------
void
TCheckListData::Select(int index)
{
  SelIndices->Add(index);
}
//-----------------------------------------------------------------------------
void
TCheckListData::SelectString(const tstring& str)
{
  for (int i = 0; i < static_cast<int>(Items.size()); ++i)
    if (Items[i]->GetText() == str)
    {
      SelIndices->Add(i);
      break;
    }
}
//-----------------------------------------------------------------------------
int
TCheckListData::GetSelCount() const
{
  return SelIndices->size();
}
//-----------------------------------------------------------------------------
void
TCheckListData::ResetSelections()
{
  SelIndices->Flush();
}
 
//
/// Creates a checklist.
/// \note The constructor does not take ownership of the items. The lifetime and memory of the 
/// items must be handled by the caller, which must ensure the items outlive the checklist.
//
TCheckList::TCheckList(TWindow* parent, int id, int x, int y, int w, int h,
                       TCheckListItem* items, int numItems,
                       TModule* module)
:
  TListBox(parent, id, x, y, w, h, module)
{
  Attr.Style |= LBS_NOTIFY | LBS_OWNERDRAWVARIABLE | LBS_NOINTEGRALHEIGHT;
  Attr.Style &= ~LBS_HASSTRINGS;
  Attr.Style &= ~LBS_SORT;
 
  for(int i = 0; i < numItems; i++)
    Items.Add(&items[i]);
 
  EnableTransfer();
 
  TRACEX(OwlControl, OWL_CDLEVEL, "TCheckList constructed @" << (void*)this);
}
 
//
/// Creates a checklist.
/// \note The constructor does not take ownership of the items. The lifetime and memory of the 
/// items must be handled by the caller, which must ensure the items outlive the checklist.
//
TCheckList::TCheckList(TWindow* parent, int id, int x, int y, int w, int h,
                       TCheckListArray& items, TModule* module)
:
  TListBox(parent, id, x, y, w, h, module)
{
  Attr.Style |= LBS_NOTIFY | LBS_OWNERDRAWVARIABLE | LBS_NOINTEGRALHEIGHT;
  Attr.Style &= ~LBS_HASSTRINGS;
  Attr.Style &= ~LBS_SORT;
 
  for(int i = 0; i < (int)items.Size(); i++)
    Items.Add(items[i]);
 
  EnableTransfer();
 
  TRACEX(OwlControl, OWL_CDLEVEL, "TCheckList constructed @" << (void*)this);
}
 
//
/// Creates a checklist based on the given resource identifier.
/// \note The constructor does not take ownership of the items. The lifetime and memory of the 
/// items must be handled by the caller, which must ensure the items outlive the checklist.
//
TCheckList::TCheckList(TWindow* parent, int resourceId,
                       TCheckListItem* items, int numItems,
                       TModule* module)
:
  TListBox(parent, resourceId, module)
{
  for(int i = 0; i < numItems; i++)
    Items.Add(&items[i]);
 
  EnableTransfer();
 
  TRACEX(OwlControl, OWL_CDLEVEL, "TCheckList constructed from resource @" << (void*)this);
}
 
//
/// Creates a checklist based on the given resource identifier.
/// \note The constructor does not take ownership of the items. The lifetime and memory of the 
/// items must be handled by the caller, which must ensure the items outlive the checklist.
//
TCheckList::TCheckList(TWindow* parent, int resourceId, TCheckListArray& items,
                       TModule* module)
:
  TListBox(parent, resourceId, module)
{
  for(int i = 0; i < (int)items.Size(); i++)
    Items.Add(items[i]);
 
  EnableTransfer();
 
  TRACEX(OwlControl, OWL_CDLEVEL, "TCheckList constructed from resource @" << (void*)this);
}
 
//
/// Destructs the checklist.
/// \note The destructor does not delete the associated TCheckListItem objects. The owner of the
/// checklist is responsible for the lifetime and destruction of the items.
//
TCheckList::~TCheckList()
{
  TRACEX(OwlControl, OWL_CDLEVEL, "TCheckList destructed @" << (void*)this);
}
 
DEFINE_RESPONSE_TABLE1(TCheckList, TListBox)
  EV_WM_LBUTTONDOWN,
  EV_WM_CHAR,
END_RESPONSE_TABLE;
 
//
/// Adds the item into the checklist.
//
/// Adds string of item to the list box, returning its position in the list
/// (0 is the first position). Returns a negative value if an error occurs.
/// The list items are automatically sorted unless the style LBS_SORT
/// is not used for list box creation.
/// \note This function does not take ownership of the item. The lifetime and memory of the item
/// must be handled by the caller, which must ensure the item outlives the checklist box.
//
int
TCheckList::AddItem(TCheckListItem* item)
{
   if(item){
    if (GetStyle() & LBS_SORT){
      int i=0;
      while(i < (int)Items.Size()){
        if(*Items[i] > *item)
          break;
        i++;
      }
      if(i >= (int)Items.Size())
        i = -1;
      return InsertItem(item, i);
    }
    else{
      if(GetHandle() && AddString((LPTSTR)item) < 0)
        return -1;
      return Items.Add(item);
    }
  }
  return -1;
}
 
//
/// Inserts the item into the checklist box at the given position.
//
/// Inserts string of item in the list box at the position supplied in index,
/// and returns the item's actual position (starting at 0) in the list.
/// A negative value is returned if an error occurs. The list is not resorted.
/// If index is -1, the string is appended to the end of the list.
/// \note This function does not take ownership of the item. The lifetime and memory of the item
/// must be handled by the caller, which must ensure the item outlives the checklist box.
//
int
TCheckList::InsertItem(TCheckListItem* item, int idx)
{
  if(item){
    if(GetHandle() && InsertString((LPCTSTR)item, idx) < 0)
       return -1;
    if(idx == -1)
      idx = Items.Add(item);
    else
      Items.AddAt(item, idx);
    return idx;
  }
  return -1;
}
 
//
/// Detaches the item in the list at the given position (starting at 0).
/// Returns the address of the associated TCheckListBoxItem, or nullptr if an error occurs.
/// \note The caller is responsible for deleting the returned item.
//
TCheckListItem*
TCheckList::DetachItem(int idx)
{
  TCheckListItem* item = GetItem(idx);
  if(item){
    Items.DetachItem(item);
    if(GetHandle())
      DeleteString(idx);
     return item;
  }
  return 0;
}
 
//
/// Toggles the checked state when the space key is pressed.
//
void
TCheckList::EvChar(uint key, uint /*repeatCount*/, uint /*flags*/)
{
  TCheckListItem* item = GetItem(GetCaretIndex());
 
  if (item && item->IsEnabled() && key == _T(' ')) {
    item->Toggle();
    Update();
  }
//  else     /??? Eat char ???????????
//    TListBox::EvChar(key, repeatCount, flags);
}
 
//
/// Toggles the checked state when the mouse is clicked in the checkbox.
//
void
TCheckList::EvLButtonDown(uint modKeys, const TPoint& point)
{
  TListBox::EvLButtonDown(modKeys, point);
  TCheckListItem* item = GetItem(GetCaretIndex());
  if(item && item->IsEnabled() && point.x < CheckList_BoxWidth){
    item->Toggle();
    Update();
    // Inform parent of change with BN_CLICKED message
    if(GetParentO())
      GetParentO()->HandleMessage(WM_COMMAND,
                                  MkParam2(Attr.Id, BN_CLICKED),
                                  TParam2(GetHandle()));
  }
}
 
int SortCheckList(const void *a, const void *b)
{
  return _tcscmp((*(TCheckListItem**)a)->Text.c_str(),
                 (*(TCheckListItem**)b)->Text.c_str());
}
 
static void SortArray(TCheckListItem** items, uint size)
{
  qsort((void*)items, (size_t)size, sizeof(TCheckListItem*), SortCheckList);
}
 
//
/// Adds the strings into the listbox.
//
void
TCheckList::SetupWindow()
{
  TRACEX(OwlControl, 1, "TCheckList::SetupWindow() @" << (void*)this);
 
  // Call the base class
  //
  TListBox::SetupWindow();
 
  // if sorted ListBox
  if ((GetStyle() & LBS_SORT)){
    if(Items.Size())
      SortArray(&Items[0],Items.Size());
     for (uint i = 0; i < Items.Size(); i++)
      InsertString((LPCTSTR)Items[i], i);
  }
  else{
     for (uint i = 0; i < Items.Size(); i++)
       AddString((LPCTSTR)Items[i]);
  }
}
 
void
TCheckList::ClearList()
{
  TListBox::ClearList();
  Items.Flush();
}
 
//
/// Refreshes the window.
//
void
TCheckList::Update()
{
  int topIndex = GetTopIndex();
  int selIndex = GetSelIndex();
  SendMessage(WM_SETREDRAW, false);
  Invalidate();
  SetTopIndex(topIndex);
  if (selIndex != LB_ERR)
    SetSelIndex(selIndex);
  SendMessage(WM_SETREDRAW, true);
}
 
//
/// Repaints the item entirely.
//
void
TCheckList::ODAFocus(DRAWITEMSTRUCT & drawInfo)
{
  PaintItem(drawInfo);
}
 
//
// Repaints the item entirely.
//
void
TCheckList::ODASelect(DRAWITEMSTRUCT & drawInfo)
{
  PaintItem(drawInfo);
}
 
//
/// Repaints the item entirely.
//
void
TCheckList::ODADrawEntire(DRAWITEMSTRUCT & drawInfo)
{
  PaintItem(drawInfo);
}
 
//
/// Paints the item entirely.
//
void
TCheckList::PaintItem(DRAWITEMSTRUCT & drawInfo)
{
  TCheckListItem* item = GetItem(drawInfo.itemID);
  if (item == 0)
    return;
 
  const bool disabled = !item->IsEnabled() || (drawInfo.itemState & ODS_DISABLED);
 
  // Prepare DC
  //
  TDC dc(drawInfo.hDC);
 
  // Erase entire line
  //
  TRect rect(drawInfo.rcItem);
  TBrush bkgnd(TColor::SysWindow);
  dc.FillRect(rect, bkgnd);
 
  // Draw checkbox
  //
  TRect checkboxRect(rect.left+1,rect.top+1,
                     rect.left+CheckList_BoxWidth,rect.bottom-1);
 
  // Draw checkbox in 3D Windows Style
  //
  uint state;
  if (item->IsIndeterminate())
    state = TUIPart::Button3State|TUIPart::Checked;//TUIPart::Pushed;
  else
    state = TUIPart::ButtonCheck;
 
  if(item->IsChecked())
    state |= TUIPart::Checked;
 
  if (disabled)
    state |= TUIPart::Inactive;
 
  TUIPart().Paint(dc, checkboxRect, TUIPart::uiButton, (TUIPart::TState)state);
 
  // Draw select state with hightlight color
  //
  TRect textRect = rect;
  textRect.left = checkboxRect.right + 2;
 
  if (disabled)
  {
    dc.SetTextColor(TColor::SysGrayText);
    dc.SetBkColor(TColor::SysWindow);
  }
  else if (drawInfo.itemState & ODS_SELECTED)
  {
    TBrush fillBrush(TColor::SysHighlight);
    dc.FillRect(textRect, fillBrush);
    dc.SetTextColor(TColor::SysHighlightText);
    dc.SetBkColor(TColor::SysHighlight);
  }
  else
  {
    dc.SetTextColor(TColor::SysWindowText);
    dc.SetBkColor(TColor::SysWindow);
  }
 
  // Draw Text
  //
  textRect.left++;
  PaintText(dc, textRect, item->Text);
  textRect.left--;
 
  // Draw focus and selected states
  //
  if (drawInfo.itemState & ODS_FOCUS)
    dc.DrawFocusRect(textRect);
}
 
//
/// Returns the item at the specified index.
//
TCheckListItem*
TCheckList::GetItem(int index)
{
  if(GetHandle()){
    if (index < 0 || index >= GetCount())
      return 0;
    return (TCheckListItem*)GetItemData(index);
  }
  else{
    if (index < 0 || index >= (int)Items.Size())
      return 0;
    return Items[index];
  }
}
 
uint
TCheckList::Transfer(void* buffer, TTransferDirection direction)
{
  if (!buffer && direction != tdSizeData) return 0;
  long            style          = GetStyle();
  TCheckListData* checkListData = (TCheckListData*)buffer;
 
  if (direction == tdGetData){
    // First, clear out Strings array and fill with contents of list box
    //
    checkListData->Clear(false);
 
    int count = GetCount();
    for(int i =0; i < count; i++)
      checkListData->AddItem(GetItem(i));
 
    // Update transfer data with new selected item(s)
    //
    checkListData->ResetSelections();
 
    if (!(style & MULTIPLESEL)) {
      // Single selection
      //
      checkListData->Select(GetSelIndex());
    }
    else {
      // Multiple selection
      //
      int selCount = GetSelCount();
      if (selCount > 0) {
        TAPointer<int> selections(new int[selCount]);
 
        GetSelIndexes(selections, selCount);
 
        // Select each item by index
        //
        for (int selIndex = 0; selIndex < selCount; selIndex++)
          checkListData->Select(selections[selIndex]);
      }
    }
  }
  else if (direction == tdSetData){
    ClearList();
 
    // Add each string, item data and selections in listBoxData to list box
    //
    const int noSelection = -1;
    uint selCount = checkListData->GetSelCount();  // for multi selection
    int  selIndex  = noSelection;               // for single selection
    for (uint i = 0; i < checkListData->ItemCount(); i++){
      // Index may be different from i when the listbox is sorted.
      //
       int index =  AddString((LPCTSTR)checkListData->GetItem(i));
      if (style & MULTIPLESEL) {
        for (uint j = 0; j < selCount; j++)
          if (checkListData->GetSelIndices()[j] == (int)i) {
            SetSel(index, true);
            break;
          }
      }
      else {
        if (selCount && (uint)checkListData->GetSelIndices()[0] == i)
          selIndex = index;
        else
          // Inserted something before item and the item to select has been
          // pushed further down in the list.
          //
          if (selIndex != noSelection && index <= selIndex)
            selIndex++;
      }
    }
    if (selIndex != noSelection && !(style & MULTIPLESEL))
      SetSelIndex(selIndex);
  }
  return sizeof(TCheckListData);
}
 
//----------------------------------------------------------------------------
 
//
/// Sets tab stops.
/// If numTabs > 0 and tabs == 0, the function returns false.
/// \note Doesn't send LB_SETTABSTOP, because the checklist is ownerdrawn.
//
bool
TTabCheckList::SetTabStops(int numTabs, int * tabs)
{
  PRECONDITION(GetStyle() & LBS_USETABSTOPS);
 
   TabStops.Flush();
 
  if (numTabs == 0){
    // Set Default Tabstops
    //
    int nBaseUnit = GetAverageCharWidths()/2;
    TabStops.Add(nBaseUnit);
    return true;
  }
  else if (numTabs == 1 && tabs != 0){
    // Set all tabs to the same width defined in tabs
    //
    int nBaseUnit = (tabs[0] * GetAverageCharWidths())/4;
    TabStops.Add(nBaseUnit);
    return true;
  }
  else if(numTabs > 1 && tabs != 0){
    // Set all tabs to different width defined in tabs
    //
    int nStop, nBaseUnit = GetAverageCharWidths();
    int nPrevStop = 0;
    for(int i=0; i<numTabs; i++){
      nStop = (tabs[i] * nBaseUnit) / 4;
      if(nStop > nPrevStop){
        TabStops.Add(nStop);
        nPrevStop = nStop;
      }
      else
        return false;
    }
    return true;
  }
  else
    return false;
}
 
void
TTabCheckList::SetupWindow()
{
  // If the listbox has the LBS_USETABSTOPS style, set the default tab stops
  // Can't do this in constuctor, because the window font is not valid
  //
  SetTabStops();
  TCheckList::SetupWindow();
}
 
void
TTabCheckList::PaintText(TDC& dc, const TRect& textRect, const tstring& text)
{
  dc.TextRect(textRect);
  dc.TabbedTextOut(textRect.TopLeft(), text, -1, TabStops.Size(), &TabStops[0], textRect.left);
}
 
int
TTabCheckList::GetAverageCharWidths()
{
  LOGFONT logFont = TFont(GetWindowFont()).GetObject();
  int nBaseUnit = logFont.lfWidth;
  if(nBaseUnit == 0)
    nBaseUnit = LoUint16(::GetDialogBaseUnits());
  return nBaseUnit;
}
 
//----------------------------------------------------------------------------
// TCheckListItem
//
 
//
/// Toggles the state of the item.
/// If the item has three states, the cycle goes from unchecked -> checked -> indeterminate -> back to unchecked.
/// Otherwise the state toggles between unchecked and checked.
//
void
TCheckListItem::Toggle()
{
  if (!IsEnabled())
    return;
  if (HasThreeStates) {
    if (IsIndeterminate())
      Uncheck();
    else if (IsChecked())
      SetIndeterminate();
    else
      Check();
  }
  else {
   if (IsChecked())
     Uncheck();
   else
     Check();
  }
}
 
//
/// Checks the item.
//
void
TCheckListItem::Check()
{
  if (IsEnabled())
    State = BF_CHECKED;
}
 
//
/// Unchecks the item.
//
void
TCheckListItem::Uncheck()
{
  if (IsEnabled())
    State = BF_UNCHECKED;
}
 
//
/// Makes the item indeterminate.
//
void
TCheckListItem::SetIndeterminate()
{
  if (IsEnabled()){
    State = BF_GRAYED;
    HasThreeStates = true;
  }
}
 
//
/// Sets the three-state property.
//
void
TCheckListItem::SetThreeStates(bool hasThreeStates)
{
  if (IsEnabled()){
    HasThreeStates = hasThreeStates;
    if (IsIndeterminate() && !hasThreeStates)
      Check();
  }
}
 
//
/// Returns the text of the item.
//
const tstring&
TCheckListItem::GetText()
{
  return Text;
}
 
TCheckListItem&
TCheckListItem::operator =(const TCheckListItem& d)
{
  Text = d.Text;
  State = d.State;
  HasThreeStates = d.HasThreeStates;
  Enabled = d.Enabled;
  return *this;
}
 
bool
TCheckListItem::operator ==(const TCheckListItem& d) const
{
  return (Text==d.Text && State == d.State &&
          HasThreeStates == d.HasThreeStates && Enabled == d.Enabled);
}
 
} // OWL namespace
//////////////////////////////////////////////

V522 There might be dereferencing of a potential null pointer 'checkListData'.

V601 The 'false' value is implicitly cast to the integer type. Inspect the second argument.

V601 The 'true' value is implicitly cast to the integer type. Inspect the second argument.