2 * This file is part of OpenTTD.
3 * OpenTTD 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, version 2.
4 * OpenTTD 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.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file picker_gui.cpp %File for dealing with picker windows */
11 #include "core/backup_type.hpp"
15 #include "picker_gui.h"
16 #include "querystring_gui.h"
17 #include "settings_type.h"
18 #include "sortlist_type.h"
19 #include "sound_func.h"
20 #include "sound_type.h"
21 #include "string_func.h"
22 #include "stringfilter_type.h"
23 #include "strings_func.h"
24 #include "widget_type.h"
25 #include "window_func.h"
26 #include "window_gui.h"
27 #include "window_type.h"
28 #include "zoom_func.h"
30 #include "widgets/picker_widget.h"
32 #include "table/sprites.h"
36 #include "safeguards.h"
38 static std::vector
<PickerCallbacks
*> &GetPickerCallbacks()
40 static std::vector
<PickerCallbacks
*> callbacks
;
44 PickerCallbacks::PickerCallbacks(const std::string
&ini_group
) : ini_group(ini_group
)
46 GetPickerCallbacks().push_back(this);
49 PickerCallbacks::~PickerCallbacks()
51 auto &callbacks
= GetPickerCallbacks();
52 callbacks
.erase(std::find(callbacks
.begin(), callbacks
.end(), this));
56 * Load favourites of a picker from config.
57 * @param ini IniFile to load to.
58 * @param callbacks Picker to load.
60 static void PickerLoadConfig(const IniFile
&ini
, PickerCallbacks
&callbacks
)
62 const IniGroup
*group
= ini
.GetGroup(callbacks
.ini_group
);
63 if (group
== nullptr) return;
65 callbacks
.saved
.clear();
66 for (const IniItem
&item
: group
->items
) {
67 std::array
<uint8_t, 4> grfid_buf
;
69 std::string_view str
= item
.name
;
71 /* Try reading "<grfid>|<localid>" */
72 auto grfid_pos
= str
.find('|');
73 if (grfid_pos
== std::string_view::npos
) continue;
75 std::string_view grfid_str
= str
.substr(0, grfid_pos
);
76 if (!ConvertHexToBytes(grfid_str
, grfid_buf
)) continue;
78 str
= str
.substr(grfid_pos
+ 1);
79 uint32_t grfid
= grfid_buf
[0] | (grfid_buf
[1] << 8) | (grfid_buf
[2] << 16) | (grfid_buf
[3] << 24);
81 auto [ptr
, err
] = std::from_chars(str
.data(), str
.data() + str
.size(), localid
);
83 if (err
== std::errc
{} && ptr
== str
.data() + str
.size()) {
84 callbacks
.saved
.insert({grfid
, localid
, 0, 0});
90 * Save favourites of a picker to config.
91 * @param ini IniFile to save to.
92 * @param callbacks Picker to save.
94 static void PickerSaveConfig(IniFile
&ini
, const PickerCallbacks
&callbacks
)
96 IniGroup
&group
= ini
.GetOrCreateGroup(callbacks
.ini_group
);
99 for (const PickerItem
&item
: callbacks
.saved
) {
100 std::string key
= fmt::format("{:08X}|{}", BSWAP32(item
.grfid
), item
.local_id
);
101 group
.CreateItem(key
);
106 * Load favourites of all registered Pickers from config.
107 * @param ini IniFile to load to.
109 void PickerLoadConfig(const IniFile
&ini
)
111 for (auto *cb
: GetPickerCallbacks()) PickerLoadConfig(ini
, *cb
);
115 * Save favourites of all registered Pickers to config.
116 * @param ini IniFile to save to.
118 void PickerSaveConfig(IniFile
&ini
)
120 for (const auto *cb
: GetPickerCallbacks()) PickerSaveConfig(ini
, *cb
);
123 /** Sort classes by id. */
124 static bool ClassIDSorter(int const &a
, int const &b
)
129 /** Filter classes by class name. */
130 static bool ClassTagNameFilter(int const *item
, PickerFilterData
&filter
)
133 filter
.AddLine(GetString(filter
.callbacks
->GetClassName(*item
)));
134 return filter
.GetState();
137 /** Sort types by id. */
138 static bool TypeIDSorter(PickerItem
const &a
, PickerItem
const &b
)
140 int r
= a
.class_index
- b
.class_index
;
141 if (r
== 0) r
= a
.index
- b
.index
;
145 /** Filter types by class name. */
146 static bool TypeTagNameFilter(PickerItem
const *item
, PickerFilterData
&filter
)
149 filter
.AddLine(GetString(filter
.callbacks
->GetTypeName(item
->class_index
, item
->index
)));
150 return filter
.GetState();
153 static const std::initializer_list
<PickerClassList::SortFunction
* const> _class_sorter_funcs
= { &ClassIDSorter
}; ///< Sort functions of the #PickerClassList
154 static const std::initializer_list
<PickerClassList::FilterFunction
* const> _class_filter_funcs
= { &ClassTagNameFilter
}; ///< Filter functions of the #PickerClassList.
155 static const std::initializer_list
<PickerTypeList::SortFunction
* const> _type_sorter_funcs
= { TypeIDSorter
}; ///< Sort functions of the #PickerTypeList.
156 static const std::initializer_list
<PickerTypeList::FilterFunction
* const> _type_filter_funcs
= { TypeTagNameFilter
}; ///< Filter functions of the #PickerTypeList.
158 PickerWindow::PickerWindow(WindowDesc
&desc
, Window
*parent
, int window_number
, PickerCallbacks
&callbacks
) : PickerWindowBase(desc
, parent
), callbacks(callbacks
),
159 class_editbox(EDITBOX_MAX_SIZE
* MAX_CHAR_LENGTH
, EDITBOX_MAX_SIZE
),
160 type_editbox(EDITBOX_MAX_SIZE
* MAX_CHAR_LENGTH
, EDITBOX_MAX_SIZE
)
162 this->window_number
= window_number
;
164 /* Init of nested tree is deferred.
165 * PickerWindow::ConstructWindow must be called by the inheriting window. */
168 void PickerWindow::ConstructWindow()
170 this->CreateNestedTree();
172 /* Test if pickers should be active.*/
173 bool isActive
= this->callbacks
.IsActive();
175 /* Functionality depends on widgets being present, not window class. */
176 this->has_class_picker
= isActive
&& this->GetWidget
<NWidgetBase
>(WID_PW_CLASS_LIST
) != nullptr && this->callbacks
.HasClassChoice();
177 this->has_type_picker
= isActive
&& this->GetWidget
<NWidgetBase
>(WID_PW_TYPE_MATRIX
) != nullptr;
179 if (this->has_class_picker
) {
180 this->GetWidget
<NWidgetCore
>(WID_PW_CLASS_LIST
)->tool_tip
= this->callbacks
.GetClassTooltip();
182 this->querystrings
[WID_PW_CLASS_FILTER
] = &this->class_editbox
;
184 if (auto *nwid
= this->GetWidget
<NWidgetStacked
>(WID_PW_CLASS_SEL
); nwid
!= nullptr) {
185 /* Check the container orientation. MakeNWidgets adds an additional NWID_VERTICAL container so we check the grand-parent. */
186 bool is_vertical
= (nwid
->parent
->parent
->type
== NWID_VERTICAL
);
187 nwid
->SetDisplayedPlane(is_vertical
? SZSP_HORIZONTAL
: SZSP_VERTICAL
);
191 this->class_editbox
.cancel_button
= QueryString::ACTION_CLEAR
;
192 this->class_string_filter
.SetFilterTerm(this->class_editbox
.text
.buf
);
193 this->class_string_filter
.callbacks
= &this->callbacks
;
195 this->classes
.SetListing(this->callbacks
.class_last_sorting
);
196 this->classes
.SetFiltering(this->callbacks
.class_last_filtering
);
197 this->classes
.SetSortFuncs(_class_sorter_funcs
);
198 this->classes
.SetFilterFuncs(_class_filter_funcs
);
200 /* Update saved type information. */
201 this->callbacks
.saved
= this->callbacks
.UpdateSavedItems(this->callbacks
.saved
);
203 /* Clear used type information. */
204 this->callbacks
.used
.clear();
206 if (this->has_type_picker
) {
207 /* Populate used type information. */
208 this->callbacks
.FillUsedItems(this->callbacks
.used
);
210 SetWidgetDisabledState(WID_PW_MODE_ALL
, !this->callbacks
.HasClassChoice());
212 this->GetWidget
<NWidgetCore
>(WID_PW_TYPE_ITEM
)->tool_tip
= this->callbacks
.GetTypeTooltip();
214 auto *matrix
= this->GetWidget
<NWidgetMatrix
>(WID_PW_TYPE_MATRIX
);
215 matrix
->SetScrollbar(this->GetScrollbar(WID_PW_TYPE_SCROLL
));
217 this->querystrings
[WID_PW_TYPE_FILTER
] = &this->type_editbox
;
219 if (auto *nwid
= this->GetWidget
<NWidgetStacked
>(WID_PW_TYPE_SEL
); nwid
!= nullptr) {
220 /* Check the container orientation. MakeNWidgets adds an additional NWID_VERTICAL container so we check the grand-parent. */
221 bool is_vertical
= (nwid
->parent
->parent
->type
== NWID_VERTICAL
);
222 nwid
->SetDisplayedPlane(is_vertical
? SZSP_HORIZONTAL
: SZSP_VERTICAL
);
226 this->type_editbox
.cancel_button
= QueryString::ACTION_CLEAR
;
227 this->type_string_filter
.SetFilterTerm(this->type_editbox
.text
.buf
);
228 this->type_string_filter
.callbacks
= &this->callbacks
;
230 this->types
.SetListing(this->callbacks
.type_last_sorting
);
231 this->types
.SetFiltering(this->callbacks
.type_last_filtering
);
232 this->types
.SetSortFuncs(_type_sorter_funcs
);
233 this->types
.SetFilterFuncs(_type_filter_funcs
);
235 this->FinishInitNested(this->window_number
);
237 this->InvalidateData(PFI_CLASS
| PFI_TYPE
| PFI_POSITION
| PFI_VALIDATE
);
240 void PickerWindow::Close(int data
)
242 this->callbacks
.Close(data
);
243 this->PickerWindowBase::Close(data
);
246 void PickerWindow::UpdateWidgetSize(WidgetID widget
, Dimension
&size
, const Dimension
&padding
, Dimension
&fill
, Dimension
&resize
)
250 case WID_PW_CLASS_LIST
:
251 resize
.height
= GetCharacterHeight(FS_NORMAL
) + padding
.height
;
252 size
.height
= 5 * resize
.height
;
256 case WID_PW_TYPE_MATRIX
:
257 /* At least two items wide. */
258 size
.width
+= resize
.width
;
259 fill
.width
= resize
.width
;
262 /* Resizing in X direction only at blob size, but at pixel level in Y. */
267 case WID_PW_TYPE_ITEM
:
268 size
.width
= ScaleGUITrad(PREVIEW_WIDTH
) + WidgetDimensions::scaled
.fullbevel
.Horizontal();
269 size
.height
= ScaleGUITrad(PREVIEW_HEIGHT
) + WidgetDimensions::scaled
.fullbevel
.Vertical();
274 void PickerWindow::DrawWidget(const Rect
&r
, WidgetID widget
) const
278 case WID_PW_CLASS_LIST
: {
279 Rect ir
= r
.Shrink(WidgetDimensions::scaled
.matrix
);
280 const int selected
= this->callbacks
.GetSelectedClass();
281 const auto vscroll
= this->GetScrollbar(WID_PW_CLASS_SCROLL
);
282 const int y_step
= this->GetWidget
<NWidgetResizeBase
>(widget
)->resize_y
;
283 auto [first
, last
] = vscroll
->GetVisibleRangeIterators(this->classes
);
284 for (auto it
= first
; it
!= last
; ++it
) {
285 DrawString(ir
, this->callbacks
.GetClassName(*it
), *it
== selected
? TC_WHITE
: TC_BLACK
);
292 case WID_PW_TYPE_ITEM
: {
293 assert(this->GetWidget
<NWidgetBase
>(widget
)->GetParentWidget
<NWidgetMatrix
>()->GetCurrentElement() < static_cast<int>(this->types
.size()));
294 const auto &item
= this->types
[this->GetWidget
<NWidgetBase
>(widget
)->GetParentWidget
<NWidgetMatrix
>()->GetCurrentElement()];
296 DrawPixelInfo tmp_dpi
;
297 Rect ir
= r
.Shrink(WidgetDimensions::scaled
.bevel
);
298 if (FillDrawPixelInfo(&tmp_dpi
, ir
)) {
299 AutoRestoreBackup
dpi_backup(_cur_dpi
, &tmp_dpi
);
300 int x
= (ir
.Width() - ScaleSpriteTrad(PREVIEW_WIDTH
)) / 2 + ScaleSpriteTrad(PREVIEW_LEFT
);
301 int y
= (ir
.Height() + ScaleSpriteTrad(PREVIEW_HEIGHT
)) / 2 - ScaleSpriteTrad(PREVIEW_BOTTOM
);
303 this->callbacks
.DrawType(x
, y
, item
.class_index
, item
.index
);
304 if (this->callbacks
.saved
.contains(item
)) {
305 DrawSprite(SPR_BLOT
, PALETTE_TO_YELLOW
, 0, 0);
307 if (this->callbacks
.used
.contains(item
)) {
308 DrawSprite(SPR_BLOT
, PALETTE_TO_GREEN
, ir
.Width() - GetSpriteSize(SPR_BLOT
).width
, 0);
312 if (!this->callbacks
.IsTypeAvailable(item
.class_index
, item
.index
)) {
313 GfxFillRect(ir
, GetColourGradient(COLOUR_GREY
, SHADE_DARKER
), FILLRECT_CHECKER
);
318 case WID_PW_TYPE_NAME
:
319 DrawString(r
, this->callbacks
.GetTypeName(this->callbacks
.GetSelectedClass(), this->callbacks
.GetSelectedType()), TC_ORANGE
, SA_CENTER
);
324 void PickerWindow::OnResize()
326 if (this->has_class_picker
) {
327 this->GetScrollbar(WID_PW_CLASS_SCROLL
)->SetCapacityFromWidget(this, WID_PW_CLASS_LIST
);
331 void PickerWindow::OnClick(Point pt
, WidgetID widget
, int)
335 case WID_PW_CLASS_LIST
: {
336 const auto vscroll
= this->GetWidget
<NWidgetScrollbar
>(WID_PW_CLASS_SCROLL
);
337 auto it
= vscroll
->GetScrolledItemFromWidget(this->classes
, pt
.y
, this, WID_PW_CLASS_LIST
);
338 if (it
== this->classes
.end()) return;
340 if (this->callbacks
.GetSelectedClass() != *it
|| HasBit(this->callbacks
.mode
, PFM_ALL
)) {
341 ClrBit(this->callbacks
.mode
, PFM_ALL
); // Disable showing all.
342 this->callbacks
.SetSelectedClass(*it
);
343 this->InvalidateData(PFI_TYPE
| PFI_POSITION
| PFI_VALIDATE
);
345 if (_settings_client
.sound
.click_beep
) SndPlayFx(SND_15_BEEP
);
346 CloseWindowById(WC_SELECT_STATION
, 0);
350 case WID_PW_MODE_ALL
:
351 case WID_PW_MODE_USED
:
352 case WID_PW_MODE_SAVED
:
353 ToggleBit(this->callbacks
.mode
, widget
- WID_PW_MODE_ALL
);
354 if (!this->IsWidgetDisabled(WID_PW_MODE_ALL
) && HasBit(this->callbacks
.mode
, widget
- WID_PW_MODE_ALL
)) {
355 /* Enabling used or saved filters automatically enables all. */
356 SetBit(this->callbacks
.mode
, PFM_ALL
);
358 this->InvalidateData(PFI_CLASS
| PFI_TYPE
| PFI_POSITION
);
362 case WID_PW_TYPE_ITEM
: {
363 int sel
= this->GetWidget
<NWidgetBase
>(widget
)->GetParentWidget
<NWidgetMatrix
>()->GetCurrentElement();
364 assert(sel
< (int)this->types
.size());
365 const auto &item
= this->types
[sel
];
368 auto it
= this->callbacks
.saved
.find(item
);
369 if (it
== std::end(this->callbacks
.saved
)) {
370 this->callbacks
.saved
.insert(item
);
372 this->callbacks
.saved
.erase(it
);
374 this->InvalidateData(PFI_TYPE
);
378 if (this->callbacks
.IsTypeAvailable(item
.class_index
, item
.index
)) {
379 this->callbacks
.SetSelectedClass(item
.class_index
);
380 this->callbacks
.SetSelectedType(item
.index
);
381 this->InvalidateData(PFI_POSITION
);
383 if (_settings_client
.sound
.click_beep
) SndPlayFx(SND_15_BEEP
);
384 CloseWindowById(WC_SELECT_STATION
, 0);
390 void PickerWindow::OnInvalidateData(int data
, bool gui_scope
)
392 if (!gui_scope
) return;
394 if ((data
& PFI_CLASS
) != 0) this->classes
.ForceRebuild();
395 if ((data
& PFI_TYPE
) != 0) this->types
.ForceRebuild();
397 this->BuildPickerClassList();
398 if ((data
& PFI_VALIDATE
) != 0) this->EnsureSelectedClassIsValid();
399 if ((data
& PFI_POSITION
) != 0) this->EnsureSelectedClassIsVisible();
401 this->BuildPickerTypeList();
402 if ((data
& PFI_VALIDATE
) != 0) this->EnsureSelectedTypeIsValid();
403 if ((data
& PFI_POSITION
) != 0) this->EnsureSelectedTypeIsVisible();
405 if (this->has_type_picker
) {
406 SetWidgetLoweredState(WID_PW_MODE_ALL
, HasBit(this->callbacks
.mode
, PFM_ALL
));
407 SetWidgetLoweredState(WID_PW_MODE_USED
, HasBit(this->callbacks
.mode
, PFM_USED
));
408 SetWidgetLoweredState(WID_PW_MODE_SAVED
, HasBit(this->callbacks
.mode
, PFM_SAVED
));
412 EventState
PickerWindow::OnHotkey(int hotkey
)
415 case PCWHK_FOCUS_FILTER_BOX
:
416 /* Cycle between the two edit boxes. */
417 if (this->has_type_picker
&& (this->nested_focus
== nullptr || this->nested_focus
->index
!= WID_PW_TYPE_FILTER
)) {
418 this->SetFocusedWidget(WID_PW_TYPE_FILTER
);
419 } else if (this->has_class_picker
&& (this->nested_focus
== nullptr || this->nested_focus
->index
!= WID_PW_CLASS_FILTER
)) {
420 this->SetFocusedWidget(WID_PW_CLASS_FILTER
);
422 SetFocusedWindow(this);
426 return ES_NOT_HANDLED
;
430 void PickerWindow::OnEditboxChanged(WidgetID wid
)
433 case WID_PW_CLASS_FILTER
:
434 this->class_string_filter
.SetFilterTerm(this->class_editbox
.text
.buf
);
435 this->classes
.SetFilterState(!class_string_filter
.IsEmpty());
436 this->InvalidateData(PFI_CLASS
);
439 case WID_PW_TYPE_FILTER
:
440 this->type_string_filter
.SetFilterTerm(this->type_editbox
.text
.buf
);
441 this->types
.SetFilterState(!type_string_filter
.IsEmpty());
442 this->InvalidateData(PFI_TYPE
);
450 /** Builds the filter list of classes. */
451 void PickerWindow::BuildPickerClassList()
453 if (!this->classes
.NeedRebuild()) return;
455 int count
= this->callbacks
.GetClassCount();
457 this->classes
.clear();
458 this->classes
.reserve(count
);
460 bool filter_used
= HasBit(this->callbacks
.mode
, PFM_USED
);
461 bool filter_saved
= HasBit(this->callbacks
.mode
, PFM_SAVED
);
462 for (int i
= 0; i
< count
; i
++) {
463 if (this->callbacks
.GetClassName(i
) == INVALID_STRING_ID
) continue;
464 if (filter_used
&& std::none_of(std::begin(this->callbacks
.used
), std::end(this->callbacks
.used
), [i
](const PickerItem
&item
) { return item
.class_index
== i
; })) continue;
465 if (filter_saved
&& std::none_of(std::begin(this->callbacks
.saved
), std::end(this->callbacks
.saved
), [i
](const PickerItem
&item
) { return item
.class_index
== i
; })) continue;
466 this->classes
.emplace_back(i
);
469 this->classes
.Filter(this->class_string_filter
);
470 this->classes
.RebuildDone();
471 this->classes
.Sort();
473 if (!this->has_class_picker
) return;
474 this->GetScrollbar(WID_PW_CLASS_SCROLL
)->SetCount(this->classes
.size());
477 void PickerWindow::EnsureSelectedClassIsValid()
479 int class_index
= this->callbacks
.GetSelectedClass();
480 if (std::binary_search(std::begin(this->classes
), std::end(this->classes
), class_index
)) return;
482 if (!this->classes
.empty()) {
483 class_index
= this->classes
.front();
485 /* Classes can be empty if filters are enabled, find the first usable class. */
486 int count
= this->callbacks
.GetClassCount();
487 for (int i
= 0; i
< count
; i
++) {
488 if (this->callbacks
.GetClassName(i
) == INVALID_STRING_ID
) continue;
494 this->callbacks
.SetSelectedClass(class_index
);
495 this->types
.ForceRebuild();
498 void PickerWindow::EnsureSelectedClassIsVisible()
500 if (!this->has_class_picker
) return;
501 if (this->classes
.empty()) return;
503 auto it
= std::find(std::begin(this->classes
), std::end(this->classes
), this->callbacks
.GetSelectedClass());
504 if (it
== std::end(this->classes
)) return;
506 int pos
= static_cast<int>(std::distance(std::begin(this->classes
), it
));
507 this->GetScrollbar(WID_PW_CLASS_SCROLL
)->ScrollTowards(pos
);
510 void PickerWindow::RefreshUsedTypeList()
512 if (!this->has_type_picker
) return;
514 this->callbacks
.used
.clear();
515 this->callbacks
.FillUsedItems(this->callbacks
.used
);
516 this->InvalidateData(PFI_TYPE
);
519 /** Builds the filter list of types. */
520 void PickerWindow::BuildPickerTypeList()
522 if (!this->types
.NeedRebuild()) return;
526 bool show_all
= HasBit(this->callbacks
.mode
, PFM_ALL
);
527 bool filter_used
= HasBit(this->callbacks
.mode
, PFM_USED
);
528 bool filter_saved
= HasBit(this->callbacks
.mode
, PFM_SAVED
);
529 int cls_id
= this->callbacks
.GetSelectedClass();
532 /* Showing used items. May also be filtered by saved items. */
533 this->types
.reserve(this->callbacks
.used
.size());
534 for (const PickerItem
&item
: this->callbacks
.used
) {
535 if (!show_all
&& item
.class_index
!= cls_id
) continue;
536 if (this->callbacks
.GetTypeName(item
.class_index
, item
.index
) == INVALID_STRING_ID
) continue;
537 this->types
.emplace_back(item
);
539 } else if (filter_saved
) {
540 /* Showing only saved items. */
541 this->types
.reserve(this->callbacks
.saved
.size());
542 for (const PickerItem
&item
: this->callbacks
.saved
) {
543 /* The used list may contain items that aren't currently loaded, skip these. */
544 if (item
.class_index
== -1) continue;
545 if (!show_all
&& item
.class_index
!= cls_id
) continue;
546 if (this->callbacks
.GetTypeName(item
.class_index
, item
.index
) == INVALID_STRING_ID
) continue;
547 this->types
.emplace_back(item
);
549 } else if (show_all
) {
550 /* Reserve enough space for everything. */
552 for (int class_index
: this->classes
) total
+= this->callbacks
.GetTypeCount(class_index
);
553 this->types
.reserve(total
);
554 /* Add types in all classes. */
555 for (int class_index
: this->classes
) {
556 int count
= this->callbacks
.GetTypeCount(class_index
);
557 for (int i
= 0; i
< count
; i
++) {
558 if (this->callbacks
.GetTypeName(class_index
, i
) == INVALID_STRING_ID
) continue;
559 this->types
.emplace_back(this->callbacks
.GetPickerItem(class_index
, i
));
563 /* Add types in only the selected class. */
564 if (cls_id
>= 0 && cls_id
< this->callbacks
.GetClassCount()) {
565 int count
= this->callbacks
.GetTypeCount(cls_id
);
566 this->types
.reserve(count
);
567 for (int i
= 0; i
< count
; i
++) {
568 if (this->callbacks
.GetTypeName(cls_id
, i
) == INVALID_STRING_ID
) continue;
569 this->types
.emplace_back(this->callbacks
.GetPickerItem(cls_id
, i
));
574 this->types
.Filter(this->type_string_filter
);
575 this->types
.RebuildDone();
578 if (!this->has_type_picker
) return;
579 this->GetWidget
<NWidgetMatrix
>(WID_PW_TYPE_MATRIX
)->SetCount(static_cast<int>(this->types
.size()));
582 void PickerWindow::EnsureSelectedTypeIsValid()
584 int class_index
= this->callbacks
.GetSelectedClass();
585 int index
= this->callbacks
.GetSelectedType();
586 if (std::any_of(std::begin(this->types
), std::end(this->types
), [class_index
, index
](const auto &item
) { return item
.class_index
== class_index
&& item
.index
== index
; })) return;
588 if (!this->types
.empty()) {
589 class_index
= this->types
.front().class_index
;
590 index
= this->types
.front().index
;
592 /* Types can be empty if filters are enabled, find the first usable type. */
593 int count
= this->callbacks
.GetTypeCount(class_index
);
594 for (int i
= 0; i
< count
; i
++) {
595 if (this->callbacks
.GetTypeName(class_index
, i
) == INVALID_STRING_ID
) continue;
600 this->callbacks
.SetSelectedClass(class_index
);
601 this->callbacks
.SetSelectedType(index
);
604 void PickerWindow::EnsureSelectedTypeIsVisible()
606 if (!this->has_type_picker
) return;
607 if (this->types
.empty()) {
608 this->GetWidget
<NWidgetMatrix
>(WID_PW_TYPE_MATRIX
)->SetClicked(-1);
612 int class_index
= this->callbacks
.GetSelectedClass();
613 int index
= this->callbacks
.GetSelectedType();
615 auto it
= std::find_if(std::begin(this->types
), std::end(this->types
), [class_index
, index
](const auto &item
) { return item
.class_index
== class_index
&& item
.index
== index
; });
616 if (it
== std::end(this->types
)) return;
618 int pos
= static_cast<int>(std::distance(std::begin(this->types
), it
));
619 this->GetWidget
<NWidgetMatrix
>(WID_PW_TYPE_MATRIX
)->SetClicked(pos
);
622 /** Create nested widgets for the class picker widgets. */
623 std::unique_ptr
<NWidgetBase
> MakePickerClassWidgets()
625 static constexpr NWidgetPart picker_class_widgets
[] = {
626 NWidget(NWID_SELECTION
, INVALID_COLOUR
, WID_PW_CLASS_SEL
),
627 NWidget(NWID_VERTICAL
),
628 NWidget(WWT_PANEL
, COLOUR_DARK_GREEN
),
629 NWidget(WWT_EDITBOX
, COLOUR_DARK_GREEN
, WID_PW_CLASS_FILTER
), SetMinimalSize(144, 0), SetPadding(2), SetFill(1, 0), SetDataTip(STR_LIST_FILTER_OSKTITLE
, STR_LIST_FILTER_TOOLTIP
),
631 NWidget(NWID_HORIZONTAL
),
632 NWidget(WWT_PANEL
, COLOUR_DARK_GREEN
),
633 NWidget(WWT_MATRIX
, COLOUR_GREY
, WID_PW_CLASS_LIST
), SetFill(1, 1), SetResize(1, 1), SetPadding(WidgetDimensions::unscaled
.picker
),
634 SetMatrixDataTip(1, 0, STR_NULL
), SetScrollbar(WID_PW_CLASS_SCROLL
),
636 NWidget(NWID_VSCROLLBAR
, COLOUR_DARK_GREEN
, WID_PW_CLASS_SCROLL
),
642 return MakeNWidgets(picker_class_widgets
, nullptr);
645 /** Create nested widgets for the type picker widgets. */
646 std::unique_ptr
<NWidgetBase
> MakePickerTypeWidgets()
648 static constexpr NWidgetPart picker_type_widgets
[] = {
649 NWidget(NWID_SELECTION
, INVALID_COLOUR
, WID_PW_TYPE_SEL
),
650 NWidget(NWID_VERTICAL
),
651 NWidget(WWT_PANEL
, COLOUR_DARK_GREEN
),
652 NWidget(WWT_EDITBOX
, COLOUR_DARK_GREEN
, WID_PW_TYPE_FILTER
), SetPadding(2), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_LIST_FILTER_OSKTITLE
, STR_LIST_FILTER_TOOLTIP
),
654 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
655 NWidget(WWT_TEXTBTN
, COLOUR_DARK_GREEN
, WID_PW_MODE_ALL
), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_PICKER_MODE_ALL
, STR_PICKER_MODE_ALL_TOOLTIP
),
656 NWidget(WWT_TEXTBTN
, COLOUR_DARK_GREEN
, WID_PW_MODE_USED
), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_PICKER_MODE_USED
, STR_PICKER_MODE_USED_TOOLTIP
),
657 NWidget(WWT_TEXTBTN
, COLOUR_DARK_GREEN
, WID_PW_MODE_SAVED
), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_PICKER_MODE_SAVED
, STR_PICKER_MODE_SAVED_TOOLTIP
),
659 NWidget(NWID_HORIZONTAL
),
660 NWidget(WWT_PANEL
, COLOUR_DARK_GREEN
), SetScrollbar(WID_PW_TYPE_SCROLL
),
661 NWidget(NWID_MATRIX
, COLOUR_DARK_GREEN
, WID_PW_TYPE_MATRIX
), SetPIP(0, 2, 0), SetPadding(WidgetDimensions::unscaled
.picker
),
662 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_PW_TYPE_ITEM
), SetScrollbar(WID_PW_TYPE_SCROLL
),
666 NWidget(NWID_VSCROLLBAR
, COLOUR_DARK_GREEN
, WID_PW_TYPE_SCROLL
),
668 NWidget(NWID_HORIZONTAL
),
669 NWidget(WWT_PANEL
, COLOUR_DARK_GREEN
),
670 NWidget(WWT_EMPTY
, INVALID_COLOUR
, WID_PW_TYPE_NAME
), SetPadding(WidgetDimensions::unscaled
.framerect
), SetResize(1, 0), SetFill(1, 0), SetMinimalTextLines(1, 0),
672 NWidget(WWT_RESIZEBOX
, COLOUR_DARK_GREEN
, WID_PW_TYPE_RESIZE
),
678 return MakeNWidgets(picker_type_widgets
, nullptr);