Fix: server menu tooltip shouldn't show language info (#12955)
[openttd-github.git] / src / picker_gui.cpp
blobf947709cea316e5ce9862d2b9b236d0283bff81a
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 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;
214 } else {
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)
244 switch (widget) {
245 /* Class picker */
246 case WID_PW_CLASS_LIST:
247 resize.height = GetCharacterHeight(FS_NORMAL) + padding.height;
248 size.height = 5 * resize.height;
249 break;
251 /* Type picker */
252 case WID_PW_TYPE_MATRIX:
253 /* At least two items wide. */
254 size.width += resize.width;
255 fill.width = resize.width;
256 fill.height = 1;
258 /* Resizing in X direction only at blob size, but at pixel level in Y. */
259 resize.height = 1;
260 break;
262 /* Type picker */
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();
266 break;
270 void PickerWindow::DrawWidget(const Rect &r, WidgetID widget) const
272 switch (widget) {
273 /* Class picker */
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);
282 ir.top += y_step;
284 break;
287 /* Type picker */
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);
311 break;
314 case WID_PW_TYPE_NAME:
315 DrawString(r, this->callbacks.GetTypeName(this->callbacks.GetSelectedClass(), this->callbacks.GetSelectedType()), TC_ORANGE, SA_CENTER);
316 break;
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)
329 switch (widget) {
330 /* Class Picker */
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);
343 break;
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);
355 break;
357 /* Type Picker */
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];
363 if (_ctrl_pressed) {
364 auto it = this->callbacks.saved.find(item);
365 if (it == std::end(this->callbacks.saved)) {
366 this->callbacks.saved.insert(item);
367 } else {
368 this->callbacks.saved.erase(it);
370 this->InvalidateData(PFI_TYPE);
371 break;
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);
381 break;
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)
410 switch (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);
419 return ES_HANDLED;
421 default:
422 return ES_NOT_HANDLED;
426 void PickerWindow::OnEditboxChanged(WidgetID wid)
428 switch (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);
433 break;
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);
439 break;
441 default:
442 break;
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();
480 } else {
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;
485 class_index = i;
486 break;
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;
520 this->types.clear();
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();
527 if (filter_used) {
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. */
547 int total = 0;
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));
558 } else {
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();
572 this->types.Sort();
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;
587 } else {
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;
592 index = i;
593 break;
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);
605 return;
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),
626 EndContainer(),
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),
631 EndContainer(),
632 NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_PW_CLASS_SCROLL),
633 EndContainer(),
634 EndContainer(),
635 EndContainer(),
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),
649 EndContainer(),
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),
654 EndContainer(),
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),
659 EndContainer(),
660 EndContainer(),
661 EndContainer(),
662 NWidget(NWID_VSCROLLBAR, COLOUR_DARK_GREEN, WID_PW_TYPE_SCROLL),
663 EndContainer(),
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),
667 EndContainer(),
668 NWidget(WWT_RESIZEBOX, COLOUR_DARK_GREEN, WID_PW_TYPE_RESIZE),
669 EndContainer(),
670 EndContainer(),
671 EndContainer(),
674 return MakeNWidgets(picker_type_widgets, nullptr);