/*
** 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 .
*/
#include "IMECandidateCtrl.h"
#include "IMECandidate.h"
#include "DialogBase.h"
#include "StyleMgr.h"
#include "MouseMgr.h"
#define BORDER_WIDTH 2
#define BORDER_HEIGHT 2
/******************************************************************************
*
* NAME
* IMECandidateCtrl::IMECandidateCtrl
*
* DESCRIPTION
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
IMECandidateCtrl::IMECandidateCtrl() :
mFullRect(0,0,0,0),
mCellSize(0,0),
mCurrSel(-1),
mScrollPos(0),
mCellsPerPage(0),
mCandidate(NULL)
{
StyleMgrClass::Assign_Font(&mTextRenderer, StyleMgrClass::FONT_LISTS);
// Candidate control never wants to take focus
Set_Wants_Focus(false);
// Setup child scrollbar
mScrollBarCtrl.Set_Wants_Focus(false);
mScrollBarCtrl.Set_Small_BMP_Mode(true);
mScrollBarCtrl.Set_Advise_Sink(this);
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::~IMECandidateCtrl
*
* DESCRIPTION
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
IMECandidateCtrl::~IMECandidateCtrl()
{
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::Init
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
* NONE
*
******************************************************************************/
void IMECandidateCtrl::Init(IME::IMECandidate* candidate)
{
WWDEBUG_SAY(("IMECandidateCtrl: Init\n"));
Reset();
mCandidate = candidate;
Changed(candidate);
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::Changed
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
* NONE
*
******************************************************************************/
void IMECandidateCtrl::Changed(IME::IMECandidate* candidate)
{
WWDEBUG_SAY(("IMECandidateCtrl: Changed\n"));
WWASSERT(mCandidate == candidate);
if (candidate)
{
mScrollPos = candidate->GetPageStart();
unsigned long candSel = candidate->GetSelection();
if (candSel != (unsigned long)mCurrSel)
{
mCurrSel = candSel;
UpdateScrollPos();
}
}
Set_Dirty();
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::Reset
*
* DESCRIPTION
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
void IMECandidateCtrl::Reset(void)
{
mCellSize.Set(0,0);
mCurrSel = -1;
mScrollPos = 0;
mCellsPerPage = 0;
mScrollBarCtrl.Show(false);
mCandidate = NULL;
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::CreateControlRenderer
*
* DESCRIPTION
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
void IMECandidateCtrl::CreateControlRenderer(void)
{
Render2DClass& renderer = mControlRenderer;
// Configure this renderer
renderer.Reset();
renderer.Enable_Texturing(false);
renderer.Set_Coordinate_Range(Render2DClass::Get_Screen_Resolution());
renderer.Add_Quad(mFullRect, RGBA_TO_INT32(0, 0, 0, 170));
// Determine which color to draw the outline in
int color = StyleMgrClass::Get_Line_Color();
int bkColor = StyleMgrClass::Get_Bk_Color();
if (IsEnabled == false)
{
color = StyleMgrClass::Get_Disabled_Line_Color();
bkColor = StyleMgrClass::Get_Disabled_Bk_Color();
}
// Draw the control outline
RectClass rect = Rect;
renderer.Add_Outline(rect, 1.0F, color);
// Now draw the background
rect.Right -= 1;
rect.Bottom -= 1;
renderer.Add_Quad(rect, bkColor);
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::CreateTextRenderer
*
* DESCRIPTION
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
void IMECandidateCtrl::CreateTextRenderer(void)
{
mHilightRenderer.Reset();
mHilightRenderer.Set_Coordinate_Range(Render2DClass::Get_Screen_Resolution());
StyleMgrClass::Configure_Hilighter(&mHilightRenderer);
mTextRenderer.Reset();
if (mCandidate)
{
// Add each candidate to the list
const unsigned int selIndexBias = (mCandidate->IsStartFrom1() ? 1 : 0);
float currYPos = ClientRect.Top;
const unsigned long candidateCount = mCandidate->GetCount();
for (unsigned long index = mScrollPos; index < candidateCount; ++index)
{
// Build the rectangle we will draw the text into
RectClass textRect;
textRect.Left = ClientRect.Left;
textRect.Top = currYPos;
textRect.Right = (textRect.Left + mCellSize.X);
textRect.Bottom = (textRect.Top + mCellSize.Y);
// If this cell extends past the end of the control then stop
if (textRect.Bottom > ClientRect.Bottom)
{
break;
}
// Draw the text
const WCHAR* text = mCandidate->GetCandidate(index);
WideStringClass entry(0, true);
entry.Format(L"%d. %s", ((index - mScrollPos) + selIndexBias), text);
StyleMgrClass::Render_Text(entry, &mTextRenderer, textRect, true, true);
// Hilight this entry (if its the currently selected one)
if ((int)index == mCurrSel)
{
StyleMgrClass::Render_Hilight(&mHilightRenderer, textRect);
}
currYPos += mCellSize.Y;
}
}
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::Render
*
* DESCRIPTION
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
void IMECandidateCtrl::Render(void)
{
// Recreate the renderers (if necessary)
if (IsDirty)
{
CreateControlRenderer();
CreateTextRenderer();
}
mControlRenderer.Render();
mTextRenderer.Render();
mHilightRenderer.Render();
DialogControlClass::Render();
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::SetCurrSel
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
* NONE
*
******************************************************************************/
void IMECandidateCtrl::SetCurrSel(int index)
{
if (mCandidate)
{
if ((index != mCurrSel) && (index == -1) || ((index >= 0) && (unsigned long)index < mCandidate->GetCount()))
{
mCurrSel = index;
Set_Dirty();
}
}
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::EntryFromPos
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
*
******************************************************************************/
int IMECandidateCtrl::EntryFromPos(const Vector2& mousePos)
{
if (mCandidate)
{
// Loop over all the entries in our current view
float currYPos = ClientRect.Top;
const unsigned long candidateCount = mCandidate->GetCount();
for (unsigned long index = mScrollPos; index < candidateCount; ++index)
{
// Is ths mouse over this entry?
if ((mousePos.Y >= currYPos && mousePos.Y <= (currYPos + mCellSize.Y))
|| mousePos.Y > ClientRect.Bottom)
{
return index;
}
currYPos += mCellSize.Y;
}
}
return -1;
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::UpdateScrollPos
*
* DESCRIPTION
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
void IMECandidateCtrl::UpdateScrollPos(void)
{
if (mCurrSel != -1)
{
unsigned int scrollPos = mScrollPos;
if (mCurrSel < (int)scrollPos)
{
// Scroll up so the current selection is in view
scrollPos = mCurrSel;
Set_Dirty();
}
else if (mCurrSel >= (int)(scrollPos + mCellsPerPage))
{
// Scroll down so the current selection is in view
scrollPos = max((unsigned int)mCurrSel - (mCellsPerPage - 1), 0);
Set_Dirty();
}
if (scrollPos != mScrollPos)
{
// Update the scrollbar
mScrollBarCtrl.Set_Pos(mScrollPos, false);
if (mCandidate)
{
mCandidate->SetPageStart(mScrollPos);
}
}
}
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::Update_Client_Rect
*
* DESCRIPTION
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
void IMECandidateCtrl::Update_Client_Rect(void)
{
WWDEBUG_SAY(("IMECandidateCtrl: Update_Client_Rect()\n"));
Vector2 pageSize;
CalculateCandidatePageExtent(pageSize, mCellSize);
mCellsPerPage = (unsigned int)(pageSize.Y / mCellSize.Y);
Rect.Right = (Rect.Left + (pageSize.X + (BORDER_WIDTH * 2.0f)));
Rect.Bottom = (Rect.Top + (pageSize.Y + (BORDER_HEIGHT * 2.0f)));
mFullRect = Rect;
ClientRect = Rect;
ClientRect.Inflate(Vector2(-BORDER_WIDTH, -BORDER_HEIGHT));
if (mCandidate)
{
WWASSERT(mCandidate->GetPageSize() <= mCellsPerPage);
// Do we need to show a scroll bar?
const unsigned long candidateCount = mCandidate->GetCount();
if ((unsigned long)mCellsPerPage < candidateCount)
{
// Position the scrollbar to the right of the list
RectClass scrollRect;
scrollRect.Left = mFullRect.Right;
scrollRect.Top = mFullRect.Top;
scrollRect.Right = -1;
scrollRect.Bottom = mFullRect.Bottom;
// Size the scroll bar
mScrollBarCtrl.Set_Window_Rect(scrollRect);
mScrollBarCtrl.Set_Page_Size(mCellsPerPage - 1);
mScrollBarCtrl.Set_Range(0, (candidateCount - mCellsPerPage));
mScrollBarCtrl.Show(true);
const RectClass& rect = mScrollBarCtrl.Get_Window_Rect();
mFullRect.Right = rect.Right;
}
}
Set_Dirty();
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
* NONE
*
******************************************************************************/
void IMECandidateCtrl::CalculateCandidatePageExtent(Vector2& outExtent, Vector2& outCellSize)
{
if (mCandidate)
{
// Get the size of the widest candidate string.
float maxCandWidth = 0.0f;
const unsigned long candidateCount = mCandidate->GetCount();
for (unsigned long index = 0; index < candidateCount; ++index)
{
// Get the extent of the current entry
const WCHAR* text = mCandidate->GetCandidate(index);
Vector2 textExtent = mTextRenderer.Get_Text_Extents(text);
if (textExtent.X > maxCandWidth)
{
maxCandWidth = textExtent.X;
}
}
Vector2 charSize = mTextRenderer.Get_Text_Extents(L"W");
// The cell size is the maximum candidate size plus some spacing.
outCellSize.X = (maxCandWidth + (charSize.X * 3.0f));
outCellSize.Y = (charSize.Y * 1.25f);
outExtent.X = outCellSize.X;
outExtent.Y = (mCellSize.Y * (float)mCandidate->GetPageSize());
}
else
{
outExtent.X = 0.0f;
outExtent.Y = 0.0f;
outCellSize.X = 0.0f;
outCellSize.Y = 0.0f;
}
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::On_Set_Cursor
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
* NONE
*
******************************************************************************/
void IMECandidateCtrl::On_Set_Cursor(const Vector2& mousePos)
{
// Change the mouse cursor if necessary
if (ClientRect.Contains(mousePos))
{
MouseMgrClass::Set_Cursor(MouseMgrClass::CURSOR_ACTION);
}
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::On_LButton_Down
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
* NONE
*
******************************************************************************/
void IMECandidateCtrl::On_LButton_Down(const Vector2& mousePos)
{
SetCurrSel(EntryFromPos(mousePos));
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::On_LButton_Up
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
* NONE
*
******************************************************************************/
void IMECandidateCtrl::On_LButton_Up(const Vector2& mousePos)
{
int sel = EntryFromPos(mousePos);
SetCurrSel(sel);
// Process candidate selection here
if (mCandidate && (sel >= 0))
{
const wchar_t* string = mCandidate->GetCandidate(sel);
WWDEBUG_SAY(("*** Selected Candidate: %d %04x\n", sel, *string));
mCandidate->SelectCandidate((unsigned long)sel);
}
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::On_Add_To_Dialog
*
* DESCRIPTION
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
void IMECandidateCtrl::On_Add_To_Dialog(void)
{
Parent->Add_Control(&mScrollBarCtrl);
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::On_Remove_From_Dialog
*
* DESCRIPTION
*
* INPUTS
* NONE
*
* RESULT
* NONE
*
******************************************************************************/
void IMECandidateCtrl::On_Remove_From_Dialog(void)
{
Parent->Remove_Control(&mScrollBarCtrl);
}
/******************************************************************************
*
* NAME
* IMECandidateCtrl::On_VScroll
*
* DESCRIPTION
*
* INPUTS
*
* RESULT
* NONE
*
******************************************************************************/
void IMECandidateCtrl::On_VScroll(ScrollBarCtrlClass*, int , int newPos)
{
mScrollPos = newPos;
if (mCandidate)
{
mCandidate->SetPageStart(mScrollPos);
}
Set_Dirty();
}