| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- // ================================================================================
- // == This file is a part of Turbo Badger. (C) 2011-2014, Emil Segerås ==
- // == See tb_core.h for more information. ==
- // ================================================================================
- #include "tb_select.h"
- #include "tb_menu_window.h"
- #include "tb_widgets_listener.h"
- #include "tb_language.h"
- #include "tb_tempbuffer.h"
- #include "tb_sort.h"
- namespace tb {
- // == Sort callback for sorting items ===================================================
- static int select_list_sort_cb(TBSelectItemSource *source, const int *a, const int *b)
- {
- int value = strcmp(source->GetItemString(*a), source->GetItemString(*b));
- return source->GetSort() == TB_SORT_DESCENDING ? -value : value;
- }
- // == TBSelectList ==============================================
- TBSelectList::TBSelectList()
- : m_value(-1)
- , m_list_is_invalid(false)
- , m_scroll_to_current(false)
- , m_header_lng_string_id(TBIDC("TBList.header"))
- , m_sort_callback(select_list_sort_cb)
- , m_ui_list_view(false)
- {
- SetSource(&m_default_source);
- SetIsFocusable(true);
- SetSkinBg(TBIDC("TBSelectList"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
- m_container.SetGravity(WIDGET_GRAVITY_ALL);
- m_container.SetRect(GetPaddingRect());
- AddChild(&m_container);
- m_layout.SetGravity(WIDGET_GRAVITY_ALL);
- m_layout.SetAxis(AXIS_Y);
- m_layout.SetSpacing(0);
- m_layout.SetLayoutPosition(LAYOUT_POSITION_LEFT_TOP);
- m_layout.SetLayoutDistributionPosition(LAYOUT_DISTRIBUTION_POSITION_LEFT_TOP);
- m_layout.SetLayoutSize(LAYOUT_SIZE_AVAILABLE);
- m_container.GetContentRoot()->AddChild(&m_layout);
- m_container.SetScrollMode(SCROLL_MODE_Y_AUTO);
- m_container.SetAdaptContentSize(true);
- }
- TBSelectList::~TBSelectList()
- {
- m_container.GetContentRoot()->RemoveChild(&m_layout);
- RemoveChild(&m_container);
- SetSource(nullptr);
- }
- void TBSelectList::OnSourceChanged()
- {
- InvalidateList();
- }
- void TBSelectList::OnItemChanged(int index)
- {
- if (m_list_is_invalid) // We're updating all widgets soon.
- return;
- TBWidget *old_widget = GetItemWidget(index);
- if (!old_widget) // We don't have this widget so we have nothing to update.
- return;
- // Replace the old widget representing the item, with a new one. Preserve its state.
- WIDGET_STATE old_state = old_widget->GetStateRaw();
- if (TBWidget *widget = CreateAndAddItemAfter(index, old_widget))
- widget->SetStateRaw(old_state);
- old_widget->GetParent()->RemoveChild(old_widget);
- delete old_widget;
- }
- void TBSelectList::OnItemAdded(int index)
- {
- if (m_list_is_invalid) // We're updating all widgets soon.
- return;
- // Sorting, filtering etc. makes it messy to handle dynamic addition of items.
- // Resort to invalidate the entire list (may even be faster anyway)
- InvalidateList();
- }
- void TBSelectList::OnItemRemoved(int index)
- {
- if (m_list_is_invalid) // We're updating all widgets soon.
- return;
- // Sorting, filtering etc. makes it messy to handle dynamic addition of items.
- // Resort to invalidate the entire list (may even be faster anyway)
- InvalidateList();
- }
- void TBSelectList::OnAllItemsRemoved()
- {
- InvalidateList();
- m_value = -1;
- }
- void TBSelectList::SetFilter(const char *filter)
- {
- TBStr new_filter;
- if (filter && *filter)
- new_filter.Set(filter);
- if (m_filter.Equals(new_filter))
- return;
- m_filter.Set(new_filter);
- InvalidateList();
- }
- void TBSelectList::SetHeaderString(const TBID& id)
- {
- if (m_header_lng_string_id == id)
- return;
- m_header_lng_string_id = id;
- InvalidateList();
- }
- void TBSelectList::InvalidateList()
- {
- if (m_list_is_invalid)
- return;
- m_list_is_invalid = true;
- Invalidate();
- }
- void TBSelectList::ValidateList()
- {
- if (!m_list_is_invalid)
- return;
- m_list_is_invalid = false;
- // FIX: Could delete and create only the changed items (faster filter change)
- // Remove old items
- while (TBWidget *child = m_layout.GetContentRoot()->GetFirstChild())
- {
- child->GetParent()->RemoveChild(child);
- delete child;
- }
- if (!m_source || !m_source->GetNumItems())
- return;
- // Create a sorted list of the items we should include using the current filter.
- TBTempBuffer sort_buf;
- if (!sort_buf.Reserve(m_source->GetNumItems() * sizeof(int)))
- return; // Out of memory
- int *sorted_index = (int *) sort_buf.GetData();
- // Populate the sorted index list
- int num_sorted_items = 0;
- for (int i = 0; i < m_source->GetNumItems(); i++)
- if (m_filter.IsEmpty() || m_source->Filter(i, m_filter))
- sorted_index[num_sorted_items++] = i;
- // Sort
- if (m_source->GetSort() != TB_SORT_NONE)
- insertion_sort<TBSelectItemSource*, int>(sorted_index, num_sorted_items, m_source, m_sort_callback);
- // Show header if we only show a subset of all items.
- // a single space filter is used as a dummy filter
- if (!m_filter.IsEmpty() && (!(m_filter.Length() == 1 && m_filter[0] == ' ')))
- {
- if (TBWidget *widget = new TBTextField())
- {
- TBStr str;
- str.SetFormatted(g_tb_lng->GetString(m_header_lng_string_id), num_sorted_items, m_source->GetNumItems());
- widget->SetText(str);
- widget->SetSkinBg(TBIDC("TBList.header"));
- widget->SetState(WIDGET_STATE_DISABLED, true);
- widget->SetGravity(WIDGET_GRAVITY_ALL);
- widget->data.SetInt(-1);
- m_layout.GetContentRoot()->AddChild(widget);
- }
- }
- // Create new items
- for (int i = 0; i < num_sorted_items; i++)
- CreateAndAddItemAfter(sorted_index[i], nullptr);
- SelectItem(m_value, true);
- // FIX: Should not scroll just because we update the list. Only automatically first time!
- m_scroll_to_current = true;
- TBWidgetEvent ev(EVENT_TYPE_CUSTOM);
- // TBIDC does not register the string with the UI system
- TBID refid("select_list_validation_end");
- ev.ref_id = refid;
- InvokeEvent(ev);
- }
- TBWidget *TBSelectList::CreateAndAddItemAfter(int index, TBWidget *reference)
- {
- if (TBWidget *widget = m_source->CreateItemWidget(index, this))
- {
- // Use item data as widget to index lookup
- widget->data.SetInt(index);
- m_layout.GetContentRoot()->AddChildRelative(widget, WIDGET_Z_REL_AFTER, reference);
- return widget;
- }
- return nullptr;
- }
- void TBSelectList::SetValue(int value)
- {
- if (value == m_value)
- return;
- if (!m_ui_list_view)
- SelectItem(m_value, false);
- m_value = value;
- SelectItem(m_value, true);
- ScrollToSelectedItem();
- TBWidgetEvent ev(EVENT_TYPE_CHANGED);
- if (TBWidget *widget = GetItemWidget(m_value))
- ev.ref_id = widget->GetID();
- InvokeEvent(ev);
- }
- TBID TBSelectList::GetItemID(int index) const
- {
- if (!m_source)
- return TBID();
- return m_source->GetItemID(index);
- }
- int TBSelectList::GetNumItems() const
- {
- if (!m_source)
- return 0;
- return m_source->GetNumItems();
- }
- TBID TBSelectList::GetSelectedItemID()
- {
- if (m_source && m_value >= 0 && m_value < m_source->GetNumItems())
- return m_source->GetItemID(m_value);
- return TBID();
- }
- void TBSelectList::SelectItem(int index, bool selected)
- {
- if (!m_ui_list_view)
- {
- if (TBWidget *widget = GetItemWidget(index))
- widget->SetState(WIDGET_STATE_SELECTED, selected);
- }
- }
- TBWidget *TBSelectList::GetItemWidget(int index)
- {
- if (index == -1)
- return nullptr;
- for (TBWidget *tmp = m_layout.GetContentRoot()->GetFirstChild(); tmp; tmp = tmp->GetNext())
- if (tmp->data.GetInt() == index)
- return tmp;
- return nullptr;
- }
- void TBSelectList::ScrollToSelectedItem()
- {
- if (m_list_is_invalid)
- {
- m_scroll_to_current = true;
- return;
- }
- m_scroll_to_current = false;
- if (TBWidget *widget = GetItemWidget(m_value))
- m_container.ScrollIntoView(widget->GetRect());
- else
- m_container.ScrollTo(0, 0);
- }
- void TBSelectList::OnSkinChanged()
- {
- m_container.SetRect(GetPaddingRect());
- }
- void TBSelectList::OnProcess()
- {
- ValidateList();
- }
- void TBSelectList::OnProcessAfterChildren()
- {
- if (m_scroll_to_current)
- ScrollToSelectedItem();
- }
- bool TBSelectList::OnEvent(const TBWidgetEvent &ev)
- {
- if (ev.type == EVENT_TYPE_CLICK && ev.target->GetParent() == m_layout.GetContentRoot())
- {
- // SetValue (EVENT_TYPE_CHANGED) might cause something to delete this (f.ex closing
- // the dropdown menu. We want to sent another event, so ensure we're still around.
- TBWidgetSafePointer this_widget(this);
- int index = ev.target->data.GetInt();
- if (!m_ui_list_view)
- SetValue(index);
- else
- {
- if (TBWidget *widget = GetItemWidget(index))
- {
- TBWidgetEvent change_ev(EVENT_TYPE_CUSTOM);
- // TBIDC does not register the string with the UI system
- TBID refid("select_list_selection_changed");
- change_ev.ref_id = refid;
- change_ev.modifierkeys = ev.modifierkeys;
- // forward to delegate
- widget->InvokeEvent(change_ev);
- }
- }
- // If we're still around, invoke the click event too.
- if (this_widget.Get())
- {
- TBSelectList *target_list = this;
- // If the parent window is a TBMenuWindow, we will iterate up the event destination
- // chain to find the top TBMenuWindow and invoke the event there.
- // That way events in submenus will reach the caller properly, and seem like it was
- // invoked on the top menu.
- TBWindow *window = GetParentWindow();
- while (TBMenuWindow *menu_win = TBSafeCast<TBMenuWindow>(window))
- {
- target_list = menu_win->GetList();
- window = menu_win->GetEventDestination()->GetParentWindow();
- }
- // Invoke the click event on the target list
- TBWidgetEvent ev(EVENT_TYPE_CLICK);
- if (TBWidget *widget = GetItemWidget(m_value))
- ev.ref_id = widget->GetID();
- target_list->InvokeEvent(ev);
- }
- return true;
- }
- else if (ev.type == EVENT_TYPE_KEY_DOWN)
- {
- if (ChangeValue(ev.special_key))
- return true;
- // Give the scroll container a chance to handle the key so it may
- // scroll. This matters if the list itself is focused instead of
- // some child view of any select item (since that would have passed
- // the container already)
- if (GetScrollContainer()->OnEvent(ev))
- return true;
- }
- return TBWidget::OnEvent(ev);
- }
- bool TBSelectList::ChangeValue(SPECIAL_KEY key)
- {
- if (m_ui_list_view || !m_source || !m_layout.GetContentRoot()->GetFirstChild())
- return false;
- bool forward;
- if (key == TB_KEY_HOME || key == TB_KEY_DOWN)
- forward = true;
- else if (key == TB_KEY_END || key == TB_KEY_UP)
- forward = false;
- else
- return false;
- TBWidget *item_root = m_layout.GetContentRoot();
- TBWidget *current = GetItemWidget(m_value);
- TBWidget *origin = nullptr;
- if (key == TB_KEY_HOME || (!current && key == TB_KEY_DOWN))
- current = item_root->GetFirstChild();
- else if (key == TB_KEY_END || (!current && key == TB_KEY_UP))
- current = item_root->GetLastChild();
- else
- origin = current;
- while (current)
- {
- if (current != origin && !current->GetDisabled())
- break;
- current = forward ? current->GetNext() : current->GetPrev();
- }
- // Select and focus what we found
- if (current)
- {
- SetValue(current->data.GetInt());
- return true;
- }
- return false;
- }
- bool TBSelectList::GetItemSelected(int index)
- {
- if (TBWidget *widget = GetItemWidget(index))
- return widget->GetState(WIDGET_STATE_SELECTED);
- return false;
- }
- void TBSelectList::SelectAllItems(bool select)
- {
- if (!m_source)
- return;
- for (int i = 0; i < m_source->GetNumItems(); i++)
- {
- SelectItem(i, select);
- }
- if (!select)
- m_value = -1;
- }
- // == TBSelectDropdown ==========================================
- TBSelectDropdown::TBSelectDropdown()
- : m_value(-1)
- {
- SetSource(&m_default_source);
- SetSkinBg(TBIDC("TBSelectDropdown"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
- m_arrow.SetSkinBg(TBIDC("TBSelectDropdown.arrow"), WIDGET_INVOKE_INFO_NO_CALLBACKS);
- GetContentRoot()->AddChild(&m_arrow);
- }
- TBSelectDropdown::~TBSelectDropdown()
- {
- GetContentRoot()->RemoveChild(&m_arrow);
- SetSource(nullptr);
- CloseWindow();
- }
- void TBSelectDropdown::OnSourceChanged()
- {
- m_value = -1;
- if (m_source && m_source->GetNumItems())
- SetValue(0);
- }
- void TBSelectDropdown::OnItemChanged(int index)
- {
- }
- void TBSelectDropdown::SetValue(int value)
- {
- if (value == m_value || !m_source)
- return;
- m_value = value;
- if (m_value < 0)
- SetText("");
- else if (m_value < m_source->GetNumItems())
- SetText(m_source->GetItemString(m_value));
- TBWidgetEvent ev(EVENT_TYPE_CHANGED);
- InvokeEvent(ev);
- }
- TBID TBSelectDropdown::GetSelectedItemID()
- {
- if (m_source && m_value >= 0 && m_value < m_source->GetNumItems())
- return m_source->GetItemID(m_value);
- return TBID();
- }
- void TBSelectDropdown::OpenWindow()
- {
- if (!m_source || !m_source->GetNumItems() || m_window_pointer.Get())
- return;
- if (TBMenuWindow *window = new TBMenuWindow(this, TBIDC("TBSelectDropdown.window")))
- {
- m_window_pointer.Set(window);
- window->SetSkinBg(TBIDC("TBSelectDropdown.window"));
- window->Show(m_source, TBPopupAlignment(), GetValue());
- }
- }
- void TBSelectDropdown::CloseWindow()
- {
- if (TBMenuWindow *window = GetMenuIfOpen())
- window->Close();
- }
- TBMenuWindow *TBSelectDropdown::GetMenuIfOpen() const
- {
- return TBSafeCast<TBMenuWindow>(m_window_pointer.Get());
- }
- bool TBSelectDropdown::OnEvent(const TBWidgetEvent &ev)
- {
- if (ev.target == this && ev.type == EVENT_TYPE_CLICK)
- {
- // Open the menu, or set the value and close it if already open (this will
- // happen when clicking by keyboard since that will call click on this button)
- if (TBMenuWindow *menu_window = GetMenuIfOpen())
- {
- TBWidgetSafePointer tmp(this);
- int value = menu_window->GetList()->GetValue();
- menu_window->Die();
- if (tmp.Get())
- SetValue(value);
- }
- else
- OpenWindow();
- return true;
- }
- else if (ev.target->GetID() == TBIDC("TBSelectDropdown.window") && ev.type == EVENT_TYPE_CLICK)
- {
- // Set the value of the clicked item
- if (TBMenuWindow *menu_window = GetMenuIfOpen())
- SetValue(menu_window->GetList()->GetValue());
- return true;
- }
- else if (ev.target == this && m_source && ev.IsKeyEvent())
- {
- if (TBMenuWindow *menu_window = GetMenuIfOpen())
- {
- // Redirect the key strokes to the list
- TBWidgetEvent redirected_ev(ev);
- return menu_window->GetList()->InvokeEvent(redirected_ev);
- }
- }
- return false;
- }
- } // namespace tb
|