2513 lines
54 KiB
C++
2513 lines
54 KiB
C++
|
/*
|
||
|
** Command & Conquer Renegade(tm)
|
||
|
** Copyright 2025 Electronic Arts Inc.
|
||
|
**
|
||
|
** This program is free software: you can redistribute it and/or modify
|
||
|
** it under the terms of the GNU General Public License as published by
|
||
|
** the Free Software Foundation, either version 3 of the License, or
|
||
|
** (at your option) any later version.
|
||
|
**
|
||
|
** This program is distributed in the hope that it will be useful,
|
||
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
** GNU General Public License for more details.
|
||
|
**
|
||
|
** You should have received a copy of the GNU General Public License
|
||
|
** along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
|
||
|
***********************************************************************************************
|
||
|
* *
|
||
|
* Project Name : Combat *
|
||
|
* *
|
||
|
* $Archive:: /Commando/Code/wwui/listctrl.cpp $*
|
||
|
* *
|
||
|
* Author:: Patrick Smith *
|
||
|
* *
|
||
|
* $Modtime:: 3/01/02 4:37p $*
|
||
|
* *
|
||
|
* $Revision:: 41 $*
|
||
|
* *
|
||
|
*---------------------------------------------------------------------------------------------*
|
||
|
* Functions: *
|
||
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||
|
|
||
|
|
||
|
#include "listctrl.h"
|
||
|
#include "assetmgr.h"
|
||
|
#include "refcount.h"
|
||
|
#include "mousemgr.h"
|
||
|
#include "ww3d.h"
|
||
|
#include "dialogmgr.h"
|
||
|
#include "dialogbase.h"
|
||
|
#include "stylemgr.h"
|
||
|
#include <commctrl.h>
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
// Local constants
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
const float PULSE_RATE = 2.0F;
|
||
|
const int ROW_SPACING = 4;
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// ListCtrlClass
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
ListCtrlClass::ListCtrlClass (void) :
|
||
|
CurrState (NORMAL),
|
||
|
ScrollPos (0),
|
||
|
HeaderRect (0, 0, 0, 0),
|
||
|
TextRect (0, 0, 0, 0),
|
||
|
CurrSel (-1),
|
||
|
RowBorderHeight (0),
|
||
|
PulsePercent (1.0F),
|
||
|
PulseDirection (1.0F),
|
||
|
IsScrollBarDisplayed (false),
|
||
|
LastPageTopEntryIndex (0),
|
||
|
IsSelectionAllowed(true),
|
||
|
IsNoSelectionAllowed(false),
|
||
|
IsMultipleSelection (false),
|
||
|
SortColumn (0),
|
||
|
SortType (SORT_NONE),
|
||
|
MinRowHeight (0)
|
||
|
{
|
||
|
//
|
||
|
// Configure each renderer
|
||
|
//
|
||
|
StyleMgrClass::Configure_Renderer (&ControlRenderer);
|
||
|
StyleMgrClass::Configure_Renderer (&UnderlineRenderer);
|
||
|
StyleMgrClass::Configure_Renderer (&HilightRenderer);
|
||
|
StyleMgrClass::Configure_Hilighter (&HilightRenderer);
|
||
|
|
||
|
//
|
||
|
// Set the font for each text renderer
|
||
|
//
|
||
|
StyleMgrClass::Assign_Font (&HeaderRenderer, StyleMgrClass::FONT_HEADER);
|
||
|
StyleMgrClass::Assign_Font (&TextRenderer, StyleMgrClass::FONT_LISTS);
|
||
|
TextRenderer.Set_Texture_Size_Hint (256);
|
||
|
|
||
|
//
|
||
|
// We don't want the scroll bar getting focus
|
||
|
//
|
||
|
ScrollBarCtrl.Set_Wants_Focus (false);
|
||
|
ScrollBarCtrl.Set_Advise_Sink (this);
|
||
|
ScrollBarCtrl.Set_Is_Embedded (true);
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// ~ListCtrlClass
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
ListCtrlClass::~ListCtrlClass (void)
|
||
|
{
|
||
|
Delete_All_Entries ();
|
||
|
|
||
|
if (Parent != NULL) {
|
||
|
Parent->Remove_Control (&ScrollBarCtrl);
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
void ListCtrlClass::Set_Tabstop(float stop)
|
||
|
{
|
||
|
TextRenderer.Set_Tabstop(stop);
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Create_Control_Renderer
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Create_Control_Renderer (void)
|
||
|
{
|
||
|
Render2DClass &renderer = ControlRenderer;
|
||
|
|
||
|
//
|
||
|
// Configure this renderer
|
||
|
//
|
||
|
renderer.Reset ();
|
||
|
renderer.Enable_Texturing (false);
|
||
|
|
||
|
//
|
||
|
// Determine which color to draw the outline in
|
||
|
//
|
||
|
int color = StyleMgrClass::Get_Line_Color ();
|
||
|
int bkcolor = StyleMgrClass::Get_Bk_Color ();
|
||
|
if (CurrState == DISABLED) {
|
||
|
color = StyleMgrClass::Get_Disabled_Line_Color ();
|
||
|
bkcolor = StyleMgrClass::Get_Disabled_Bk_Color ();
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Draw the outline
|
||
|
//
|
||
|
renderer.Add_Rect (Rect, 1.0F, color, bkcolor);
|
||
|
|
||
|
//
|
||
|
// Draw the selection bar if necessary
|
||
|
//
|
||
|
if (CurrSel != -1) {
|
||
|
|
||
|
//
|
||
|
// Calculate what percentage our pulse effect is
|
||
|
//
|
||
|
PulsePercent += PulseDirection * PULSE_RATE * (DialogMgrClass::Get_Frame_Time () / 1000.0F);
|
||
|
if (PulsePercent < 0 || PulsePercent > 1.0F) {
|
||
|
PulseDirection = -PulseDirection;
|
||
|
PulsePercent = WWMath::Clamp (PulsePercent, 0, 1);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If this control has the focus then apply our pulsating effect to
|
||
|
// the color of selection bar.
|
||
|
//
|
||
|
if (HasFocus) {
|
||
|
|
||
|
float red = ((color & 0x00FF0000) >> 16) / 256.0F;
|
||
|
float green = ((color & 0x0000FF00) >> 8) / 256.0F;
|
||
|
float blue = ((color & 0x000000FF)) / 256.0F;
|
||
|
|
||
|
float percent = (PulsePercent * 0.5F) + 0.5F;
|
||
|
color = VRGB_TO_INT32 (Vector3 (red * percent, green * percent, blue * percent));
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Get the hilight rect
|
||
|
//
|
||
|
RectClass sel_rect;
|
||
|
Get_Entry_Rect (CurrSel, sel_rect);
|
||
|
if (sel_rect.Top >= TextRect.Top && sel_rect.Bottom <= TextRect.Bottom) {
|
||
|
|
||
|
//
|
||
|
// Render the outline of the selection (if its on the screen)
|
||
|
//
|
||
|
renderer.Add_Outline (sel_rect, 1.0F, color);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Create_Text_Renderers
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Create_Text_Renderers (void)
|
||
|
{
|
||
|
//
|
||
|
// Configure the text renderers
|
||
|
//
|
||
|
HeaderRenderer.Reset ();
|
||
|
TextRenderer.Reset ();
|
||
|
HilightRenderer.Reset ();
|
||
|
UnderlineRenderer.Reset ();
|
||
|
IconMgr.Reset_Renderers ();
|
||
|
|
||
|
//
|
||
|
// Prepare the column header (if necessary)
|
||
|
//
|
||
|
if ((Style & LVS_NOCOLUMNHEADER) == 0) {
|
||
|
|
||
|
//
|
||
|
// Render each column header
|
||
|
//
|
||
|
int x_pos = HeaderRect.Left;
|
||
|
for (int index = 0; index < ColList.Count (); index ++) {
|
||
|
|
||
|
//
|
||
|
// Build a bounding rectangle for this column header
|
||
|
//
|
||
|
RectClass rect = HeaderRect;
|
||
|
rect.Left = x_pos;
|
||
|
rect.Right = x_pos + (ColList[index]->Get_Width () * HeaderRect.Width ());
|
||
|
|
||
|
//
|
||
|
// Let the last column extend to the edge
|
||
|
//
|
||
|
if (index == ColList.Count () - 1) {
|
||
|
rect.Right = HeaderRect.Right;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Get the color
|
||
|
//
|
||
|
int color = VRGB_TO_INT32 (ColList[index]->Get_Color ());
|
||
|
|
||
|
//
|
||
|
// Underline the header
|
||
|
//
|
||
|
Vector2 text_extent = HeaderRenderer.Get_Text_Extents (ColList[index]->Get_Name ());
|
||
|
float y_pos = (rect.Top + (rect.Height () / 2) + (text_extent.Y / 2)) + 2.0F;
|
||
|
UnderlineRenderer.Add_Line (Vector2 (rect.Left, y_pos),
|
||
|
Vector2 (rect.Left + text_extent.X, y_pos), 1.0F, color);
|
||
|
|
||
|
//
|
||
|
// Draw the sort designator
|
||
|
//
|
||
|
if (SortType != SORT_NONE && SortColumn == index) {
|
||
|
|
||
|
const float TRI_WIDTH = 8;
|
||
|
const float TRI_SPACE = 2;
|
||
|
|
||
|
float tri_size = TRI_WIDTH * StyleMgrClass::Get_Y_Scale ();
|
||
|
float tri_half_size = tri_size * 0.5F;
|
||
|
float tri_x_pos = rect.Left + text_extent.X + tri_size + (TRI_SPACE * StyleMgrClass::Get_Y_Scale ());
|
||
|
float tri_y_pos = HeaderRect.Center ().Y;
|
||
|
|
||
|
if (SortType == SORT_ASCENDING) {
|
||
|
|
||
|
UnderlineRenderer.Add_Line (Vector2 (tri_x_pos - tri_half_size, tri_y_pos + tri_half_size),
|
||
|
Vector2 (tri_x_pos + tri_half_size, tri_y_pos + tri_half_size), 1.0F, color);
|
||
|
|
||
|
UnderlineRenderer.Add_Line (Vector2 (tri_x_pos + tri_half_size, tri_y_pos + tri_half_size),
|
||
|
Vector2 (tri_x_pos, tri_y_pos - tri_half_size), 1.0F, color);
|
||
|
|
||
|
UnderlineRenderer.Add_Line (Vector2 (tri_x_pos, tri_y_pos - tri_half_size),
|
||
|
Vector2 (tri_x_pos - tri_half_size, tri_y_pos + tri_half_size), 1.0F, color);
|
||
|
|
||
|
} else {
|
||
|
|
||
|
UnderlineRenderer.Add_Line (Vector2 (tri_x_pos - tri_half_size, tri_y_pos - tri_half_size),
|
||
|
Vector2 (tri_x_pos + tri_half_size, tri_y_pos - tri_half_size), 1.0F, color);
|
||
|
|
||
|
UnderlineRenderer.Add_Line (Vector2 (tri_x_pos + tri_half_size, tri_y_pos - tri_half_size),
|
||
|
Vector2 (tri_x_pos, tri_y_pos + tri_half_size), 1.0F, color);
|
||
|
|
||
|
UnderlineRenderer.Add_Line (Vector2 (tri_x_pos, tri_y_pos + tri_half_size),
|
||
|
Vector2 (tri_x_pos - tri_half_size, tri_y_pos - tri_half_size), 1.0F, color);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Render the text
|
||
|
//
|
||
|
StyleMgrClass::Render_Text (ColList[index]->Get_Name (), &HeaderRenderer,
|
||
|
color, RGB_TO_INT32 (0, 0, 0), rect, true, true);
|
||
|
|
||
|
//
|
||
|
// Move on to the next column
|
||
|
//
|
||
|
x_pos = rect.Right;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Render the data by rows
|
||
|
//
|
||
|
float y_pos = TextRect.Top;
|
||
|
int row_count = Get_Entry_Count ();
|
||
|
for (int row_index = ScrollPos; row_index < row_count; row_index ++) {
|
||
|
|
||
|
//
|
||
|
// Get the height of this row
|
||
|
//
|
||
|
float row_height = RowInfoList[row_index]->Get_Height ();
|
||
|
|
||
|
//
|
||
|
// Don't render past the bottom of the control
|
||
|
//
|
||
|
if ((y_pos + row_height) >= TextRect.Bottom) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Render each column in this row
|
||
|
//
|
||
|
float x_pos = TextRect.Left;
|
||
|
for (int index = 0; index < ColList.Count (); index ++) {
|
||
|
|
||
|
//
|
||
|
// Determine how wide this column is
|
||
|
//
|
||
|
float col_width = (ColList[index]->Get_Width () * HeaderRect.Width ());
|
||
|
if (index == ColList.Count () - 1) {
|
||
|
col_width = TextRect.Right - x_pos;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Build a bounding rectangle for this entry
|
||
|
//
|
||
|
RectClass rect;
|
||
|
rect.Left = int(x_pos);
|
||
|
rect.Right = int(x_pos + col_width);
|
||
|
rect.Top = int(y_pos);
|
||
|
rect.Bottom = int(y_pos + row_height);
|
||
|
|
||
|
//
|
||
|
// Render the entry
|
||
|
//
|
||
|
Render_Entry (rect, index, row_index);
|
||
|
|
||
|
//
|
||
|
// Move on to the next column
|
||
|
//
|
||
|
x_pos = rect.Right;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Render a hilight on this row if necessary
|
||
|
//
|
||
|
if (Is_Entry_Selected (row_index)) {
|
||
|
RectClass row_rect = TextRect;
|
||
|
row_rect.Top = (int)max (TextRect.Top, (float)y_pos);
|
||
|
row_rect.Bottom = (int)min (TextRect.Bottom, (float)(y_pos + row_height));
|
||
|
StyleMgrClass::Render_Hilight (&HilightRenderer, row_rect);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Move down to the next row
|
||
|
//
|
||
|
y_pos += row_height;
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Render_Entry
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Render_Entry (const RectClass &clip_rect, int col_index, int row_index)
|
||
|
{
|
||
|
RectClass rect = clip_rect;
|
||
|
|
||
|
//
|
||
|
// Render the icons
|
||
|
//
|
||
|
int icon_count = ColList[col_index]->Get_Icon_Count (row_index);
|
||
|
for (int index = 0; index < icon_count; index ++) {
|
||
|
|
||
|
//
|
||
|
// Render this icon
|
||
|
//
|
||
|
const char *icon_name = ColList[col_index]->Get_Icon (row_index, index);
|
||
|
IconMgr.Render_Icon (rect, icon_name);
|
||
|
|
||
|
//
|
||
|
// Move the rect by the width of the icon
|
||
|
//
|
||
|
rect.Left += IconMgr.Get_Icon_Width ();
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Get the text
|
||
|
//
|
||
|
const WCHAR *text = ColList[col_index]->Get_Entry_Text (row_index);
|
||
|
int text_color = VRGB_TO_INT32 (ColList[col_index]->Get_Entry_Color (row_index));
|
||
|
|
||
|
//
|
||
|
// Render the text
|
||
|
//
|
||
|
StyleMgrClass::Render_Wrapped_Text (text, &TextRenderer, text_color,
|
||
|
RGB_TO_INT32 (0, 0, 0), rect, true, true);
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// On_Set_Cursor
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::On_Set_Cursor (const Vector2 &mouse_pos)
|
||
|
{
|
||
|
if (IsSelectionAllowed) {
|
||
|
//
|
||
|
// Change the mouse cursor
|
||
|
//
|
||
|
MouseMgrClass::Set_Cursor (MouseMgrClass::CURSOR_ACTION);
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Set_Sort_Designator
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Set_Sort_Designator (int col_index, SORT_TYPE type)
|
||
|
{
|
||
|
SortColumn = col_index;
|
||
|
SortType = type;
|
||
|
|
||
|
Set_Dirty ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Sort_Alphabetically
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Sort_Alphabetically (int col_index, SORT_TYPE type)
|
||
|
{
|
||
|
//
|
||
|
// Sort the entries
|
||
|
//
|
||
|
Sort (Default_Sort_Callback, MAKELONG ((WORD)col_index, (WORD)type));
|
||
|
|
||
|
//
|
||
|
// Update the sort marker
|
||
|
//
|
||
|
Set_Sort_Designator (col_index, type);
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Default_Sort_Callback
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
int CALLBACK
|
||
|
ListCtrlClass::Default_Sort_Callback (ListCtrlClass *list_ctrl, int item_index1, int item_index2, uint32 user_param)
|
||
|
{
|
||
|
//
|
||
|
// Get the sorting params
|
||
|
//
|
||
|
int sort_col_index = LOWORD (user_param);
|
||
|
SORT_TYPE sort_type = (SORT_TYPE)HIWORD (user_param);
|
||
|
|
||
|
//
|
||
|
// Sort by name
|
||
|
//
|
||
|
const WCHAR *name1 = list_ctrl->Get_Entry_Text (item_index1, sort_col_index);
|
||
|
const WCHAR *name2 = list_ctrl->Get_Entry_Text (item_index2, sort_col_index);
|
||
|
|
||
|
int retval = ::CompareStringW (LOCALE_USER_DEFAULT, NORM_IGNORECASE, name1, -1, name2, -1);
|
||
|
|
||
|
if (retval == 0) {
|
||
|
retval = wcsicmp(name1, name2);
|
||
|
} else {
|
||
|
retval -= 2;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Invert the return value if we are sorting descendingly
|
||
|
//
|
||
|
if (sort_type == SORT_DESCENDING) {
|
||
|
retval = -retval;
|
||
|
}
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Update_Client_Rect
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Update_Client_Rect (void)
|
||
|
{
|
||
|
Vector2 header_size = HeaderRenderer.Get_Text_Extents (L"W");
|
||
|
|
||
|
//
|
||
|
// Set the client area
|
||
|
//
|
||
|
ClientRect = Rect;
|
||
|
ClientRect.Inflate (Vector2 (-header_size.X, -1));
|
||
|
HeaderRect = ClientRect;
|
||
|
TextRect = ClientRect;
|
||
|
|
||
|
//
|
||
|
// Calculate what the header rectangle should be
|
||
|
//
|
||
|
if ((Style & LVS_NOCOLUMNHEADER) == 0) {
|
||
|
float char_height = header_size.Y;
|
||
|
HeaderRect.Bottom = HeaderRect.Top + (char_height * 3);
|
||
|
} else {
|
||
|
HeaderRect.Bottom = HeaderRect.Top;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Move the text rectangle down
|
||
|
//
|
||
|
TextRect.Top = HeaderRect.Bottom;
|
||
|
|
||
|
//float scale_y = Render2DClass::Get_Screen_Resolution().Height () / 600;
|
||
|
|
||
|
//
|
||
|
// Choose an arbitrary width, the scroll bar
|
||
|
// will snap to the only width it supports
|
||
|
//
|
||
|
float width = 10;
|
||
|
|
||
|
//
|
||
|
// Calculate the scroll bar's rectangle
|
||
|
//
|
||
|
RectClass scroll_rect;
|
||
|
scroll_rect.Left = Rect.Right - width;
|
||
|
scroll_rect.Top = Rect.Top;
|
||
|
scroll_rect.Right = Rect.Right;
|
||
|
scroll_rect.Bottom = Rect.Bottom;
|
||
|
|
||
|
//
|
||
|
// Size the scroll bar
|
||
|
//
|
||
|
ScrollBarCtrl.Set_Window_Rect (scroll_rect);
|
||
|
|
||
|
Set_Dirty ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Find_Top_Of_Page
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
int
|
||
|
ListCtrlClass::Find_Top_Of_Page (int bottom_index)
|
||
|
{
|
||
|
int retval = 0;
|
||
|
//int count = RowInfoList.Count ();
|
||
|
float y_pos = TextRect.Bottom;
|
||
|
|
||
|
//
|
||
|
// Scan the entries until we've found the first one
|
||
|
// to extend off the top of the page
|
||
|
//
|
||
|
for (int index = bottom_index; index >= 0; index --) {
|
||
|
y_pos -= RowInfoList[index]->Get_Height ();
|
||
|
|
||
|
//
|
||
|
// If we've gone off the page, then back off one entry
|
||
|
//
|
||
|
if (y_pos < TextRect.Top) {
|
||
|
retval = (index + 1);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Find_End_Of_Page
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
int
|
||
|
ListCtrlClass::Find_End_Of_Page (void)
|
||
|
{
|
||
|
int count = RowInfoList.Count ();
|
||
|
int retval = count;
|
||
|
float y_pos = TextRect.Top;
|
||
|
|
||
|
//
|
||
|
// Scan the entries until we've found the first one
|
||
|
// to extend off the page
|
||
|
//
|
||
|
for (int index = ScrollPos; index < count; index ++) {
|
||
|
y_pos += RowInfoList[index]->Get_Height ();
|
||
|
|
||
|
//
|
||
|
// If we've gone off the page, then back off one entry
|
||
|
//
|
||
|
if (y_pos >= TextRect.Bottom) {
|
||
|
retval = (index - 1);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Find_Last_Page_Top_Entry
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
int
|
||
|
ListCtrlClass::Find_Last_Page_Top_Entry (void)
|
||
|
{
|
||
|
int retval = 0;
|
||
|
|
||
|
float y_pos = TextRect.Bottom;
|
||
|
int count = RowInfoList.Count ();
|
||
|
|
||
|
//
|
||
|
// Scan backwards from the bottom entry until we've
|
||
|
// found one that extends off the top of the page.
|
||
|
//
|
||
|
for (int index = count - 1; index >= 0; index --) {
|
||
|
y_pos -= RowInfoList[index]->Get_Height ();
|
||
|
if (y_pos <= TextRect.Top) {
|
||
|
retval = index + 1;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Update_Scroll_Bar_Visibility
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Update_Scroll_Bar_Visibility (void)
|
||
|
{
|
||
|
LastPageTopEntryIndex = Find_Last_Page_Top_Entry ();
|
||
|
|
||
|
//
|
||
|
// Configure the scroll-bar's ranges
|
||
|
//
|
||
|
ScrollBarCtrl.Set_Page_Size (0);
|
||
|
ScrollBarCtrl.Set_Range (0, LastPageTopEntryIndex);
|
||
|
|
||
|
//
|
||
|
// Determine if we have more entries then we can
|
||
|
// display on one page
|
||
|
//
|
||
|
bool needs_scrollbar = false;
|
||
|
float y_pos = TextRect.Top;
|
||
|
for (int index = 0; index < RowInfoList.Count (); index ++) {
|
||
|
y_pos += RowInfoList[index]->Get_Height ();
|
||
|
if (y_pos >= TextRect.Bottom) {
|
||
|
needs_scrollbar = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
float new_right = 0;
|
||
|
|
||
|
//
|
||
|
// Do we need to show a scroll bar?
|
||
|
//
|
||
|
bool was_scrollbar_displayed = IsScrollBarDisplayed;
|
||
|
if (needs_scrollbar) {
|
||
|
new_right = ScrollBarCtrl.Get_Window_Rect ().Left;
|
||
|
IsScrollBarDisplayed = true;
|
||
|
} else if (Parent != NULL) {
|
||
|
new_right = ScrollBarCtrl.Get_Window_Rect ().Right;
|
||
|
IsScrollBarDisplayed = false;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Reset our window size (as necessary)
|
||
|
//
|
||
|
Rect.Right = new_right;
|
||
|
ClientRect.Right = Rect.Right - (ClientRect.Left - Rect.Left);
|
||
|
TextRect.Right = ClientRect.Right;
|
||
|
HeaderRect.Right = ClientRect.Right;
|
||
|
|
||
|
//
|
||
|
// Update the row heights of each entry if the scroll bar
|
||
|
// visibility changed
|
||
|
//
|
||
|
if (was_scrollbar_displayed != IsScrollBarDisplayed) {
|
||
|
for (index = 0; index < RowInfoList.Count (); index ++) {
|
||
|
Update_Row_Height (index);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Update the last page top entry
|
||
|
//
|
||
|
LastPageTopEntryIndex = Find_Last_Page_Top_Entry ();
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Configure the scroll-bar's ranges
|
||
|
//
|
||
|
ScrollBarCtrl.Set_Page_Size (0);
|
||
|
ScrollBarCtrl.Set_Range (0, LastPageTopEntryIndex);
|
||
|
|
||
|
//
|
||
|
// Configure the scroll bar's position
|
||
|
//
|
||
|
if (ScrollBarCtrl.Get_Pos () > LastPageTopEntryIndex) {
|
||
|
int new_pos = max (LastPageTopEntryIndex, 0);
|
||
|
ScrollBarCtrl.Set_Pos (new_pos);
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Render
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Render (void)
|
||
|
{
|
||
|
//
|
||
|
// Recreate the renderers (if necessary)
|
||
|
//
|
||
|
if (IsDirty) {
|
||
|
Update_Scroll_Bar_Visibility ();
|
||
|
Create_Text_Renderers ();
|
||
|
}
|
||
|
|
||
|
if (IsScrollBarDisplayed) {
|
||
|
Parent->Add_Control (&ScrollBarCtrl);
|
||
|
} else {
|
||
|
Parent->Remove_Control (&ScrollBarCtrl);
|
||
|
}
|
||
|
|
||
|
if (HasFocus || IsDirty) {
|
||
|
Create_Control_Renderer ();
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Render the background and text
|
||
|
//
|
||
|
IconMgr.Render_Icons ();
|
||
|
TextRenderer.Render ();
|
||
|
HilightRenderer.Render ();
|
||
|
UnderlineRenderer.Render ();
|
||
|
HeaderRenderer.Render ();
|
||
|
ControlRenderer.Render ();
|
||
|
|
||
|
DialogControlClass::Render ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Get_First_Selected
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
int
|
||
|
ListCtrlClass::Get_First_Selected (void) const
|
||
|
{
|
||
|
int retval = -1;
|
||
|
|
||
|
//
|
||
|
// Loop over all the entries in the list control
|
||
|
//
|
||
|
int entry_count = Get_Entry_Count ();
|
||
|
for (int index = 0; index < entry_count; index ++) {
|
||
|
|
||
|
//
|
||
|
// Is this entry selected?
|
||
|
//
|
||
|
if (RowInfoList[index]->Is_Selected ()) {
|
||
|
retval = index;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Get_Next_Selected
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
int
|
||
|
ListCtrlClass::Get_Next_Selected (int index) const
|
||
|
{
|
||
|
int retval = -1;
|
||
|
|
||
|
//
|
||
|
// Loop over all the entries in the list control
|
||
|
//
|
||
|
int entry_count = Get_Entry_Count ();
|
||
|
for (index ++; index < entry_count; index ++) {
|
||
|
|
||
|
//
|
||
|
// Is this entry selected?
|
||
|
//
|
||
|
if (RowInfoList[index]->Is_Selected ()) {
|
||
|
retval = index;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Select_All
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Select_All (bool select)
|
||
|
{
|
||
|
//
|
||
|
// Loop over all the entries in the list control
|
||
|
//
|
||
|
int entry_count = Get_Entry_Count ();
|
||
|
for (int index = 0; index < entry_count; index ++) {
|
||
|
RowInfoList[index]->Select (select);
|
||
|
}
|
||
|
|
||
|
Set_Dirty();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Toggle_Entry_Selection
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
bool
|
||
|
ListCtrlClass::Toggle_Entry_Selection (int index)
|
||
|
{
|
||
|
if (index < 0 || index >= Get_Entry_Count ()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Toggle the selection of this entry
|
||
|
//
|
||
|
bool selected = RowInfoList[index]->Is_Selected ();
|
||
|
RowInfoList[index]->Select (!selected);
|
||
|
Set_Dirty ();
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// On_LButton_DblClk
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::On_LButton_DblClk (const Vector2 &mouse_pos)
|
||
|
{
|
||
|
int sel_entry = Entry_From_Pos (mouse_pos);
|
||
|
if (sel_entry == CurrSel) {
|
||
|
|
||
|
//
|
||
|
// Notify any advise sinks
|
||
|
//
|
||
|
ADVISE_NOTIFY (On_ListCtrl_DblClk (this, Get_ID (), sel_entry));
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// On_LButton_Down
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::On_LButton_Down (const Vector2 &mouse_pos)
|
||
|
{
|
||
|
if (mouse_pos.Y <= HeaderRect.Bottom) {
|
||
|
|
||
|
//
|
||
|
// Find out which column was clicked
|
||
|
//
|
||
|
int col_index = Col_From_Pos (mouse_pos);
|
||
|
|
||
|
if (-1 != col_index) {
|
||
|
//
|
||
|
// Notify any advise sinks
|
||
|
//
|
||
|
ADVISE_NOTIFY (On_ListCtrl_Column_Click (this, Get_ID (), col_index));
|
||
|
}
|
||
|
} else {
|
||
|
|
||
|
//
|
||
|
// Change the hilighted entry to reflect the mouse click
|
||
|
//
|
||
|
int new_sel = Entry_From_Pos (mouse_pos);
|
||
|
if (new_sel != -1) {
|
||
|
Set_Sel (new_sel, true);
|
||
|
} else if (IsNoSelectionAllowed) {
|
||
|
Set_Sel(-1, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// On_LButton_Up
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::On_LButton_Up (const Vector2 &mouse_pos)
|
||
|
{
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// On_Mouse_Move
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::On_Mouse_Move (const Vector2 &mouse_pos)
|
||
|
{
|
||
|
int sel_entry = Entry_From_Pos(mouse_pos);
|
||
|
ADVISE_NOTIFY(On_ListCtrl_Mouse_Over(this, Get_ID(), sel_entry));
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// On_Set_Focus
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::On_Set_Focus (void)
|
||
|
{
|
||
|
Set_Dirty ();
|
||
|
|
||
|
DialogControlClass::On_Set_Focus ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// On_Kill_Focus
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::On_Kill_Focus (DialogControlClass *focus)
|
||
|
{
|
||
|
Set_Dirty ();
|
||
|
|
||
|
DialogControlClass::On_Kill_Focus (focus);
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// On_Key_Down
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
bool
|
||
|
ListCtrlClass::On_Key_Down (uint32 key_id, uint32 key_data)
|
||
|
{
|
||
|
bool handled = true;
|
||
|
|
||
|
switch (key_id)
|
||
|
{
|
||
|
case VK_UP:
|
||
|
case VK_LEFT:
|
||
|
Set_Sel (CurrSel - 1, true);
|
||
|
break;
|
||
|
|
||
|
case VK_DOWN:
|
||
|
case VK_RIGHT:
|
||
|
Set_Sel (CurrSel + 1, true);
|
||
|
break;
|
||
|
|
||
|
case VK_PRIOR:
|
||
|
if (CurrSel == ScrollPos) {
|
||
|
Scroll_Page (-1);
|
||
|
} else {
|
||
|
Set_Sel (ScrollPos, true);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case VK_NEXT:
|
||
|
{
|
||
|
int end_of_page = Find_End_Of_Page ();
|
||
|
|
||
|
//
|
||
|
// If we are at the end of the page, then
|
||
|
// scroll one page, otherwise snap to the
|
||
|
// end of the page
|
||
|
//
|
||
|
if (CurrSel == end_of_page) {
|
||
|
Scroll_Page (1);
|
||
|
} else {
|
||
|
Set_Sel (end_of_page, true);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case VK_HOME:
|
||
|
Set_Sel (0, true);
|
||
|
break;
|
||
|
|
||
|
case VK_END:
|
||
|
Set_Sel (Get_Entry_Count () - 1, true);
|
||
|
break;
|
||
|
|
||
|
case VK_SPACE:
|
||
|
case VK_RETURN:
|
||
|
ADVISE_NOTIFY(On_ListCtrl_DblClk(this, Get_ID(), Get_Curr_Sel()));
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
handled = false;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return handled;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Sort
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Sort (LISTCTRL_SORT_CALLBACK sort_callback, uint32 user_param)
|
||
|
{
|
||
|
//
|
||
|
// Quick sort the data
|
||
|
//
|
||
|
int entry_count = Get_Entry_Count ();
|
||
|
if (entry_count > 1) {
|
||
|
Quick_Sort (0, entry_count - 1, sort_callback, user_param);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Force a repaint
|
||
|
//
|
||
|
Set_Dirty ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Quick_Sort
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Quick_Sort
|
||
|
(
|
||
|
int start_index,
|
||
|
int end_index,
|
||
|
LISTCTRL_SORT_CALLBACK sort_callback,
|
||
|
uint32 user_param
|
||
|
)
|
||
|
{
|
||
|
//
|
||
|
// Setup a handy macro for swapping all columns of a particular row
|
||
|
//
|
||
|
#define QSORT_ROW_SWAP(index1, index2) \
|
||
|
{ \
|
||
|
if (CurrSel == index1) { CurrSel = index2; } \
|
||
|
else if (CurrSel == index2) { CurrSel = index1; } \
|
||
|
ListRowClass *temp = RowInfoList[index1]; \
|
||
|
RowInfoList[index1] = RowInfoList[index2]; \
|
||
|
RowInfoList[index2] = temp; \
|
||
|
for (int col_index = 0; col_index < ColList.Count (); col_index ++) { \
|
||
|
ColList[col_index]->Swap_Entries (index1, index2); \
|
||
|
} \
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Determine our ranges
|
||
|
//
|
||
|
int pivot_index = start_index;
|
||
|
int min_index = min (start_index + 1, end_index);
|
||
|
int max_index = end_index;
|
||
|
|
||
|
//
|
||
|
// If the range is large enough, try to pick
|
||
|
// a *good* pivot point.
|
||
|
//
|
||
|
if ((end_index - start_index) > 10) {
|
||
|
int middle_index = start_index + ((end_index - start_index) / 2);
|
||
|
|
||
|
//
|
||
|
// Put the smaller of the middle and end indices into the middle slot
|
||
|
//
|
||
|
if ((*sort_callback) (this, end_index, middle_index, user_param) < 0) {
|
||
|
QSORT_ROW_SWAP (end_index, middle_index);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Put the larger of the middle and start indices into the start slot
|
||
|
//
|
||
|
if ((*sort_callback) (this, middle_index, start_index, user_param) > 0) {
|
||
|
QSORT_ROW_SWAP (middle_index, start_index);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Put the smaller of the start and end indices into the start slot
|
||
|
//
|
||
|
if ((*sort_callback) (this, end_index, start_index, user_param) < 0) {
|
||
|
QSORT_ROW_SWAP (end_index, start_index);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Put all the other entries in our range on either the lower or upper
|
||
|
// side of the pivot entry based on their relative sort value.
|
||
|
//
|
||
|
bool keep_going = true;
|
||
|
do
|
||
|
{
|
||
|
//
|
||
|
// Find the first entry that is "greater-than" the pivot
|
||
|
//
|
||
|
for (; min_index <= max_index; min_index ++) {
|
||
|
|
||
|
//
|
||
|
// Is this entry "greater-than" the pivot?
|
||
|
//
|
||
|
if ((*sort_callback) (this, min_index, start_index, user_param) > 0) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// The last index that is "less-than" the pivot entry will
|
||
|
// be the pivot's new index.
|
||
|
//
|
||
|
pivot_index = min_index;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Find the last entry that is "less-than" the pivot
|
||
|
//
|
||
|
for (; max_index >= min_index; max_index --) {
|
||
|
|
||
|
//
|
||
|
// Is this entry "less-than" the pivot?
|
||
|
//
|
||
|
if ((*sort_callback) (this, max_index, start_index, user_param) < 0) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Have we processed all the entries in our range?
|
||
|
//
|
||
|
if (min_index >= max_index) {
|
||
|
keep_going = false;
|
||
|
} else {
|
||
|
|
||
|
//
|
||
|
// Swap the entries
|
||
|
//
|
||
|
QSORT_ROW_SWAP (min_index, max_index);
|
||
|
|
||
|
//
|
||
|
// For efficiency's sake, skip over the entries we just swapped.
|
||
|
//
|
||
|
min_index ++;
|
||
|
max_index --;
|
||
|
pivot_index ++;
|
||
|
}
|
||
|
|
||
|
} while (keep_going);
|
||
|
|
||
|
//
|
||
|
// Insert the pivot point into its "sorted" position in the array (if necessary)
|
||
|
//
|
||
|
if (pivot_index > start_index) {
|
||
|
QSORT_ROW_SWAP (start_index, pivot_index);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Calculate the region below the pivot
|
||
|
//
|
||
|
int lower_range_min = start_index;
|
||
|
int lower_range_max = pivot_index - 1;
|
||
|
|
||
|
//
|
||
|
// Recurse (if necessary) into the region below the pivot
|
||
|
//
|
||
|
if (lower_range_max < end_index && lower_range_min < lower_range_max) {
|
||
|
Quick_Sort (lower_range_min, lower_range_max, sort_callback, user_param);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Calculate the region above the pivot
|
||
|
//
|
||
|
int upper_range_min = pivot_index + 1;
|
||
|
int upper_range_max = end_index;
|
||
|
|
||
|
//
|
||
|
// Recurse (if necessary) into the region above the pivot
|
||
|
//
|
||
|
if (upper_range_min > start_index && upper_range_min < upper_range_max) {
|
||
|
Quick_Sort (upper_range_min, upper_range_max, sort_callback, user_param);
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// On_Create
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::On_Create (void)
|
||
|
{
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// On_Destroy
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::On_Destroy (void)
|
||
|
{
|
||
|
Delete_All_Entries ();
|
||
|
Delete_All_Columns ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Auto_Size_Columns_Include_Contents
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Auto_Size_Columns_Include_Contents (float col_spacing)
|
||
|
{
|
||
|
float total_width = HeaderRect.Width ();
|
||
|
|
||
|
//
|
||
|
// Loop over all the columns
|
||
|
//
|
||
|
for (int index = 0; index < ColList.Count (); index ++) {
|
||
|
float max_width = HeaderRenderer.Get_Text_Extents (ColList[index]->Get_Name ()).X;
|
||
|
|
||
|
//
|
||
|
// Now, take into consideration the largest entry we've got
|
||
|
//
|
||
|
int count = ColList[index]->Get_Entry_Count ();
|
||
|
for (int row = 0; row < count; row ++) {
|
||
|
float width = TextRenderer.Get_Text_Extents (ColList[index]->Get_Entry_Text (row)).X;
|
||
|
max_width = max (width, max_width);
|
||
|
|
||
|
//
|
||
|
// Add the width of all the icons to this row's total
|
||
|
//
|
||
|
int icon_count = ColList[index]->Get_Icon_Count (row);
|
||
|
for (int icon_index = 0; icon_index < icon_count; icon_index ++) {
|
||
|
max_width += IconMgr.Get_Icon_Width ();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Auto-size this column
|
||
|
//
|
||
|
float percent = (max_width + col_spacing) / total_width;
|
||
|
ColList[index]->Set_Width (percent);
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Auto_Size_Columns
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Auto_Size_Columns (float col_spacing)
|
||
|
{
|
||
|
float max_width = HeaderRect.Width ();
|
||
|
|
||
|
for (int index = 0; index < ColList.Count (); index ++) {
|
||
|
|
||
|
//
|
||
|
// Auto-size this column
|
||
|
//
|
||
|
float width = HeaderRenderer.Get_Text_Extents (ColList[index]->Get_Name ()).X;
|
||
|
float percent = (width + col_spacing) / max_width;
|
||
|
ColList[index]->Set_Width (percent);
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Add_Icon
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Add_Icon (int index, int col_index, const char *texture_name)
|
||
|
{
|
||
|
if (col_index < 0 || col_index >= ColList.Count ()) {
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Add an icon to this entry
|
||
|
//
|
||
|
IconMgr.Add_Icon (texture_name);
|
||
|
ColList[col_index]->Add_Icon (index, texture_name);
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Reset_Icons
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Reset_Icons (int index, int col_index)
|
||
|
{
|
||
|
if (col_index < 0 || col_index >= ColList.Count ()) {
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Reset all the icons in this entry
|
||
|
//
|
||
|
ColList[col_index]->Reset_Icons (index);
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Add_Column
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Add_Column (const WCHAR *column_name, float width, const Vector3 &color)
|
||
|
{
|
||
|
//
|
||
|
// Create a new column and add it to the list
|
||
|
//
|
||
|
ListColumnClass *column = new ListColumnClass;
|
||
|
column->Set_Name (column_name);
|
||
|
column->Set_Width (width);
|
||
|
column->Set_Color (color);
|
||
|
ColList.Add (column);
|
||
|
Set_Dirty ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Set_Column_Color
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Set_Column_Color (int col_index, const Vector3 &color)
|
||
|
{
|
||
|
if (col_index < 0 || col_index >= ColList.Count ()) {
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
ColList[col_index]->Set_Color (color);
|
||
|
Set_Dirty ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Remove_Column
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
bool
|
||
|
ListCtrlClass::Remove_Column (int index)
|
||
|
{
|
||
|
if (index < 0 || index >= ColList.Count ()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Free the column
|
||
|
//
|
||
|
ListColumnClass *column = ColList[index];
|
||
|
delete column;
|
||
|
|
||
|
//
|
||
|
// Remove the column from the list
|
||
|
//
|
||
|
ColList.Delete (index);
|
||
|
Set_Dirty ();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Delete_All_Columns
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Delete_All_Columns (void)
|
||
|
{
|
||
|
for (int index = 0; index < ColList.Count (); index ++) {
|
||
|
ListColumnClass *column = ColList[index];
|
||
|
delete column;
|
||
|
}
|
||
|
|
||
|
ColList.Delete_All ();
|
||
|
Set_Dirty ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Get_Column_Count
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
int
|
||
|
ListCtrlClass::Get_Column_Count (void) const
|
||
|
{
|
||
|
return ColList.Count ();
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Get_Entry_Count
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
int
|
||
|
ListCtrlClass::Get_Entry_Count (void) const
|
||
|
{
|
||
|
return RowInfoList.Count ();
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Delete_Entry
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
bool
|
||
|
ListCtrlClass::Delete_Entry (int index)
|
||
|
{
|
||
|
bool retval = true;
|
||
|
|
||
|
//
|
||
|
// Nofity the advise sinks that we are deleting this entry
|
||
|
//
|
||
|
ADVISE_NOTIFY (On_ListCtrl_Delete_Entry (this, Get_ID (), index));
|
||
|
|
||
|
//
|
||
|
// Remove this row from our data structures
|
||
|
//
|
||
|
if (index >= 0 && index < RowInfoList.Count ()) {
|
||
|
delete RowInfoList[index];
|
||
|
RowInfoList.Delete (index);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Remove this entry from each column
|
||
|
//
|
||
|
for (int col_index = 0; col_index < ColList.Count (); col_index ++) {
|
||
|
retval &= ColList[col_index]->Delete_Entry (index);
|
||
|
}
|
||
|
|
||
|
// When an entry that is before the current selection is deleted then
|
||
|
// we need to adjust the current selection to reflect that.
|
||
|
if (index < CurrSel) {
|
||
|
--CurrSel;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Update the current selection indicator (if necessary)
|
||
|
//
|
||
|
int entry_count = Get_Entry_Count ();
|
||
|
if (CurrSel >= entry_count) {
|
||
|
CurrSel = entry_count - 1;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Hilight the new current selection (if necessary)
|
||
|
//
|
||
|
if (CurrSel >= 0 && CurrSel <= RowInfoList.Count ()) {
|
||
|
RowInfoList[CurrSel]->Select ((IsMultipleSelection == false));
|
||
|
}
|
||
|
|
||
|
Set_Dirty ();
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Find_Entry
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
int ListCtrlClass::Find_Entry(int col_index, const WCHAR* text)
|
||
|
{
|
||
|
int count = ColList.Count();
|
||
|
|
||
|
if (col_index >= 0 && col_index < count) {
|
||
|
ListColumnClass* list = ColList[col_index];
|
||
|
count = list->Get_Entry_Count();
|
||
|
|
||
|
for (int index = 0; index < count; index++) {
|
||
|
const WCHAR* entryText = list->Get_Entry_Text(index);
|
||
|
|
||
|
if (wcscmp(entryText, text) == 0) {
|
||
|
return index;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Insert_Entry
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
int
|
||
|
ListCtrlClass::Insert_Entry (int index, const WCHAR *text)
|
||
|
{
|
||
|
if (ColList.Count () <= 0) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Insert a blank row in our data structure
|
||
|
//
|
||
|
if (index < RowInfoList.Count ()) {
|
||
|
RowInfoList.Insert (index + 1, new ListRowClass);
|
||
|
} else {
|
||
|
RowInfoList.Add (new ListRowClass);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Insert a new entry in the first column with the given text
|
||
|
//
|
||
|
index = ColList[0]->Insert_Entry (index, text);
|
||
|
|
||
|
//
|
||
|
// Use the default text color for each new entry
|
||
|
//
|
||
|
int color = StyleMgrClass::Get_Text_Color ();
|
||
|
float red = ((color & 0x00FF0000) >> 16) / 256.0F;
|
||
|
float green = ((color & 0x0000FF00) >> 8) / 256.0F;
|
||
|
float blue = ((color & 0x000000FF)) / 256.0F;
|
||
|
Vector3 new_color (red, green, blue);
|
||
|
|
||
|
ColList[0]->Set_Entry_Color (index, new_color);
|
||
|
|
||
|
//
|
||
|
// Add blank entries to all the other columns
|
||
|
//
|
||
|
for (int col_index = 1; col_index < ColList.Count (); col_index ++) {
|
||
|
ListColumnClass *column = ColList[col_index];
|
||
|
column->Insert_Entry (index, L"");
|
||
|
column->Set_Entry_Color (index, new_color);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Update the cached row height for this entry...
|
||
|
//
|
||
|
Update_Row_Height (index);
|
||
|
|
||
|
//
|
||
|
// Update the last page top entry
|
||
|
//
|
||
|
LastPageTopEntryIndex = Find_Last_Page_Top_Entry ();
|
||
|
Set_Dirty ();
|
||
|
return index;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Update_Row_Height
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Update_Row_Height (int row_index)
|
||
|
{
|
||
|
int border_height = (ROW_SPACING * StyleMgrClass::Get_Y_Scale ());
|
||
|
float height = (TextRenderer.Get_Text_Extents (L"W").Y + border_height);
|
||
|
|
||
|
//
|
||
|
// Render each column in this row
|
||
|
//
|
||
|
float x_pos = TextRect.Left;
|
||
|
for (int index = 0; index < ColList.Count (); index ++) {
|
||
|
|
||
|
//
|
||
|
// Determine how wide this column is
|
||
|
//
|
||
|
float col_width = (ColList[index]->Get_Width () * HeaderRect.Width ());
|
||
|
if (index == ColList.Count () - 1) {
|
||
|
col_width = TextRect.Right - x_pos;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Set the wrapping width
|
||
|
//
|
||
|
TextRenderer.Set_Wrapping_Width (col_width);
|
||
|
|
||
|
//
|
||
|
// Calculate the height of this text
|
||
|
//
|
||
|
const WCHAR *text = ColList[index]->Get_Entry_Text (row_index);
|
||
|
Vector2 extents = TextRenderer.Get_Formatted_Text_Extents (text);
|
||
|
height = max (height, extents.Y + border_height);
|
||
|
|
||
|
//
|
||
|
// Increment the x-position
|
||
|
//
|
||
|
x_pos += col_width;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Make sure the row is AT LEST MinRowHeight units high
|
||
|
//
|
||
|
float min_height = MinRowHeight * StyleMgrClass::Get_Y_Scale ();
|
||
|
height = max (min_height, height);
|
||
|
|
||
|
//
|
||
|
// Store the row height
|
||
|
//
|
||
|
RowInfoList[row_index]->Set_Height (height);
|
||
|
|
||
|
TextRenderer.Set_Wrapping_Width (0);
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Set_Icon_Size
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Set_Icon_Size (float width, float height)
|
||
|
{
|
||
|
//
|
||
|
// Pass these values on the icon manager
|
||
|
//
|
||
|
IconMgr.Set_Icon_Width (width);
|
||
|
IconMgr.Set_Icon_Height (height);
|
||
|
Set_Dirty ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Set_Min_Row_Height
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Set_Min_Row_Height (int height)
|
||
|
{
|
||
|
MinRowHeight = height;
|
||
|
|
||
|
//
|
||
|
// Update each row using this new height information
|
||
|
//
|
||
|
for (int index = 0; index < RowInfoList.Count (); index ++) {
|
||
|
Update_Row_Height (index);
|
||
|
}
|
||
|
|
||
|
Set_Dirty ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Select_Entry
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
bool
|
||
|
ListCtrlClass::Select_Entry (int index, bool onoff)
|
||
|
{
|
||
|
if (index < 0 || index >= Get_Entry_Count ()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Set the selection state of the row
|
||
|
//
|
||
|
RowInfoList[index]->Select (onoff);
|
||
|
Set_Dirty ();
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Is_Entry_Selected
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
bool
|
||
|
ListCtrlClass::Is_Entry_Selected (int index)
|
||
|
{
|
||
|
bool retval = false;
|
||
|
if (index >= 0 && index < Get_Entry_Count ()) {
|
||
|
|
||
|
//
|
||
|
// Get the selection state of the row
|
||
|
//
|
||
|
retval = RowInfoList[index]->Is_Selected ();
|
||
|
Set_Dirty ();
|
||
|
}
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Set_Entry_Text
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
bool
|
||
|
ListCtrlClass::Set_Entry_Text (int index, int col_index, const WCHAR *text)
|
||
|
{
|
||
|
if (col_index < 0 || col_index >= ColList.Count ()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Change the text entry in this cell
|
||
|
//
|
||
|
ColList[col_index]->Set_Entry_Text (index, text);
|
||
|
|
||
|
//
|
||
|
// Update the cached row height for this entry...
|
||
|
//
|
||
|
Update_Row_Height (index);
|
||
|
|
||
|
//
|
||
|
// Update the last page top entry
|
||
|
//
|
||
|
LastPageTopEntryIndex = Find_Last_Page_Top_Entry ();
|
||
|
Set_Dirty ();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Set_Entry_Int
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
bool
|
||
|
ListCtrlClass::Set_Entry_Int (int index, int col_index, int value)
|
||
|
{
|
||
|
if (col_index < 0 || col_index >= ColList.Count ()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Convert the integer to a string
|
||
|
//
|
||
|
WideStringClass number_str;
|
||
|
number_str.Format (L"%d", value);
|
||
|
|
||
|
//
|
||
|
// Change the text entry in this cell
|
||
|
//
|
||
|
ColList[col_index]->Set_Entry_Text (index, number_str);
|
||
|
Set_Dirty ();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Set_Entry_Color
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
bool
|
||
|
ListCtrlClass::Set_Entry_Color (int index, int col_index, const Vector3 &color)
|
||
|
{
|
||
|
if (col_index < 0 || col_index >= ColList.Count ()) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Change the color for the entry in this cell
|
||
|
//
|
||
|
ColList[col_index]->Set_Entry_Color (index, color);
|
||
|
Set_Dirty ();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Set_Entry_Data
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
bool
|
||
|
ListCtrlClass::Set_Entry_Data (int index, int col_index, uint32 user_data)
|
||
|
{
|
||
|
//
|
||
|
// Store the user data in the first column
|
||
|
//
|
||
|
ColList[col_index]->Set_Entry_Data (index, user_data);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Get_Entry_Data
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
uint32
|
||
|
ListCtrlClass::Get_Entry_Data (int index, int col_index)
|
||
|
{
|
||
|
uint32 user_data = 0;
|
||
|
|
||
|
//
|
||
|
// Lookup the user data
|
||
|
//
|
||
|
if (index >= 0 && index < Get_Entry_Count () && col_index >= 0 && col_index < ColList.Count ()) {
|
||
|
user_data = ColList[col_index]->Get_Entry_Data (index);
|
||
|
}
|
||
|
|
||
|
return user_data;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Get_Entry_Text
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
const WCHAR *
|
||
|
ListCtrlClass::Get_Entry_Text (int index, int col_index)
|
||
|
{
|
||
|
//
|
||
|
// Return the string to the caller
|
||
|
//
|
||
|
return ColList[col_index]->Get_Entry_Text (index);
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Delete_All_Entries
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Delete_All_Entries (void)
|
||
|
{
|
||
|
//
|
||
|
// Notify the advise sinks (if necessary) that each entry
|
||
|
// is being deleted
|
||
|
//
|
||
|
int entry_count = Get_Entry_Count ();
|
||
|
for (int item_index = 0; item_index < entry_count; item_index ++) {
|
||
|
ADVISE_NOTIFY (On_ListCtrl_Delete_Entry (this, Get_ID (), item_index));
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Delete each of our row information sturctures
|
||
|
//
|
||
|
for (int index = 0; index < RowInfoList.Count (); index ++) {
|
||
|
delete RowInfoList[index];
|
||
|
}
|
||
|
RowInfoList.Delete_All ();
|
||
|
|
||
|
//
|
||
|
// Now delete all the entries from each column
|
||
|
//
|
||
|
for (index = 0; index < ColList.Count (); index ++) {
|
||
|
ColList[index]->Delete_All_Entries ();
|
||
|
}
|
||
|
|
||
|
Set_Dirty ();
|
||
|
|
||
|
//
|
||
|
// Reset the scroll and current selection positions
|
||
|
//
|
||
|
ScrollPos = 0;
|
||
|
CurrSel = -1;
|
||
|
Set_Dirty ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Scroll_To_End
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Scroll_To_End (void)
|
||
|
{
|
||
|
//
|
||
|
// Update the last page top entry
|
||
|
//
|
||
|
LastPageTopEntryIndex = Find_Last_Page_Top_Entry ();
|
||
|
|
||
|
//
|
||
|
// Force scroll to the end
|
||
|
//
|
||
|
ScrollPos = LastPageTopEntryIndex;
|
||
|
ScrollPos = max (ScrollPos, 0);
|
||
|
|
||
|
//
|
||
|
// Update the scrollbar
|
||
|
//
|
||
|
ScrollBarCtrl.Set_Pos (ScrollPos, false);
|
||
|
|
||
|
Set_Dirty ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Set_Curr_Sel
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Set_Curr_Sel (int new_sel)
|
||
|
{
|
||
|
if (new_sel == -1) {
|
||
|
Select_All(false);
|
||
|
CurrSel = -1;
|
||
|
} else {
|
||
|
Set_Sel(new_sel, false);
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Set_Sel
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Set_Sel (int new_sel, bool notify)
|
||
|
{
|
||
|
if (IsSelectionAllowed) {
|
||
|
|
||
|
//
|
||
|
// Unselect the old entry (if necessary)
|
||
|
//
|
||
|
if (IsMultipleSelection == false) {
|
||
|
Select_Entry (CurrSel, false);
|
||
|
}
|
||
|
|
||
|
int old_sel = CurrSel;
|
||
|
|
||
|
if ((new_sel == -1) && IsNoSelectionAllowed) {
|
||
|
CurrSel = -1;
|
||
|
} else {
|
||
|
//
|
||
|
// Bound the selection index
|
||
|
//
|
||
|
int count = Get_Entry_Count ();
|
||
|
CurrSel = max (new_sel, 0);
|
||
|
CurrSel = min (CurrSel, count - 1);
|
||
|
|
||
|
//
|
||
|
// Select the new entry
|
||
|
//
|
||
|
if (IsMultipleSelection) {
|
||
|
Toggle_Entry_Selection (CurrSel);
|
||
|
} else {
|
||
|
Select_Entry (CurrSel, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Notify anyone who cares that we have changed the selection
|
||
|
//
|
||
|
if (notify && (old_sel != CurrSel)) {
|
||
|
ADVISE_NOTIFY(On_ListCtrl_Sel_Change(this, Get_ID(), old_sel, CurrSel));
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Force a repaint
|
||
|
//
|
||
|
Set_Dirty ();
|
||
|
Update_Scroll_Pos ();
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Update_Scroll_Pos
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Update_Scroll_Pos (void)
|
||
|
{
|
||
|
if (CurrSel < 0) {
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Do we need to scroll up?
|
||
|
//
|
||
|
if (CurrSel < ScrollPos) {
|
||
|
ScrollPos = CurrSel;
|
||
|
Set_Dirty ();
|
||
|
} else {
|
||
|
|
||
|
//
|
||
|
// Do we need to scroll down?
|
||
|
//
|
||
|
RectClass rect;
|
||
|
Get_Entry_Rect (CurrSel, rect);
|
||
|
if (rect.Bottom >= TextRect.Bottom) {
|
||
|
|
||
|
//
|
||
|
// Calculate where we should scroll to
|
||
|
//
|
||
|
ScrollPos = Find_Top_Of_Page (CurrSel);
|
||
|
Set_Dirty ();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Update the scrollbar
|
||
|
//
|
||
|
ScrollBarCtrl.Set_Pos (ScrollPos, false);
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Get_Entry_Rect
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Get_Entry_Rect (int index, RectClass &rect)
|
||
|
{
|
||
|
if (index < 0 || index >= RowInfoList.Count ()) {
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Lookup the height of this row
|
||
|
//
|
||
|
float row_height = RowInfoList[index]->Get_Height ();
|
||
|
float y_pos = -1000.0F;
|
||
|
|
||
|
if (ScrollPos <= index) {
|
||
|
|
||
|
//
|
||
|
// Calculate the starting y-position of this entry
|
||
|
//
|
||
|
y_pos = TextRect.Top;
|
||
|
for (int curr_index = ScrollPos; curr_index < index; curr_index ++) {
|
||
|
y_pos += RowInfoList[curr_index]->Get_Height ();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
rect.Left = int(TextRect.Left);
|
||
|
rect.Right = int(TextRect.Right);
|
||
|
rect.Top = int(y_pos);
|
||
|
rect.Bottom = int(rect.Top + row_height);
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Col_From_Pos
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
int
|
||
|
ListCtrlClass::Col_From_Pos (const Vector2 &mouse_pos)
|
||
|
{
|
||
|
int retval = -1;
|
||
|
|
||
|
//
|
||
|
// Test each column
|
||
|
//
|
||
|
int x_pos = HeaderRect.Left;
|
||
|
int col_count = ColList.Count ();
|
||
|
for (int col_index = 0; col_index < col_count; col_index ++) {
|
||
|
|
||
|
//
|
||
|
// Determine how wide this column is
|
||
|
//
|
||
|
int col_width = (ColList[col_index]->Get_Width () * HeaderRect.Width ());
|
||
|
|
||
|
//
|
||
|
// Let the last column extend to the edge
|
||
|
//
|
||
|
if (col_index == col_count - 1) {
|
||
|
col_width = HeaderRect.Right - x_pos;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Is the coordinate inside this col?
|
||
|
//
|
||
|
if (mouse_pos.X >= x_pos && mouse_pos.X <= (x_pos + col_width)) {
|
||
|
retval = col_index;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Move on to the next column
|
||
|
//
|
||
|
x_pos += col_width;
|
||
|
}
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Entry_From_Pos
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
int
|
||
|
ListCtrlClass::Entry_From_Pos (const Vector2 &mouse_pos)
|
||
|
{
|
||
|
int retval = -1;
|
||
|
if (mouse_pos.Y < TextRect.Top || mouse_pos.Y > TextRect.Bottom) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Test each row
|
||
|
//
|
||
|
int y_pos = TextRect.Top;
|
||
|
int row_count = Get_Entry_Count ();
|
||
|
for (int row_index = ScrollPos; row_index < row_count; row_index ++) {
|
||
|
|
||
|
float row_height = RowInfoList[row_index]->Get_Height ();
|
||
|
|
||
|
//
|
||
|
// Is the coordinate inside this row?
|
||
|
//
|
||
|
if (mouse_pos.Y >= y_pos && mouse_pos.Y <= (y_pos + row_height)) {
|
||
|
retval = row_index;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Move down to the next row
|
||
|
//
|
||
|
y_pos += row_height;
|
||
|
|
||
|
//
|
||
|
// Stop searching if we've moved off the page
|
||
|
//
|
||
|
if (mouse_pos.Y >= TextRect.Bottom) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Scroll_Page
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::Scroll_Page (int direction)
|
||
|
{
|
||
|
int count = RowInfoList.Count ();
|
||
|
float height = TextRect.Height ();
|
||
|
|
||
|
bool found = false;
|
||
|
|
||
|
//
|
||
|
// Scan either direction from the current scroll
|
||
|
// position until we've moved a whole page
|
||
|
//
|
||
|
for ( int index = ScrollPos;
|
||
|
index >= 0 && index < count;
|
||
|
index += direction)
|
||
|
{
|
||
|
//
|
||
|
// Decrement the remaining distance
|
||
|
//
|
||
|
height -= RowInfoList[index]->Get_Height ();
|
||
|
|
||
|
//
|
||
|
// If we've gone of the page, then back off one entry
|
||
|
//
|
||
|
if (height < 0) {
|
||
|
ScrollPos = (index - direction);
|
||
|
ScrollPos = min (ScrollPos, LastPageTopEntryIndex);
|
||
|
ScrollBarCtrl.Set_Pos (ScrollPos, false);
|
||
|
Set_Dirty ();
|
||
|
found = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Check the boundary conditions
|
||
|
//
|
||
|
if (index < 0 && found == false) {
|
||
|
ScrollPos = 0;
|
||
|
ScrollBarCtrl.Set_Pos (ScrollPos, false);
|
||
|
Set_Dirty ();
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// On_VScroll_Page
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::On_VScroll_Page (ScrollBarCtrlClass *scrollbar, int ctrl_id, int direction)
|
||
|
{
|
||
|
Scroll_Page (direction);
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// On_VScroll
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::On_VScroll (ScrollBarCtrlClass *, int , int new_position)
|
||
|
{
|
||
|
if (ScrollPos != new_position) {
|
||
|
ScrollPos = new_position;
|
||
|
Set_Dirty ();
|
||
|
}
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// On_Mouse_Wheel
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListCtrlClass::On_Mouse_Wheel (int direction)
|
||
|
{
|
||
|
if (direction < 0) {
|
||
|
if (ScrollPos > 0) {
|
||
|
ScrollPos --;
|
||
|
ScrollBarCtrl.Set_Pos (ScrollPos, false);
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
if (ScrollPos < LastPageTopEntryIndex) {
|
||
|
ScrollPos ++;
|
||
|
ScrollBarCtrl.Set_Pos (ScrollPos, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Set_Dirty ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
//********************************************************************************//
|
||
|
//
|
||
|
// Start of ListColumnClass
|
||
|
//
|
||
|
//********************************************************************************//
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Free_Data
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListColumnClass::Free_Data (void)
|
||
|
{
|
||
|
Delete_All_Entries ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Reset_Contents
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListColumnClass::Reset_Contents (void)
|
||
|
{
|
||
|
//
|
||
|
// Remove all the entries
|
||
|
//
|
||
|
Free_Data ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Insert_Entry
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
int
|
||
|
ListColumnClass::Insert_Entry (int index, const WCHAR *entry_name)
|
||
|
{
|
||
|
ListEntryClass *entry = new ListEntryClass (entry_name);
|
||
|
|
||
|
//
|
||
|
// Should we insert this entry in the list or add it to the end?
|
||
|
//
|
||
|
if (index < EntryList.Count ()) {
|
||
|
EntryList.Insert (index + 1, entry);
|
||
|
} else {
|
||
|
EntryList.Add (entry);
|
||
|
index = (EntryList.Count () - 1);
|
||
|
}
|
||
|
|
||
|
return index;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Delete_Entry
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
bool
|
||
|
ListColumnClass::Delete_Entry (int index)
|
||
|
{
|
||
|
bool retval = false;
|
||
|
|
||
|
//
|
||
|
// Delete the entry if we can find it in our list
|
||
|
//
|
||
|
if (index >= 0 && index < EntryList.Count ()) {
|
||
|
delete EntryList[index];
|
||
|
EntryList.Delete (index);
|
||
|
}
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Delete_All_Entries
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListColumnClass::Delete_All_Entries (void)
|
||
|
{
|
||
|
//
|
||
|
// Free each of the entries in the list
|
||
|
//
|
||
|
for (int index = 0; index < EntryList.Count (); index ++) {
|
||
|
delete EntryList[index];
|
||
|
}
|
||
|
|
||
|
EntryList.Delete_All ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Move_Entry
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListColumnClass::Move_Entry (int old_index, int new_index)
|
||
|
{
|
||
|
if ( old_index < 0 || (old_index >= EntryList.Count ()) &&
|
||
|
new_index < 0 || (new_index >= EntryList.Count ()))
|
||
|
{
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Move the entry
|
||
|
//
|
||
|
ListEntryClass *old_entry = EntryList[old_index];
|
||
|
EntryList.Insert (new_index, old_entry);
|
||
|
EntryList.Delete (old_index);
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Swap_Entries
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
ListColumnClass::Swap_Entries (int index1, int index2)
|
||
|
{
|
||
|
if ( index1 < 0 || (index1 >= EntryList.Count ()) &&
|
||
|
index2 < 0 || (index2 >= EntryList.Count ()))
|
||
|
{
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Move the entry
|
||
|
//
|
||
|
ListEntryClass *temp_entry = EntryList[index1];
|
||
|
EntryList[index1] = EntryList[index2];
|
||
|
EntryList[index2] = temp_entry;
|
||
|
return ;
|
||
|
}
|
||
|
|