Skip to content

Commit

Permalink
Added a dynamic filtering to the listbox widget and enabled it on all…
Browse files Browse the repository at this point in the history
… lists which tend to have a lot of values and could use additional filtering.

FUNCTIONAL:
 - Fixed a bug where using shift+tab to navigate back through the focus list would never focus the first element -> for example in the add command dialog you could never get back to the list of commands by using shift+tab
 - Added a new filtering to some of the lists which works like this:
   - Type letters and space to filter the list
   - It will limit the items in the list only to those that match every word you type
 - Enabled the filtering on:
   - Add command dialog: list of commands and most of the lists in there
   - Customize character dialog: character list
   - Character management dialog: custom characters list, type list
   - Build Levels screen: list of holds
   - Hold selection screen: list of hold
   - Restore screen: list of levels

INTERNAL:
 - Added some fancy functions to WChar.cpp that are used to implement the filtering
 - Select/Unselect on CFocusWidget are virtual so you can things happen on losing focus
 - CWidget has a way to prevent event bubbling, currently only works KeyDown but can be easily expanded

NOTES FOR TESTERS:
 - Here are some issues I've found/introduced during development and fixed. It's best to double check them, and you can test all of them on the command list dialog.:
   a) Select some option -> Filter so that it's not available -> Up/Down arrows wouldn't select things
   b) (Add Command dialog) Select some option -> Filter so that it's not available -> widgets for the selected option would not be available
   c) Scrollbar was not resized
   d) The last item in the list was not available in the filtering if there were more things in the list than can fit
   e) Filter -> Select an item -> remove the filter -> The list would scroll away from the currently selected item
   f) Filter -> It was still possible to scroll down as if the list was not slimmed down
  • Loading branch information
EvidentlyCube committed Nov 24, 2020
1 parent 8df8ef8 commit 2e228cc
Show file tree
Hide file tree
Showing 21 changed files with 364 additions and 94 deletions.
50 changes: 50 additions & 0 deletions BackEndLib/Wchar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "Ports.h" // for towlower, iswspace
#include <cstdio>
#include <cstring>
#include <sstream>

//Some common small strings. Larger strings are localizable and should be kept in database.
const WCHAR wszAmpersand[] = { We('&'),We(0) };
Expand Down Expand Up @@ -130,6 +131,13 @@ void AsciiToUnicode(const std::string &str, WSTRING &wstr) {
AsciiToUnicode(str.c_str(), wstr);
}

//*****************************************************************************
const WSTRING AsciiToUnicode(const char *psz) {
WSTRING res;
AsciiToUnicode(psz, res);
return res;
}

//******************************************************************************
void CTextToUnicode(const char *psz, WSTRING &wstr)
{
Expand Down Expand Up @@ -885,3 +893,45 @@ WSTRING WCSReplace(
processed = loc + from.size();
}
}

//*****************************************************************************
WSTRING WCSToLower(WSTRING const &source)
//Converts WSTRING to lowercase
{
WSTRING lowercased;
lowercased.reserve(source.size());
for (WSTRING::const_iterator it = source.begin(); it != source.end(); ++it)
lowercased += towlower(*it);

return lowercased;
}

//*****************************************************************************
const std::vector<WSTRING> WCSExplode(WSTRING const &source, WCHAR const delimiter)
// Explodes a string into pieces
// Adapted from: https://stackoverflow.com/a/12967010
{
std::vector<WSTRING> result;
std::wistringstream iss(source);

for (WSTRING token; std::getline(iss, token, delimiter); )
{
if (!token.empty())
result.push_back(token);
}

return result;
}

//*****************************************************************************
bool WCSContainsAll(WSTRING const &haystack, std::vector<WSTRING> const &needles)
// Returns true if haystack contains every string in needle, even if they overlap
{
for (std::vector<WSTRING>::const_iterator iter = needles.begin(); iter < needles.end(); ++iter)
{
if (haystack.find(*iter) == WSTRING::npos)
return false;
}

return true;
}
7 changes: 7 additions & 0 deletions BackEndLib/Wchar.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
#include "PortsBase.h"
#include "Types.h" //need BYTE, UINT

