Update: Translations from eints
[openttd-github.git] / src / picker_gui.cpp
blob4f56485952ec805788dd0a9a00b398e729b14deb
1 /*
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/>.
6 */
8 /** @file picker_gui.cpp %File for dealing with picker windows */
10 #include "stdafx.h"
11 #include "core/backup_type.hpp"
12 #include "gui.h"
13 #include "hotkeys.h"
14 #include "ini_type.h"
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"
34 #include <charconv>
36 #include "safeguards.h"
38 static std::vector<PickerCallbacks *> &GetPickerCallbacks()
40 static std::vector<PickerCallbacks *> callbacks;
41 return 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));
55 /**
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);
80 uint16_t localid;
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});
89 /**
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);
97 group.Clear();
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)
126 return a < b;
129 /** Filter classes by class name. */
130 static bool ClassTagNameFilter(int const *item, PickerFilterData &filter)
132 filter.ResetState();
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;
142 return r < 0;
145 /** Filter types by class name. */
146 static bool TypeTagNameFilter(PickerItem const *item, PickerFilterData &filter)
148 filter.ResetState();
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;
183 } else {
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;
218 } else {
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)
248 switch (widget) {
249 /* Class picker */
250 case WID_PW_CLASS_LIST:
251 resize.height = GetCharacterHeight(FS_NORMAL) + padding.height;
252 size.height = 5 * resize.height;
253 break;
255 /* Type picker */
256 case WID_PW_TYPE_MATRIX:
257 /* At least two items wide. */
258 size.width += resize.width;
259 fill.width = resize.width;
260 fill.height = 1;
262 /* Resizing in X direction only at blob size, but at pixel level in Y. */
263 resize.height = 1;
264 break;
266 /* Type picker */
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();
270 break;
274 void PickerWindow::DrawWidget(const Rect &r, WidgetID widget) const
276 switch (widget) {
277 /* Class picker */
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);
286 ir.top += y_step;
288 break;
291 /* Type picker */
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);
315 break;
318 case WID_PW_TYPE_NAME:
319 DrawString(r, this->callbacks.GetTypeName(this->callbacks.GetSelectedClass(), this->callbacks.GetSelectedType()), TC_ORANGE, SA_CENTER);
320 break;
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)
333 switch (widget) {
334 /* Class Picker */
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);
347 break;
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);
359 break;
361 /* Type Picker */
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];
367 if (_ctrl_pressed) {
368 auto it = this->callbacks.saved.find(item);
369 if (it == std::end(this->callbacks.saved)) {
370 this->callbacks.saved.insert(item);
371 } else {
372 this->callbacks.saved.erase(it);
374 this->InvalidateData(PFI_TYPE);
375 break;
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);
385 break;
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)
414 switch (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);
423 return ES_HANDLED;
425 default:
426 return ES_NOT_HANDLED;
430 void PickerWindow::OnEditboxChanged(WidgetID wid)
432 switch (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);
437 break;
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);
443 break;
445 default:
446 break;
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();
484 } else {
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;
489 class_index = i;
490 break;
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;
524 this->types.clear();
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();
531 if (filter_used) {
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. */
551 int total = 0;
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));
562 } else {
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();
576 this->types.Sort();
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;
591 } else {
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;
596 index = i;
597 break;
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);
609 return;
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),
630 EndContainer(),
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),
635 EndContainer(),
636 NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_PW_CLASS_SCROLL),
637 EndContainer(),
638 EndContainer(),
639 EndContainer(),
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),
653 EndContainer(),
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),
658 EndContainer(),
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),
663 EndContainer(),
664 EndContainer(),
665 EndContainer(),
666 NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_PW_TYPE_SCROLL),
667 EndContainer(),
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),
671 EndContainer(),
672 NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN, WID_PW_TYPE_RESIZE),
673 EndContainer(),
674 EndContainer(),
675 EndContainer(),
678 return MakeNWidgets(picker_type_widgets, nullptr);