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 if (this->has_type_picker
) {
201 /* Update used and saved type information. */
202 this->callbacks
.saved
= this->callbacks
.UpdateSavedItems(this->callbacks
.saved
);
203 this->callbacks
.used
.clear();
204 this->callbacks
.FillUsedItems(this->callbacks
.used
);
206 SetWidgetDisabledState(WID_PW_MODE_ALL
, !this->callbacks
.HasClassChoice());
208 this->GetWidget
<NWidgetCore
>(WID_PW_TYPE_ITEM
)->tool_tip
= this->callbacks
.GetTypeTooltip();
210 auto *matrix
= this->GetWidget
<NWidgetMatrix
>(WID_PW_TYPE_MATRIX
);
211 matrix
->SetScrollbar(this->GetScrollbar(WID_PW_TYPE_SCROLL
));
213 this->querystrings
[WID_PW_TYPE_FILTER
] = &this->type_editbox
;
215 if (auto *nwid
= this->GetWidget
<NWidgetStacked
>(WID_PW_TYPE_SEL
); nwid
!= nullptr) {
216 /* Check the container orientation. MakeNWidgets adds an additional NWID_VERTICAL container so we check the grand-parent. */
217 bool is_vertical
= (nwid
->parent
->parent
->type
== NWID_VERTICAL
);
218 nwid
->SetDisplayedPlane(is_vertical
? SZSP_HORIZONTAL
: SZSP_VERTICAL
);
222 this->type_editbox
.cancel_button
= QueryString::ACTION_CLEAR
;
223 this->type_string_filter
.SetFilterTerm(this->type_editbox
.text
.buf
);
224 this->type_string_filter
.callbacks
= &this->callbacks
;
226 this->types
.SetListing(this->callbacks
.type_last_sorting
);
227 this->types
.SetFiltering(this->callbacks
.type_last_filtering
);
228 this->types
.SetSortFuncs(_type_sorter_funcs
);
229 this->types
.SetFilterFuncs(_type_filter_funcs
);
231 this->FinishInitNested(this->window_number
);
233 this->InvalidateData(PFI_CLASS
| PFI_TYPE
| PFI_POSITION
| PFI_VALIDATE
);
236 void PickerWindow::Close(int data
)
238 this->callbacks
.Close(data
);
239 this->PickerWindowBase::Close(data
);
242 void PickerWindow::UpdateWidgetSize(WidgetID widget
, Dimension
&size
, const Dimension
&padding
, Dimension
&fill
, Dimension
&resize
)
246 case WID_PW_CLASS_LIST
:
247 resize
.height
= GetCharacterHeight(FS_NORMAL
) + padding
.height
;
248 size
.height
= 5 * resize
.height
;
252 case WID_PW_TYPE_MATRIX
:
253 /* At least two items wide. */
254 size
.width
+= resize
.width
;
255 fill
.width
= resize
.width
;
258 /* Resizing in X direction only at blob size, but at pixel level in Y. */
263 case WID_PW_TYPE_ITEM
:
264 size
.width
= ScaleGUITrad(PREVIEW_WIDTH
) + WidgetDimensions::scaled
.fullbevel
.Horizontal();
265 size
.height
= ScaleGUITrad(PREVIEW_HEIGHT
) + WidgetDimensions::scaled
.fullbevel
.Vertical();
270 void PickerWindow::DrawWidget(const Rect
&r
, WidgetID widget
) const
274 case WID_PW_CLASS_LIST
: {
275 Rect ir
= r
.Shrink(WidgetDimensions::scaled
.matrix
);
276 const int selected
= this->callbacks
.GetSelectedClass();
277 const auto vscroll
= this->GetScrollbar(WID_PW_CLASS_SCROLL
);
278 const int y_step
= this->GetWidget
<NWidgetResizeBase
>(widget
)->resize_y
;
279 auto [first
, last
] = vscroll
->GetVisibleRangeIterators(this->classes
);
280 for (auto it
= first
; it
!= last
; ++it
) {
281 DrawString(ir
, this->callbacks
.GetClassName(*it
), *it
== selected
? TC_WHITE
: TC_BLACK
);
288 case WID_PW_TYPE_ITEM
: {
289 assert(this->GetWidget
<NWidgetBase
>(widget
)->GetParentWidget
<NWidgetMatrix
>()->GetCurrentElement() < static_cast<int>(this->types
.size()));
290 const auto &item
= this->types
[this->GetWidget
<NWidgetBase
>(widget
)->GetParentWidget
<NWidgetMatrix
>()->GetCurrentElement()];
292 DrawPixelInfo tmp_dpi
;
293 Rect ir
= r
.Shrink(WidgetDimensions::scaled
.bevel
);
294 if (FillDrawPixelInfo(&tmp_dpi
, ir
)) {
295 AutoRestoreBackup
dpi_backup(_cur_dpi
, &tmp_dpi
);
296 int x
= (ir
.Width() - ScaleSpriteTrad(PREVIEW_WIDTH
)) / 2 + ScaleSpriteTrad(PREVIEW_LEFT
);
297 int y
= (ir
.Height() + ScaleSpriteTrad(PREVIEW_HEIGHT
)) / 2 - ScaleSpriteTrad(PREVIEW_BOTTOM
);
299 this->callbacks
.DrawType(x
, y
, item
.class_index
, item
.index
);
300 if (this->callbacks
.saved
.contains(item
)) {
301 DrawSprite(SPR_BLOT
, PALETTE_TO_YELLOW
, 0, 0);
303 if (this->callbacks
.used
.contains(item
)) {
304 DrawSprite(SPR_BLOT
, PALETTE_TO_GREEN
, ir
.Width() - GetSpriteSize(SPR_BLOT
).width
, 0);
308 if (!this->callbacks
.IsTypeAvailable(item
.class_index
, item
.index
)) {
309 GfxFillRect(ir
, GetColourGradient(COLOUR_GREY
, SHADE_DARKER
), FILLRECT_CHECKER
);
314 case WID_PW_TYPE_NAME
:
315 DrawString(r
, this->callbacks
.GetTypeName(this->callbacks
.GetSelectedClass(), this->callbacks
.GetSelectedType()), TC_ORANGE
, SA_CENTER
);
320 void PickerWindow::OnResize()
322 if (this->has_class_picker
) {
323 this->GetScrollbar(WID_PW_CLASS_SCROLL
)->SetCapacityFromWidget(this, WID_PW_CLASS_LIST
);
327 void PickerWindow::OnClick(Point pt
, WidgetID widget
, int)
331 case WID_PW_CLASS_LIST
: {
332 const auto vscroll
= this->GetWidget
<NWidgetScrollbar
>(WID_PW_CLASS_SCROLL
);
333 auto it
= vscroll
->GetScrolledItemFromWidget(this->classes
, pt
.y
, this, WID_PW_CLASS_LIST
);
334 if (it
== this->classes
.end()) return;
336 if (this->callbacks
.GetSelectedClass() != *it
|| HasBit(this->callbacks
.mode
, PFM_ALL
)) {
337 ClrBit(this->callbacks
.mode
, PFM_ALL
); // Disable showing all.
338 this->callbacks
.SetSelectedClass(*it
);
339 this->InvalidateData(PFI_TYPE
| PFI_POSITION
| PFI_VALIDATE
);
341 if (_settings_client
.sound
.click_beep
) SndPlayFx(SND_15_BEEP
);
342 CloseWindowById(WC_SELECT_STATION
, 0);
346 case WID_PW_MODE_ALL
:
347 case WID_PW_MODE_USED
:
348 case WID_PW_MODE_SAVED
:
349 ToggleBit(this->callbacks
.mode
, widget
- WID_PW_MODE_ALL
);
350 if (!this->IsWidgetDisabled(WID_PW_MODE_ALL
) && HasBit(this->callbacks
.mode
, widget
- WID_PW_MODE_ALL
)) {
351 /* Enabling used or saved filters automatically enables all. */
352 SetBit(this->callbacks
.mode
, PFM_ALL
);
354 this->InvalidateData(PFI_CLASS
| PFI_TYPE
| PFI_POSITION
);
358 case WID_PW_TYPE_ITEM
: {
359 int sel
= this->GetWidget
<NWidgetBase
>(widget
)->GetParentWidget
<NWidgetMatrix
>()->GetCurrentElement();
360 assert(sel
< (int)this->types
.size());
361 const auto &item
= this->types
[sel
];
364 auto it
= this->callbacks
.saved
.find(item
);
365 if (it
== std::end(this->callbacks
.saved
)) {
366 this->callbacks
.saved
.insert(item
);
368 this->callbacks
.saved
.erase(it
);
370 this->InvalidateData(PFI_TYPE
);
374 if (this->callbacks
.IsTypeAvailable(item
.class_index
, item
.index
)) {
375 this->callbacks
.SetSelectedClass(item
.class_index
);
376 this->callbacks
.SetSelectedType(item
.index
);
377 this->InvalidateData(PFI_POSITION
);
379 if (_settings_client
.sound
.click_beep
) SndPlayFx(SND_15_BEEP
);
380 CloseWindowById(WC_SELECT_STATION
, 0);
386 void PickerWindow::OnInvalidateData(int data
, bool gui_scope
)
388 if (!gui_scope
) return;
390 if ((data
& PFI_CLASS
) != 0) this->classes
.ForceRebuild();
391 if ((data
& PFI_TYPE
) != 0) this->types
.ForceRebuild();
393 this->BuildPickerClassList();
394 if ((data
& PFI_VALIDATE
) != 0) this->EnsureSelectedClassIsValid();
395 if ((data
& PFI_POSITION
) != 0) this->EnsureSelectedClassIsVisible();
397 this->BuildPickerTypeList();
398 if ((data
& PFI_VALIDATE
) != 0) this->EnsureSelectedTypeIsValid();
399 if ((data
& PFI_POSITION
) != 0) this->EnsureSelectedTypeIsVisible();
401 if (this->has_type_picker
) {
402 SetWidgetLoweredState(WID_PW_MODE_ALL
, HasBit(this->callbacks
.mode
, PFM_ALL
));
403 SetWidgetLoweredState(WID_PW_MODE_USED
, HasBit(this->callbacks
.mode
, PFM_USED
));
404 SetWidgetLoweredState(WID_PW_MODE_SAVED
, HasBit(this->callbacks
.mode
, PFM_SAVED
));
408 EventState
PickerWindow::OnHotkey(int hotkey
)
411 case PCWHK_FOCUS_FILTER_BOX
:
412 /* Cycle between the two edit boxes. */
413 if (this->has_type_picker
&& (this->nested_focus
== nullptr || this->nested_focus
->index
!= WID_PW_TYPE_FILTER
)) {
414 this->SetFocusedWidget(WID_PW_TYPE_FILTER
);
415 } else if (this->has_class_picker
&& (this->nested_focus
== nullptr || this->nested_focus
->index
!= WID_PW_CLASS_FILTER
)) {
416 this->SetFocusedWidget(WID_PW_CLASS_FILTER
);
418 SetFocusedWindow(this);
422 return ES_NOT_HANDLED
;
426 void PickerWindow::OnEditboxChanged(WidgetID wid
)
429 case WID_PW_CLASS_FILTER
:
430 this->class_string_filter
.SetFilterTerm(this->class_editbox
.text
.buf
);
431 this->classes
.SetFilterState(!class_string_filter
.IsEmpty());
432 this->InvalidateData(PFI_CLASS
);
435 case WID_PW_TYPE_FILTER
:
436 this->type_string_filter
.SetFilterTerm(this->type_editbox
.text
.buf
);
437 this->types
.SetFilterState(!type_string_filter
.IsEmpty());
438 this->InvalidateData(PFI_TYPE
);
446 /** Builds the filter list of classes. */
447 void PickerWindow::BuildPickerClassList()
449 if (!this->classes
.NeedRebuild()) return;
451 int count
= this->callbacks
.GetClassCount();
453 this->classes
.clear();
454 this->classes
.reserve(count
);
456 bool filter_used
= HasBit(this->callbacks
.mode
, PFM_USED
);
457 bool filter_saved
= HasBit(this->callbacks
.mode
, PFM_SAVED
);
458 for (int i
= 0; i
< count
; i
++) {
459 if (this->callbacks
.GetClassName(i
) == INVALID_STRING_ID
) continue;
460 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;
461 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;
462 this->classes
.emplace_back(i
);
465 this->classes
.Filter(this->class_string_filter
);
466 this->classes
.RebuildDone();
467 this->classes
.Sort();
469 if (!this->has_class_picker
) return;
470 this->GetScrollbar(WID_PW_CLASS_SCROLL
)->SetCount(this->classes
.size());
473 void PickerWindow::EnsureSelectedClassIsValid()
475 int class_index
= this->callbacks
.GetSelectedClass();
476 if (std::binary_search(std::begin(this->classes
), std::end(this->classes
), class_index
)) return;
478 if (!this->classes
.empty()) {
479 class_index
= this->classes
.front();
481 /* Classes can be empty if filters are enabled, find the first usable class. */
482 int count
= this->callbacks
.GetClassCount();
483 for (int i
= 0; i
< count
; i
++) {
484 if (this->callbacks
.GetClassName(i
) == INVALID_STRING_ID
) continue;
490 this->callbacks
.SetSelectedClass(class_index
);
491 this->types
.ForceRebuild();
494 void PickerWindow::EnsureSelectedClassIsVisible()
496 if (!this->has_class_picker
) return;
497 if (this->classes
.empty()) return;
499 auto it
= std::find(std::begin(this->classes
), std::end(this->classes
), this->callbacks
.GetSelectedClass());
500 if (it
== std::end(this->classes
)) return;
502 int pos
= static_cast<int>(std::distance(std::begin(this->classes
), it
));
503 this->GetScrollbar(WID_PW_CLASS_SCROLL
)->ScrollTowards(pos
);
506 void PickerWindow::RefreshUsedTypeList()
508 if (!this->has_type_picker
) return;
510 this->callbacks
.used
.clear();
511 this->callbacks
.FillUsedItems(this->callbacks
.used
);
512 this->InvalidateData(PFI_TYPE
);
515 /** Builds the filter list of types. */
516 void PickerWindow::BuildPickerTypeList()
518 if (!this->types
.NeedRebuild()) return;
522 bool show_all
= HasBit(this->callbacks
.mode
, PFM_ALL
);
523 bool filter_used
= HasBit(this->callbacks
.mode
, PFM_USED
);
524 bool filter_saved
= HasBit(this->callbacks
.mode
, PFM_SAVED
);
525 int cls_id
= this->callbacks
.GetSelectedClass();
528 /* Showing used items. May also be filtered by saved items. */
529 this->types
.reserve(this->callbacks
.used
.size());
530 for (const PickerItem
&item
: this->callbacks
.used
) {
531 if (!show_all
&& item
.class_index
!= cls_id
) continue;
532 if (this->callbacks
.GetTypeName(item
.class_index
, item
.index
) == INVALID_STRING_ID
) continue;
533 this->types
.emplace_back(item
);
535 } else if (filter_saved
) {
536 /* Showing only saved items. */
537 this->types
.reserve(this->callbacks
.saved
.size());
538 for (const PickerItem
&item
: this->callbacks
.saved
) {
539 /* The used list may contain items that aren't currently loaded, skip these. */
540 if (item
.class_index
== -1) continue;
541 if (!show_all
&& item
.class_index
!= cls_id
) continue;
542 if (this->callbacks
.GetTypeName(item
.class_index
, item
.index
) == INVALID_STRING_ID
) continue;
543 this->types
.emplace_back(item
);
545 } else if (show_all
) {
546 /* Reserve enough space for everything. */
548 for (int class_index
: this->classes
) total
+= this->callbacks
.GetTypeCount(class_index
);
549 this->types
.reserve(total
);
550 /* Add types in all classes. */
551 for (int class_index
: this->classes
) {
552 int count
= this->callbacks
.GetTypeCount(class_index
);
553 for (int i
= 0; i
< count
; i
++) {
554 if (this->callbacks
.GetTypeName(class_index
, i
) == INVALID_STRING_ID
) continue;
555 this->types
.emplace_back(this->callbacks
.GetPickerItem(class_index
, i
));
559 /* Add types in only the selected class. */
560 if (cls_id
>= 0 && cls_id
< this->callbacks
.GetClassCount()) {
561 int count
= this->callbacks
.GetTypeCount(cls_id
);
562 this->types
.reserve(count
);
563 for (int i
= 0; i
< count
; i
++) {
564 if (this->callbacks
.GetTypeName(cls_id
, i
) == INVALID_STRING_ID
) continue;
565 this->types
.emplace_back(this->callbacks
.GetPickerItem(cls_id
, i
));
570 this->types
.Filter(this->type_string_filter
);
571 this->types
.RebuildDone();
574 if (!this->has_type_picker
) return;
575 this->GetWidget
<NWidgetMatrix
>(WID_PW_TYPE_MATRIX
)->SetCount(static_cast<int>(this->types
.size()));
578 void PickerWindow::EnsureSelectedTypeIsValid()
580 int class_index
= this->callbacks
.GetSelectedClass();
581 int index
= this->callbacks
.GetSelectedType();
582 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;
584 if (!this->types
.empty()) {
585 class_index
= this->types
.front().class_index
;
586 index
= this->types
.front().index
;
588 /* Types can be empty if filters are enabled, find the first usable type. */
589 int count
= this->callbacks
.GetTypeCount(class_index
);
590 for (int i
= 0; i
< count
; i
++) {
591 if (this->callbacks
.GetTypeName(class_index
, i
) == INVALID_STRING_ID
) continue;
596 this->callbacks
.SetSelectedClass(class_index
);
597 this->callbacks
.SetSelectedType(index
);
600 void PickerWindow::EnsureSelectedTypeIsVisible()
602 if (!this->has_type_picker
) return;
603 if (this->types
.empty()) {
604 this->GetWidget
<NWidgetMatrix
>(WID_PW_TYPE_MATRIX
)->SetClicked(-1);
608 int class_index
= this->callbacks
.GetSelectedClass();
609 int index
= this->callbacks
.GetSelectedType();
611 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
; });
612 if (it
== std::end(this->types
)) return;
614 int pos
= static_cast<int>(std::distance(std::begin(this->types
), it
));
615 this->GetWidget
<NWidgetMatrix
>(WID_PW_TYPE_MATRIX
)->SetClicked(pos
);
618 /** Create nested widgets for the class picker widgets. */
619 std::unique_ptr
<NWidgetBase
> MakePickerClassWidgets()
621 static constexpr NWidgetPart picker_class_widgets
[] = {
622 NWidget(NWID_SELECTION
, INVALID_COLOUR
, WID_PW_CLASS_SEL
),
623 NWidget(NWID_VERTICAL
),
624 NWidget(WWT_PANEL
, COLOUR_DARK_GREEN
),
625 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
),
627 NWidget(NWID_HORIZONTAL
),
628 NWidget(WWT_PANEL
, COLOUR_DARK_GREEN
),
629 NWidget(WWT_MATRIX
, COLOUR_GREY
, WID_PW_CLASS_LIST
), SetFill(1, 1), SetResize(1, 1), SetPadding(WidgetDimensions::unscaled
.picker
),
630 SetMatrixDataTip(1, 0, STR_NULL
), SetScrollbar(WID_PW_CLASS_SCROLL
),
632 NWidget(NWID_VSCROLLBAR
, COLOUR_DARK_GREEN
, WID_PW_CLASS_SCROLL
),
638 return MakeNWidgets(picker_class_widgets
, nullptr);
641 /** Create nested widgets for the type picker widgets. */
642 std::unique_ptr
<NWidgetBase
> MakePickerTypeWidgets()
644 static constexpr NWidgetPart picker_type_widgets
[] = {
645 NWidget(NWID_SELECTION
, INVALID_COLOUR
, WID_PW_TYPE_SEL
),
646 NWidget(NWID_VERTICAL
),
647 NWidget(WWT_PANEL
, COLOUR_DARK_GREEN
),
648 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
),
650 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
651 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
),
652 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
),
653 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
),
655 NWidget(NWID_HORIZONTAL
),
656 NWidget(WWT_PANEL
, COLOUR_DARK_GREEN
), SetScrollbar(WID_PW_TYPE_SCROLL
),
657 NWidget(NWID_MATRIX
, COLOUR_DARK_GREEN
, WID_PW_TYPE_MATRIX
), SetPIP(0, 2, 0), SetPadding(WidgetDimensions::unscaled
.picker
),
658 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_PW_TYPE_ITEM
), SetScrollbar(WID_PW_TYPE_SCROLL
),
662 NWidget(NWID_VSCROLLBAR
, COLOUR_DARK_GREEN
, WID_PW_TYPE_SCROLL
),
664 NWidget(NWID_HORIZONTAL
),
665 NWidget(WWT_PANEL
, COLOUR_DARK_GREEN
),
666 NWidget(WWT_EMPTY
, INVALID_COLOUR
, WID_PW_TYPE_NAME
), SetPadding(WidgetDimensions::unscaled
.framerect
), SetResize(1, 0), SetFill(1, 0), SetMinimalTextLines(1, 0),
668 NWidget(WWT_RESIZEBOX
, COLOUR_DARK_GREEN
, WID_PW_TYPE_RESIZE
),
674 return MakeNWidgets(picker_type_widgets
, nullptr);