#include <vector>

#define STRFY(x) #x
#define STRFY_EXPAND(x) STRFY(x)

Expand Down Expand Up @@ -90,6 +92,7 @@ void SanitizeSingleLineString(WSTRING& wstr);
void SanitizeMultiLineString(WSTRING& wstr);
void AsciiToUnicode(const char *psz, WSTRING &wstr);
void AsciiToUnicode(const std::string& str, WSTRING &wstr);
const WSTRING AsciiToUnicode(const char *psz);
void CTextToUnicode(const char *psz, WSTRING &wstr);
bool UnicodeToAscii(const WSTRING& wstr, char *psz);
bool UnicodeToAscii(const WSTRING& wstr, std::string &str);
Expand Down Expand Up @@ -156,5 +159,9 @@ void fputWs(const WCHAR* wsz, FILE* pFile);

std::string strReplace(std::string const &source, std::string const &from, std::string const &to);
WSTRING WCSReplace(WSTRING const &source, WSTRING const &from, WSTRING const &to);
WSTRING WCSToLower(WSTRING const &source);
const std::vector<WSTRING> WCSExplode(WSTRING const &source, WCHAR const delimiter);
bool WCSContainsAll(WSTRING const &haystack, std::vector<WSTRING> const &needles);


#endif
19 changes: 18 additions & 1 deletion DROD/CharacterDialogWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4326,6 +4326,7 @@ void CCharacterDialogWidget::PopulateImperativeListBox(const bool /*bDefaultScri
this->pImperativeListBox->AddItem(ScriptFlag::Stunnable, g_pTheDB->GetMessageText(MID_NPCStunnable));
this->pImperativeListBox->AddItem(ScriptFlag::NotStunnable, g_pTheDB->GetMessageText(MID_NPCNotStunnable));
this->pImperativeListBox->SelectLine(0);
this->pImperativeListBox->SetAllowFiltering(true);
}

//*****************************************************************************
Expand Down Expand Up @@ -4356,7 +4357,8 @@ void CCharacterDialogWidget::PopulateBehaviorListBox()
this->pBehaviorListBox->AddItem(ScriptFlag::Behavior::PuffImmune, g_pTheDB->GetMessageText(MID_PuffImmune));
this->pBehaviorListBox->AddItem(ScriptFlag::Behavior::FatalPushImmune, g_pTheDB->GetMessageText(MID_FatalPushImmune));
this->pBehaviorListBox->AddItem(ScriptFlag::Behavior::CanBeNPCBeethro, g_pTheDB->GetMessageText(MID_CanBeNPCBeethro));
this->pImperativeListBox->SelectLine(0);
this->pBehaviorListBox->SelectLine(0);
this->pBehaviorListBox->SetAllowFiltering(true);
}

//*****************************************************************************
Expand Down Expand Up @@ -4447,6 +4449,7 @@ void CCharacterDialogWidget::PopulateCommandListBox()
this->pActionListBox->AddItem(CCharacterCommand::CC_WorldMapSelect, g_pTheDB->GetMessageText(MID_WorldMapSelect));

this->pActionListBox->SelectLine(0);
this->pActionListBox->SetAllowFiltering(true);
}

//*****************************************************************************
Expand Down Expand Up @@ -4515,7 +4518,9 @@ void CCharacterDialogWidget::PopulateEventListBox()
this->pEventListBox->AddItem(CID_Tunnel, g_pTheDB->GetMessageText(MID_Tunnel));
this->pEventListBox->AddItem(CID_WispOnPlayer, g_pTheDB->GetMessageText(MID_WispOnPlayer));
this->pEventListBox->AddItem(CID_WubbaStabbed, g_pTheDB->GetMessageText(MID_WubbaStabbed));

this->pEventListBox->SelectLine(0);
this->pEventListBox->SetAllowFiltering(true);
}

//*****************************************************************************
Expand Down Expand Up @@ -4659,6 +4664,8 @@ void CCharacterDialogWidget::PopulateItemListBox(CListBoxWidget *pListBox,
pListBox->AddItem(T_WALL_H, g_pTheDB->GetMessageText(MID_SecretWall));
pListBox->AddItem(T_WATER, g_pTheDB->GetMessageText(MID_Water));
pListBox->AddItem(T_WALL_IMAGE, g_pTheDB->GetMessageText(MID_WallImage));

pListBox->SetAllowFiltering(true);
}

//*****************************************************************************
Expand Down Expand Up @@ -4693,6 +4700,8 @@ void CCharacterDialogWidget::PopulateGraphicListBox(CListBoxWidget *pListBox)
const UINT graphic = graphics[i];
pListBox->AddItem(graphic, g_pTheDB->GetMessageText(getMIDForMonster(graphic)));
}

pListBox->SetAllowFiltering(true);
}

//*****************************************************************************
Expand Down Expand Up @@ -4743,6 +4752,8 @@ void CCharacterDialogWidget::PopulateCharacterList(CListBoxWidget *pListBox)
HoldCharacter *pChar = *ch;
pListBox->AddItem(pChar->dwCharID, pChar->charNameText.c_str());
}

pListBox->SetAllowFiltering(true);
}

//*****************************************************************************
Expand All @@ -4767,6 +4778,8 @@ void CCharacterDialogWidget::PopulateGotoLabelList(const COMMANDPTR_VECTOR& comm
this->wIncrementedLabel = pCommand->x;
}
}

this->pGotoLabelListBox->SetAllowFiltering(true);
}

//*****************************************************************************
Expand Down Expand Up @@ -4806,6 +4819,10 @@ void CCharacterDialogWidget::PopulateMainGraphicList()

ASSERT(this->pSpeakerListBox);
PopulateSpeakerList(this->pSpeakerListBox);

this->pGraphicListBox->SetAllowFiltering(true);
this->pPlayerGraphicListBox->SetAllowFiltering(true);
this->pSpeakerListBox->SetAllowFiltering(true);
}

//*****************************************************************************
Expand Down
6 changes: 3 additions & 3 deletions DROD/DemoScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ void CDemoScreen::GoToTurn()
{
WCHAR temp[10];
WSTRING message = g_pTheDB->GetMessageText(MID_GotoDemoMovePrompt);
message = WCSReplace(message, L"%end%", _itoW(this->pDemo->wEndTurnNo - this->pDemo->wBeginTurnNo, temp, 10, 10));
message = WCSReplace(message, AsciiToUnicode("%end%"), _itoW(this->pDemo->wEndTurnNo - this->pDemo->wBeginTurnNo, temp, 10, 10));

CScreen::TextInputDialogOptions options(
message.c_str(),
Expand Down Expand Up @@ -609,8 +609,8 @@ void CDemoScreen::SetSignTextToCurrentRoom()

WCHAR temp[10];
WSTRING suffix = g_pTheDB->GetMessageText(MID_DemoMoveNumberSuffix);
suffix = WCSReplace(suffix, L"%now%", _itoW(wMoveNow, temp, 10, 10));
suffix = WCSReplace(suffix, L"%total%", _itoW(wMovesTotal, temp, 10, 10));
suffix = WCSReplace(suffix, AsciiToUnicode("%now%"), _itoW(wMoveNow, temp, 10, 10));
suffix = WCSReplace(suffix, AsciiToUnicode("%total%"), _itoW(wMovesTotal, temp, 10, 10));

this->wstrSignText += suffix;
}
Expand Down
1 change: 1 addition & 0 deletions DROD/EditSelectScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ CEditSelectScreen::CEditSelectScreen()
X_HOLD_LBOX, Y_HOLD_LBOX, CX_HOLD_LBOX, CY_HOLD_LBOX, true);
AddWidget(this->pHoldListBoxWidget);
this->pHoldListBoxWidget->IgnoreLeadingArticlesInSort();
this->pHoldListBoxWidget->SetAllowFiltering(true);

//Hold buttons.
pButton = new CButtonWidget(TAG_COPY_HOLD, X_COPY_HOLD, Y_COPY_HOLD,
Expand Down
2 changes: 2 additions & 0 deletions DROD/HoldSelectScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,8 @@ bool CHoldSelectScreen::SetForActivate()
PopulateHoldListBox();
SetHoldDesc();

this->pHoldListBoxWidget->SetAllowFiltering(true);

//Never return to the win screen (for rating a hold). Yank it out of the return list so
//that we go back to the title screen instead.
if (g_pTheSM->GetReturnScreenType() == SCR_WinStart)
Expand Down
4 changes: 4 additions & 0 deletions DROD/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "DrodSound.h"
#include "GameScreen.h"

#include <FrontEndLib/ListBoxWidget.h> // To init i18n
#include <FrontEndLib/ImageWidget.h>

#include "../DRODLib/Db.h"
Expand Down Expand Up @@ -682,6 +683,9 @@ MESSAGE_ID Init(

srand(int(time(NULL)));

// Init FrontendLocalization
CListBoxWidget::wstrFilterWord = g_pTheDB->GetMessageText(MID_ListboxFilter);

//Success.
return bRestoredFromCorruption ? MID_DatCorrupted_Restored :
bUpgradingDataFiles ? MID_DRODUpgradingDataFiles : MID_Success;
Expand Down
1 change: 1 addition & 0 deletions DROD/RestoreScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ CRestoreScreen::CRestoreScreen()
this->pLevelListBoxWidget = new CListBoxWidget(TAG_LEVEL_LBOX,
X_LEVEL_LBOX, Y_LEVEL_LBOX, CX_LEVEL_LBOX, CY_LEVEL_LBOX);
AddWidget(this->pLevelListBoxWidget);
this->pLevelListBoxWidget->SetAllowFiltering(true);

//Room selection area.
AddWidget(new CLabelWidget(TAG_CHOOSE_ROOM_LABEL, X_CHOOSE_ROOM_LABEL, Y_CHOOSE_ROOM_LABEL,
Expand Down
6 changes: 3 additions & 3 deletions DROD/SettingsScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,8 @@ CSettingsScreen::CSettingsScreen()
Y_TABBEDMENU, CX_TABBEDMENU, CY_TABBEDMENU, 4, CY_MENU_TAB, BG_COLOR);
pTabbedMenu->SetTabText(PERSONAL_TAB, g_pTheDB->GetMessageText(MID_Settings));
pTabbedMenu->SetTabText(GAS_TAB, g_pTheDB->GetMessageText(MID_GraphicsAndSound));
pTabbedMenu->SetTabText(KEYMAP_1_TAB, L"Keymap 1"); //@MID-FIX
pTabbedMenu->SetTabText(KEYMAP_2_TAB, L"Keymap 2");
pTabbedMenu->SetTabText(KEYMAP_1_TAB, g_pTheDB->GetMessageText(MID_SettingsTabKeymap1));
pTabbedMenu->SetTabText(KEYMAP_2_TAB, g_pTheDB->GetMessageText(MID_SettingsTabKeymap2));
pTabbedMenu->SetBGImage("Background", 128);
AddWidget(pTabbedMenu);

Expand Down Expand Up @@ -1156,7 +1156,7 @@ void CSettingsScreen::DoKeyRedefinition(const UINT dwTagNo) {
if (pOverwrittenKey) {
WSTRING str = WCSReplace(
g_pTheDB->GetMessageText(MID_OverwritingMacroKeyError),
L"%1",
AsciiToUnicode("%1"),
g_pTheDB->GetMessageText(pOverwrittenKey->commandMID)
);
ShowOkMessage(str.c_str());
Expand Down
3 changes: 3 additions & 0 deletions DRODLib/DbBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -881,9 +881,12 @@ const WCHAR* CDbBase::GetMessageText(
case MID_DemoMoveNumberSuffix: strText = "(Demo move %now%/%total%)"; break;
case MID_ErrorCannotReplaceWithDifferentExistingFile: strText = "You are trying to replace a file named '%fileBase%' with '%fileSelected%'. Unfortunately there is already a file with that name in this hold - it must first be deleted."; break;
case MID_ReplaceMediaWithAnother: strText = "This hold already contains a file named '%file%'. Do you want to replace it with this new file? All usages of it will be updated."; break;
case MID_SettingsTabKeymap1: strText = "Keymap 1"; break;
case MID_SettingsTabKeymap2: strText = "Keymap 2"; break;
case MID_ReplaceFileButton: strText = "Replace"; break;
case MID_FilePendingDeletionSuffix: strText = "(Pending deletion)"; break;
case MID_Undelete: strText = "Undelete"; break;
case MID_ListboxFilter: strText = "Filter"; break;
// case MID_DRODUpgradingDataFiles: strText = "DROD is upgrading your data files." NEWLINE "This could take a moment. Please be patient..."; break;
// case MID_No: strText = "&No"; break;
default: break;
Expand Down
13 changes: 10 additions & 3 deletions DRODLib/I18N.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ namespace I18N {
//************************************************************************************
WSTRING DescribeInputKey(const InputKey key)
{
static WSTRING ShiftShort = AsciiToUnicode("Sh+");
static WSTRING ShiftLong = AsciiToUnicode("Shift ");
static WSTRING CtrlShort = AsciiToUnicode("Ct+");
static WSTRING CtrlLong = AsciiToUnicode("Ctrl ");
static WSTRING AltShort = AsciiToUnicode("ALt+");
static WSTRING AltLong = AsciiToUnicode("Alt ");

if (key == SDLK_UNKNOWN)
return g_pTheDB->GetMessageText(MID_UNKNOWN);

Expand All @@ -48,15 +55,15 @@ namespace I18N {
std::wstringstream str;

if (bIsShift) {
str << (wModifierCount > 1 && bHasNonModifier ? L"Sh+" : L"Shift ");
str << (wModifierCount > 1 && bHasNonModifier ? ShiftShort : ShiftLong);
}

if (bIsCtrl) {
str << (wModifierCount > 1 && bHasNonModifier ? L"Ct+" : L"Ctrl ");
str << (wModifierCount > 1 && bHasNonModifier ? CtrlShort : CtrlLong);
}

if (bIsAlt) {
str << (wModifierCount > 1 && bHasNonModifier ? L"Alt+" : L"Alt ");
str << (wModifierCount > 1 && bHasNonModifier ? AltShort : AltLong);
}

if (keyCode != SDLK_UNKNOWN)
Expand Down
14 changes: 10 additions & 4 deletions FrontEndLib/EventHandlerWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,13 @@ void CEventHandlerWidget::Activate_HandleKeyDown(
if (IsDeactivating())
return; //if widget became inactive after key, stop handling event


if (pSelectedWidget->bIsPreventingEventBubbling)
{ // If widget wants to trap the event then stop handling it
pSelectedWidget->bIsPreventingEventBubbling = false;
return;
}

//If selected widget doesn't accept text entry then hotkey
//without ALT can be used for any widget.
//If selected widget does accept text entry, it can use the Enter
Expand Down Expand Up @@ -1029,18 +1036,17 @@ bool CEventHandlerWidget::SelectPrevWidget(
CWidget *pWidget;
if (iSeek != this->FocusList.begin())
{
--iSeek;
do
while (iSeek != this->FocusList.begin())
{
--iSeek;

pWidget = *iSeek;
if (pWidget && pWidget->IsSelectable(!bPaint))
{
ChangeSelection(iSeek, bPaint);
return true;
}
--iSeek;
}
while (iSeek != this->FocusList.begin());
}

//Search for next selectable widget from end of focus list.
Expand Down
4 changes: 2 additions & 2 deletions FrontEndLib/FocusWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ class CFocusWidget : public CWidget
virtual void Hide(const bool bPaint = true);
virtual bool IsFocusable() const {return this->bFocusingAllowed;}
bool IsSelected() const {return this->bSelected;}
void Select(const bool bPaint = true);
virtual void Select(const bool bPaint = true);
void SetFocusAllowed(const bool bVal = true);
void Unselect(const bool bPaint = true);
virtual void Unselect(const bool bPaint = true);

private:
bool bSelected;
Expand Down

0 comments on commit 2e228cc

Please sign in to comment.