//----------------------------------------------------------------------------
// ObjectWindows
// Copyright (c) 1991, 1996 by Borland International, All Rights Reserved
//
/// \file
/// Implementation of class TListBox and TListBoxData.
//----------------------------------------------------------------------------
#include <owl/pch.h>
 
#include <owl/listbox.h>
#include <stdlib.h>
#include <vector>
 
using namespace std;
 
namespace owl {
 
OWL_DIAGINFO;
 
// Let the compiler know that the following template instances will be defined elsewhere.
//#pragma option -Jgx
 
 
#define MULTIPLESEL    (LBS_MULTIPLESEL | LBS_EXTENDEDSEL)
 
//
// TListBoxData constructor
/// Constructs Strings and SelStrings. Initializes SelCount to 0.
//
TListBoxData::TListBoxData()
{
  Strings = new TStringArray;
  ItemDatas = new TLParamArray;
  SelIndices = new TIntArray;
}
 
//
// TListBoxData destructor
/// Deletes the space allocated for Strings and SelStrings.
//
TListBoxData::~TListBoxData()
{
  delete Strings;
  delete ItemDatas;
  delete SelIndices;
}
//
/// Resets the list box by flushing the ItemDatas and Strings arrays and calling
/// ResetSelections.
void TListBoxData::Clear()
{
  Strings->Flush();
  ItemDatas->Flush();
  ResetSelections();
}
//
/// Returns the number of items in SelIndices.
int TListBoxData::GetSelCount() const
{
  return SelIndices->Size();
}
//
/// Flushes SelIndices.
void TListBoxData::ResetSelections()
{
  SelIndices->Flush();
}
 
//
/// Adds the specified string to Strings. If IsSelected is true, the string is
/// selected.
//
void
TListBoxData::AddString(LPCTSTR str, bool isSelected)
{
  Strings->Add(str);
  if (isSelected)
    Select(Strings->Size()-1);
}
 
//
/// Adds a string to the Strings array, optionally selects it, and adds itemData to
/// the ItemDatas array.
//
void
TListBoxData::AddStringItem(LPCTSTR str, LPARAM itemData, bool isSelected)
{
  ItemDatas->Add(itemData);
  AddString(str, isSelected);
}
 
//
/// Selects the string at the given index.
//
void
TListBoxData::Select(int index)
{
  if (index != LB_ERR && SelIndices->Find(index) == SelIndices->NPOS)
    SelIndices->Add(index);
}
 
//
/// Adds the index of the string matching str to SelIndices.
//
void
TListBoxData::SelectString(LPCTSTR str)
{
  for (uint i = 0; i < Strings->Size(); i++)
    if (_tcscmp((*Strings)[i].c_str(), str) == 0) {
      Select(i);
      break;
    }
}
 
//
/// Returns the length (excluding the terminating NULL) of the string at the
/// specified index in SelIndices.
//
int
TListBoxData::GetSelStringLength(int index) const
{
  return index >= 0 && index < GetSelCount() ?
    static_cast<int>((*Strings)[(*SelIndices)[index]].length()) :
    -1;
}
 
//
/// Locates the string at the specified index in SelIndices and copies it into
/// buffer. bufferSize includes the terminating NULL.
//
void
TListBoxData::GetSelString(tchar * buffer, int bufferSize, int index) const
{
  if (bufferSize > 0) {
    if (index < 0 || index >= GetSelCount())
      *buffer = 0;
 
    else {
      _tcsncpy(buffer, (*Strings)[(*SelIndices)[index]].c_str(), bufferSize-1);
      buffer[bufferSize - 1] = 0;
    }
  }
}
 
//
/// Locates the string at the specified index in SelIndices and copies it into str.
//
void
TListBoxData::GetSelString(tstring& str, int index) const
{
  if (index >= 0 && index < GetSelCount())
    str = (*Strings)[(*SelIndices)[index]];
  else
    str = _T("");
}
 
//----------------------------------------------------------------------------
 
//
// Constructor for TListBox
//
//
/// Constructs a list box object with the supplied parent window (parent) library ID
/// (module), position (x, y) relative to the origin of the parent window's client
/// area, width (w), and height (h). Invokes a TControl constructor. Adds
/// LBS_STANDARD to the default styles for the list box to provide it with
/// - A border (WS_BORDER)
/// - A vertical scroll bar (WS_VSCROLL)
/// - Automatic alphabetic sorting of list items (LBS_SORT)
/// - Parent window notification upon selection (LBS_NOTIFY)
/// The TListBox member functions that are described as being for single-selection
/// list boxes are inherited by TComboBox and can also be used by combo boxes. Also,
/// these member functions return -1 for multiple-selection list boxes.
//
TListBox::TListBox(TWindow*   parent,
                   int        id,
                   int x, int y, int w, int h,
                   TModule*   module)
:
  TControl(parent, id, 0, x, y, w, h, module)
{
  Attr.Style |= LBS_STANDARD;
  Attr.ExStyle |= WS_EX_CLIENTEDGE;  // Creates 3d sunken inside edge
}
 
//
/// Constructs a TListBox object to be associated with a list box control of a
/// TDialog object. Invokes the TControl constructor with similar parameters. The
/// module parameter must correspond to a list box resource that you define.
//
TListBox::TListBox(TWindow*   parent,
                   int        resourceId,
                   TModule*   module)
:
  TControl(parent, resourceId, module)
{
}
 
//
/// Constructs a TListBox object to encapsulate (alias) an existing control.
//
TListBox::TListBox(THandle hWnd, TModule* module)
:
  TControl(hWnd, module)
{
}
 
//
/// Returns the name of TListBox's registration class, "LISTBOX".
//
auto TListBox::GetWindowClassName() -> TWindowClassName
{
  return TWindowClassName{_T("LISTBOX")};
}
 
//
//
/// Transfers the items and selection(s) of the list box to or from a transfer
/// buffer if tdSetData or tdGetData, respectively, is passed as the direction.
/// buffer is expected to point to a pointer to a TListBoxData structure.
///
/// Transfer returns the size of the TListBoxData structure. To retrieve the size
/// without transferring data, your application must pass tdSizeData as the
/// direction.
//
uint
TListBox::Transfer(void* buffer, TTransferDirection direction)
{
  if (!buffer && direction != tdSizeData) return 0;
  long           style = GetStyle();
  TListBoxData*  listBoxData = (TListBoxData*)buffer;
 
  if (direction == tdGetData) {
 
    // First, clear out Strings array and fill with contents of list box
    //
    listBoxData->Clear();
 
    // Pre-calculate max string length so that one big buffer can be used
    //
    int  count = GetCount();
    int  maxStrLen = 0;
    int  i;
    for (i = 0; i < count; i++) {
      int  strLen = GetStringLen(i);
      if (strLen > maxStrLen)
        maxStrLen = strLen;
    }
 
    // Get each string and item data in the listbox & add to listboxdata
    //
    TAPointer<tchar> tmpStr(new tchar[maxStrLen+1]);
    for (i = 0; i < GetCount(); i++) {
      GetString(tmpStr, i);
      listBoxData->AddStringItem(tmpStr, GetItemData(i), false);
    }
 
    // Update transfer data with new selected item(s)
    //
    listBoxData->ResetSelections();
 
    if (!(style & MULTIPLESEL)) {
      // Single selection
      //
      listBoxData->Select(GetSelIndex());
    }
    else {
      // Multiple selection
      //
      int  selCount = GetSelCount();
      if (selCount > 0) {
        int*  selections = new int[selCount];
 
        GetSelIndexes(selections, selCount);
 
        // Select each item by index
        //
        for (int selIndex = 0; selIndex < selCount; selIndex++)
          listBoxData->Select(selections[selIndex]);
 
        delete[] selections;
      }
    }
  }
  else if (direction == tdSetData) {
    ClearList();
 
    // Add each string, item data and selections in listBoxData to list box
    //
    const int noSelection = -1;
    uint  selCount = listBoxData->GetSelCount();  // for multi selection
    int  selIndex = noSelection;                 // for single selection
    for (uint i = 0; i < listBoxData->GetStrings().Size(); i++) {
      // Index may be different from i when the listbox is sorted.
      //
      int index = AddString(listBoxData->GetStrings()[i]);
      if(i < listBoxData->GetItemDatas().Size())
        SetItemData(index, listBoxData->GetItemDatas()[i]);
      if (style & MULTIPLESEL) {
        for (uint j = 0; j < selCount; j++)
          if (listBoxData->GetSelIndices()[j] == (int)i) {
            SetSel(index, true);
            break;
          }
      }
      else {
        if (selCount && (uint)listBoxData->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(TListBoxData);
}
 
#if 0 // Obsoleted by FindStringExact
 
//
/// Returns the index of the first string in the associated listbox
/// which is the same as the passed string
//
/// Starting at the line number passed in searchIndex, searches the list box for an
/// exact match with the string str. If a match is not found after the last string
/// has been compared, the search continues from the beginning of the list until a
/// match has been found or until the list has been completely traversed. Searches
/// from the beginning of the list when -1 is supplied as searchIndex. Returns the
/// index of the first string found if successful, or a negative value if an error
/// occurs.
///
int
TListBox::FindExactString(LPCTSTR findStr, int startIndex) const
{
  bool  found = false;
  int   firstMatch = indexStart = FindString(findStr, startIndex);
  do {
    if (startIndex > -1) {
      TAPointer<tchar> tmpStr = new tchar[GetStringLen(startIndex) + 1];
 
      GetString(tmpStr, startIndex);
 
      if (_tcscmp(tmpStr, findStr) == 0)
        found = true;
      else
        startIndex = FindString(findStr, startIndex);
    }
  } while (!found && startIndex != firstMatch);
 
  return found ? startIndex : -1;
}
 
#endif
 
//
/// For single-selection list boxes. Retrieves the currently selected item, putting
/// up to maxChars of it in str. GetSelString returns one of the following: the
/// string length, a negative value if an error occurs, or -1 if no string is
/// selected. For multiple-selection list boxes, it returns -1.
//
/// \note Since the Windows function is not passed a size parameter, we have to
/// allocate a string to hold the largest string (gotten from a query), and
/// copy a part of it
//
int
TListBox::GetSelString(LPTSTR str, int maxChars) const
{
  int  index = GetSelIndex();
 
  if (index > -1) {
    int  length = GetStringLen(index);
 
    if (maxChars >= length)
      return GetString(str, index);
    else if(length > 0){
      TAPointer<tchar> tmpStr(new tchar[length + 1]);
      GetString(tmpStr, index);
      _tcsncpy(str, tmpStr, maxChars);
      return maxChars;
    }
  }
  return -1;
}
 
//
/// Returns the number of selected items in the single- or multiple-selection list
/// box or combo box.
//
int
TListBox::GetSelCount() const
{
  if (!(GetStyle() & MULTIPLESEL))
    return GetSelIndex() < 0 ? 0 : 1;
 
  // Multiple-selection list box
  //
  return (int)CONST_CAST(TListBox*,this)->SendMessage(LB_GETSELCOUNT);
}
 
//
/// For multiple-selection list boxes, retrieves the total number of selected items
/// for a multiselection list and copies them into the buffer. The strs parameter is
/// an array of pointers to chars. Each of the pointers to the buffers is of
/// maxChars. maxCount is the size of the array.
///
/// Returns the number of items put into Strings.  -1 is returned if this is
/// not a multiple-selection list box
///
/// \note Since the Windows function is not passed a size parameter, we have to
/// allocate a string to hold the largest string (gotten from a query), and
/// copy a part of it
//
int
TListBox::GetSelStrings(LPTSTR* strs, int maxCount, int maxChars) const
{
  if (!(GetStyle() & MULTIPLESEL))
    return -1;
 
  int i = GetSelCount();
 
  if (i < maxCount)
    maxCount = i;
 
  if (maxCount > 0) {
    int*  selections = new int[maxCount];
 
    GetSelIndexes(selections, maxCount);
 
    for (int selIndex = 0; selIndex < maxCount; selIndex++) {
      int  tmpStrLen = GetStringLen(selections[selIndex]);
 
      if (maxChars >= tmpStrLen)
        GetString(strs[selIndex], selections[selIndex]);
 
      else if(tmpStrLen > 0) {
        TAPointer<tchar> tmpStr(new tchar[tmpStrLen+1]);
        GetString(tmpStr, selections[selIndex]);
        _tcsncpy(strs[selIndex], tmpStr, maxChars);
      }
    }
    delete[] selections;
  }
  return maxCount;
}
 
//
/// Container-aware overload
//
TStringArray
TListBox::GetSelStrings() const
{
  TStringArray s;
  TIntArray selections = GetSelIndexes();
  for (int i = 0; i != (int)selections.size(); ++i)
    s.Add(GetString(selections[i]));
  return s;
}
 
//
/// For single-selection list boxes. Forces the selection of the first item
/// beginning with the text supplied in str that appears beyond the position
/// (starting at 0) supplied in SearchIndex. If startIndex is -1, the entire list
/// is searched, beginning with the first item. SetSelString returns the position of
/// the newly selected item, or a negative value in the case of an error.
//
int
TListBox::SetSelString(LPCTSTR findStr, int startIndex)
{
  if (!(GetStyle() & MULTIPLESEL))
    return (int)SendMessage(LB_SELECTSTRING, startIndex, TParam2(findStr));
  return -1;
}
 
//
/// For multiple-selection list boxes, selects the strings in the associated list
/// box that begin with the prefixes specified in the prefixes array. For each
/// string the search begins at the beginning of the list and continues until a
/// match is found or until the list has been completely traversed. If shouldSet is
/// true, the matched strings are selected and highlighted; if shouldSet is false,
/// the highlight is removed from the matched strings and they are no longer
/// selected. Returns the number of strings successfully selected or deselected (-1
/// for single-selection list boxes and combo boxes). If numSelections is less than
/// 0, all strings are selected or deselected, and a negative value is returned on
/// failure.
//
int
TListBox::SetSelStrings(LPCTSTR* strs, int numSelections, bool shouldSet)
{
  if (!(GetStyle() & MULTIPLESEL))
    return -1;
 
  if (numSelections < 0)
    return SetSel(-1, shouldSet);
 
  int  successes = 0;
  for (int i = 0; i < numSelections; i++) {
    int  selIndex;
    if ((selIndex = FindString(strs[i], -1)) > -1)
      if (SetSel(selIndex, shouldSet) > -1)
        successes++;
  }
  return successes;
}
 
//
/// Container-aware overload
//
int
TListBox::SetSelStrings(const TStringArray& strs, bool shouldSet)
{
  if (!(GetStyle() & MULTIPLESEL))
    return -1;
 
  int successes = 0;
  for (int i = 0; i < static_cast<int>(strs.size()); i++)
  {
    int selIndex = FindString(strs[i], -1);
    if (selIndex > -1)
      if (SetSel(selIndex, shouldSet) > -1)
        successes++;
  }
  return successes;
}
 
//
/// For single-selection list boxes. Returns the non-negative index (starting at 0)
/// of the currently selected item, or a negative value if no item is selected.
///
/// \note A negative value is returned if this is a multiple-selection list box
//
int
TListBox::GetSelIndex() const
{
  if (!(GetStyle() & MULTIPLESEL))
    return (int)CONST_CAST(TListBox*,this)->SendMessage(LB_GETCURSEL);
  return -1;
}
 
//
/// For multiple-selection list boxes. Fills the indexes array with the indexes of
/// up to maxCount selected strings. Returns the number of items put in indexes (-1
/// for single-selection list boxes and combo boxes).
//
int
TListBox::GetSelIndexes(int* indexes, int maxCount) const
{
  if (!(GetStyle() & MULTIPLESEL))
    return -1;
  return (int)CONST_CAST(TListBox*,this)->SendMessage(LB_GETSELITEMS,
                                                        maxCount,
                                                        TParam2(indexes));
}
 
//
/// Container-aware overload.
/// For multiple-selection list boxes.
/// \note An empty container is returned if this is a single-selection list box.
//
TIntArray
TListBox::GetSelIndexes() const
{
  TIntArray selections;
  if (!(GetStyle() & MULTIPLESEL)) return selections;
  int selCount = GetSelCount();
  if (selCount > 0)
  {
    std::vector<int> buf(selCount);
    int n = GetSelIndexes(&buf[0], static_cast<int>(buf.size()));
    CHECK(n == static_cast<int>(buf.size()));
    for (int i = 0; i != n; ++i)
      selections.Add(buf[i]);
  }
  return selections;
}
 
//
/// For single-selection list boxes. Forces the selection of the item at the
/// position (starting at 0) supplied in index. If index is -1, the list box is
/// cleared of any selection. SetSelIndex returns a negative number if an error
/// occurs.
//
int
TListBox::SetSelIndex(int index)
{
  if (!(GetStyle() & MULTIPLESEL))
    return (int)SendMessage(LB_SETCURSEL, index);
  return -1;
}
 
//
/// For multiple-selection list boxes. Selects or deselects the strings in the
/// associated list box at the indexes specified in the Indexes array. If ShouldSet
/// is true, the indexed strings are selected and highlighted; if ShouldSet is false
/// the highlight is removed and they are no longer selected. Returns the number of
/// strings successfully selected or deselected (-1 for single-selection list boxes
/// and combo boxes). If NumSelections is less than 0, all strings are selected or
/// deselected, and a negative value is returned on failure.
//
int
TListBox::SetSelIndexes(int* indexes, int numSelections, bool shouldSet)
{
  int  successes = 0;
 
  if (!(GetStyle() & MULTIPLESEL))
    return -1;  // including if it's a combobox
 
  if (numSelections < 0)
    return (int)SendMessage(LB_SETSEL, shouldSet, -1);
 
  else {
    for (int i = 0; i < numSelections; i++)
      if ((int)SendMessage(LB_SETSEL, shouldSet, indexes[i]) > -1)
        successes++;
  }
  return successes;
}
 
//
/// Container-aware overload
//
int
TListBox::SetSelIndexes(const TIntArray& indexes, bool shouldSet)
{
  if (!(GetStyle() & MULTIPLESEL))
    return -1;  // including if it's a combobox
 
  int successes = 0;
  for (int i = 0; i < static_cast<int>(indexes.size()); i++)
    if (static_cast<int>(SendMessage(LB_SETSEL, shouldSet, indexes[i])) > -1)
      successes++;
  return successes;
}
 
//
/// Returns true if the index is selected.
bool TListBox::IsSelected(int index) const
{
  PRECONDITION(GetHandle());
  if (GetStyle() & MULTIPLESEL) return SendMessage(LB_GETSEL, TParam1(index)) > 0;
  return GetSelIndex() == index;
}
 
//
/// Sets the selection state for the item at the specified index, and returns
/// true if successful.
bool TListBox::SetSelected(int index, bool selected)
{
  PRECONDITION(GetHandle());
  if (GetStyle() & MULTIPLESEL) return SendMessage(LB_SETSEL, TParam1(selected), TParam2(index)) != LB_ERR;
  return (SetSelIndex(selected ? index : -1) >= 0) || (index == -1) || !selected;
}
 
//
/// Replaces the item in the list at the position supplied in index, and returns that
/// item's actual position (starting at 0) in the list. A negative value is returned
/// if an error occurs. The list is not resorted.
//
int TListBox::ReplaceString(LPCTSTR str, int index)
{
  auto isSel = IsSelected(index);
  auto ret = DeleteString(index);
  if (ret >= 0)
  {
    ret = InsertString(str, index);
    if ((ret >= 0) && isSel) SetSelected(index);
  }
  return ret;
}
 
//
/// For use with CopyText
//
struct TListBoxGetString
{
  const TListBox& listbox;
  int index;
  TListBoxGetString(const TListBox& c, int index_) : listbox(c), index(index_) {}
 
  int operator()(LPTSTR buf, int buf_size)
  {return listbox.GetString(buf, index);}
};
 
//
/// String-aware overload
//
tstring TListBox::GetString(int index) const
{
  return CopyText(GetStringLen(index), TListBoxGetString(*this, index));
}
 
//
/// String-aware overload
//
tstring TListBox::GetSelString() const
{
  int i = GetSelIndex();
  return (i < 0) ? tstring() : GetString(i);
}
 
tstring TListBoxData::GetSelString(int index) const
{
  tstring s;
  GetSelString(s, index);
  return s;
}
 
 
 
IMPLEMENT_STREAMABLE1(TListBox, TControl);
 
#if OWL_PERSISTENT_STREAMS
 
//
// Reads an instance of TListBox from the supplied ipstream
//
void*
TListBox::Streamer::Read(ipstream& is, uint32 /*version*/) const
{
  ReadBaseObject((TControl*)GetObject(), is);
  return GetObject();
}
 
//
// Writes the TListBox to the supplied opstream
//
void
TListBox::Streamer::Write(opstream& os) const
{
  WriteBaseObject((TControl*)GetObject(), os);
}
 
#endif
 
} // OWL namespace
/* ========================================================================== */
 

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

V815 Decreased performance. Consider replacing the expression 'str = ""' with 'str.clear()'.