// // Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #include #include #include #include "UI.h" #include "UIEvents.h" #include "UIListView.h" using namespace tb; namespace Atomic { class ListViewItemSource; class ListViewItemWidget; class ListViewItemWidget : public TBLayout { public: ListViewItemWidget(ListViewItem *item, ListViewItemSource *source, TBSelectItemViewer *sourceviewer, int index); virtual bool OnEvent(const TBWidgetEvent &ev); void UpdateText(const String& text) { if (textField_) textField_->SetText(text.CString()); } void UpdateIcon(const String& icon) { if (icon_) icon_->SetSkinBg(TBIDC(icon.CString())); } void UpdateTextSkin(const String& skin) { if (textField_) textField_->SetSkinBg(TBIDC(skin.CString())); } void SetExpanded(bool expanded) { if (expandBox_) expandBox_->SetValue(expanded ? 1 : 0); } private: TBCheckBox* expandBox_; TBTextField* textField_; TBSkinImage* icon_; ListViewItemSource *source_; TBSelectItemViewer *sourceviewer_; int index_; ListViewItem* item_; }; class ListViewItem : public TBGenericStringItem { bool expanded_; bool selected_; public: ListViewItem(const char *str, const TBID &id, const char* icon, ListViewItemSource* source) : TBGenericStringItem(str, id), source_(source), parent_(0), depth_(0), widget_(0), expanded_(false), icon_(icon), selected_(false) { } ListViewItem* AddChild(const char* text, const char* icon, const TBID &id); bool GetSelected() { return selected_; } void SetSelected(bool value) { selected_ = value; } bool GetExpanded() { return expanded_; } void GetChildren(PODVector& children, bool recursive = false) { children += children_; if (recursive) { for (unsigned i = 0; i < children_.Size(); i++) { children_[i]->GetChildren(children, recursive); } } } void SetExpanded(bool expanded) { if (widget_) widget_->SetExpanded(expanded); expanded_ = expanded; if (!expanded_) { //for (unsigned i = 0; i < children_.Size(); i ++) // children_[i]->SetExpanded(expanded); } else { if (parent_) parent_->SetExpanded(expanded_); } } void UpdateText(const String& text); void UpdateTextSkin(const String& skin); void UpdateIcon(const String& icon); ListViewItemSource* source_; ListViewItem* parent_; int depth_; PODVector children_; ListViewItemWidget* widget_; String icon_; String textSkin_; }; class ListViewItemSource : public TBSelectItemSourceList { public: UIListView* uiListView_; ListViewItemSource(UIListView* list) : uiListView_(list) {} virtual ~ListViewItemSource() {} virtual bool Filter(int index, const char *filter); virtual TBWidget *CreateItemWidget(int index, TBSelectItemViewer *viewer); }; // implementation void ListViewItem::UpdateText(const String& text) { str = text.CString(); if (widget_) widget_->UpdateText(text); } void ListViewItem::UpdateIcon(const String& icon) { icon_ = icon; if (widget_) widget_->UpdateIcon(icon); } void ListViewItem::UpdateTextSkin(const String& skin) { textSkin_ = skin; if (widget_) widget_->UpdateTextSkin(skin); } ListViewItem* ListViewItem::AddChild(const char *text, const char* icon, const TBID &id) { ListViewItem* child = new ListViewItem(text, id, icon, source_); child->parent_ = this; child->depth_ = depth_ + 1; // filter, alphabetically into source ListViewItem* insert = this; int parentIndex = -1; for (int i = 0; i < source_->GetNumItems(); i++) { if (source_->GetItem(i) == this) { parentIndex = i; break; } } for (int i = parentIndex + 1; i < source_->GetNumItems(); i++) { ListViewItem* item = source_->GetItem(i); if (item->depth_ <= depth_) break; if (item->depth_ != depth_ + 1) { insert = item; continue; } if (strcmp(item->str.CStr(), text) >= 0) break; insert = item; } for (int i = 0; i < source_->GetNumItems(); i++) { if (source_->GetItem(i) == insert) { source_->AddItem(child, i + 1); break; } } children_.Push(child); return child; } ListViewItemWidget::ListViewItemWidget(ListViewItem *item, ListViewItemSource *source, TBSelectItemViewer *sourceviewer, int index) : source_(source) , sourceviewer_(sourceviewer) , index_(index) , item_(item) , expandBox_(0) , textField_(0) , icon_(0) { SetLayoutDistribution(LAYOUT_DISTRIBUTION_GRAVITY); SetLayoutDistributionPosition(LAYOUT_DISTRIBUTION_POSITION_LEFT_TOP); SetPaintOverflowFadeout(false); SetCapturing(false); item_->widget_ = this; for (int i = 0; i < item->depth_; i++) { LayoutParams lp; lp.SetWidth(6); lp.SetHeight(4); TBWidget* spacer = new TBWidget(); spacer->SetLayoutParams(lp); GetContentRoot()->AddChild(spacer); } if (item_->children_.Size()) { expandBox_ = new TBCheckBox(); expandBox_->SetSkinBg(TBIDC("TBCheckBox.uilistview")); expandBox_->SetValue(item_->GetExpanded()); expandBox_->SetID(item->id); GetContentRoot()->AddChild(expandBox_); } else { LayoutParams lp; lp.SetWidth(12); lp.SetHeight(4); TBWidget* spacer = new TBWidget(); spacer->SetLayoutParams(lp); GetContentRoot()->AddChild(spacer); } if (item->icon_.Length()) { icon_ = new TBSkinImage(TBIDC(item->icon_.CString())); icon_->SetIgnoreInput(true); GetContentRoot()->AddChild(icon_); } TBFontDescription fd; fd.SetID(TBIDC("Vera")); fd.SetSize(11); TBTextField* tfield = textField_ = new TBTextField(); tfield->SetIgnoreInput(true); tfield->SetSkinBg(item->textSkin_.Length() ? TBIDC(item->textSkin_.CString()) : TBIDC("Folder")); tfield->SetText(item->str); tfield->SetFontDescription(fd); SetSkinBg(TBIDC("TBSelectItem")); GetContentRoot()->AddChild(tfield); SetID(item->id); } bool ListViewItemWidget::OnEvent(const TBWidgetEvent &ev) { if (ev.type == EVENT_TYPE_WHEEL) { return false; } if (ev.type == EVENT_TYPE_POINTER_DOWN || ev.type == EVENT_TYPE_RIGHT_POINTER_UP) { SetFocus(WIDGET_FOCUS_REASON_POINTER); TBWidget* parent = GetParent(); while (parent) { if (parent->IsOfType()) { TBWidgetEvent nev = ev; nev.ref_id = item_->id; parent->InvokeEvent(nev); break; } parent = parent->GetParent(); } return true; } // get clicks this way, not sure we want to if (ev.type == EVENT_TYPE_CLICK && ev.target == expandBox_ && ev.target->GetID() == item_->id) { item_->SetExpanded(!item_->GetExpanded()); source_->uiListView_->UpdateItemVisibility(); // want to bubble return false; } return TBLayout::OnEvent(ev); } bool ListViewItemSource::Filter(int index, const char *filter) { ListViewItem* item = GetItem(index); if (!item->parent_) return true; if (item->parent_->GetExpanded()) return true; return true; } TBWidget *ListViewItemSource::CreateItemWidget(int index, TBSelectItemViewer *viewer) { ListViewItem* item = GetItem(index); if (TBLayout *layout = new ListViewItemWidget(item, this, viewer, index)) return layout; return nullptr; } /* static int select_list_sort_cb(TBSelectItemSource *_source, const int *a, const int *b) { ListViewItemSource* source = (ListViewItemSource*) _source; ListViewItem* itemA = source->GetItem(*a); ListViewItem* itemB = source->GetItem(*b); int value; if (itemA->depth_ == itemB->depth_) { value = strcmp(source->GetItemString(*a), source->GetItemString(*b)); } else { value = itemA->depth_ > itemB->depth_ ? 1 : -1; } return source->GetSort() == TB_SORT_DESCENDING ? -value : value; } */ UIListView::UIListView(Context* context, bool createWidget) : UIWidget(context, createWidget), source_(0), itemLookupId_(0), multiSelect_(false) { rootList_ = new UISelectList(context); rootList_->SetUIListView(true); // dummy filter so filter is called rootList_->SetFilter(" "); widget_->SetGravity(WIDGET_GRAVITY_ALL); rootList_->SetGravity(UI_GRAVITY_ALL); source_ = new ListViewItemSource(this); rootList_->GetTBSelectList()->SetSource(source_); AddChild(rootList_); } UIListView::~UIListView() { } unsigned UIListView::AddRootItem(const String& text, const String& icon, const String& id) { ListViewItem* item = new ListViewItem(text.CString(), TBID(id.CString()), icon.CString(), source_); source_->AddItem(item); itemLookup_[itemLookupId_++] = item; return itemLookupId_ - 1; } void UIListView::SetItemText(const String& id, const String& text) { TBID tbid(id.CString()); for (int i = 0; i < source_->GetNumItems(); i++) { if (source_->GetItemID(i) == tbid) { ListViewItem* item = source_->GetItem(i); item->UpdateText(text); return; } } } void UIListView::SetItemTextSkin(const String& id, const String& skin) { TBID tbid(id.CString()); for (int i = 0; i < source_->GetNumItems(); i++) { if (source_->GetItemID(i) == tbid) { ListViewItem* item = source_->GetItem(i); item->UpdateTextSkin(skin); return; } } } void UIListView::SetItemIcon(const String& id, const String& icon) { TBID tbid(id.CString()); for (int i = 0; i < source_->GetNumItems(); i++) { if (source_->GetItemID(i) == tbid) { ListViewItem* item = source_->GetItem(i); item->UpdateIcon(icon); return; } } } void UIListView::DeleteItemByID(const String& id) { TBID tbid(id.CString()); for (int i = 0; i < source_->GetNumItems(); i++) { if (source_->GetItemID(i) == tbid) { ListViewItem* item = source_->GetItem(i); if (item->parent_) item->parent_->children_.Remove(item); PODVector children; item->GetChildren(children, true); for (unsigned j = 0; j < children.Size(); j++) { for (int k = 0; k < source_->GetNumItems(); k++) { if (children[j] == source_->GetItem(k)) { source_->DeleteItem(k); break; } } } source_->DeleteItem(i); rootList_->InvalidateList(); return; } } } unsigned UIListView::AddChildItem(unsigned parentItemID, const String& text, const String& icon, const String& id) { if (!itemLookup_.Contains(parentItemID)) return -1; ListViewItem* item = itemLookup_[parentItemID]; ListViewItem* child = item->AddChild(text.CString(), icon.CString(), TBID(id.CString())); itemLookup_[itemLookupId_++] = child; return itemLookupId_ - 1; } void UIListView::SetExpanded(unsigned itemID, bool value) { if (!itemLookup_.Contains(itemID)) return; itemLookup_[itemID]->SetExpanded(value); } bool UIListView::GetExpanded(unsigned itemID) { if (!itemLookup_.Contains(itemID)) return false; return itemLookup_[itemID]->GetExpanded(); } bool UIListView::GetExpandable(unsigned itemID) { if (!itemLookup_.Contains(itemID)) return false; return itemLookup_[itemID]->children_.Size() > 0; } void UIListView::DeleteAllItems() { itemLookup_.Clear(); source_->DeleteAllItems(); } void UIListView::SelectItemByID(const String& id, bool selected) { TBID tid = TBIDC(id.CString()); for (int i = 0; i < source_->GetNumItems(); i++) { ListViewItem* item = source_->GetItem(i); if (tid == item->id) { if (selected) { if (item->GetSelected()) return; item->SetSelected(selected); if (item->parent_) item->parent_->SetExpanded(true); SetValueFirstSelected(); UpdateItemVisibility(); ScrollToSelectedItem(); } else { if (!item->GetSelected()) return; item->SetSelected(false); UpdateItemVisibility(); } return; } } } void UIListView::UpdateItemVisibility() { for (int i = 0; i < source_->GetNumItems(); i++) { ListViewItem* item = source_->GetItem(i); if (!item->widget_) continue; item->widget_->SetVisibilility(WIDGET_VISIBILITY_VISIBLE); item->widget_->SetState(WIDGET_STATE_SELECTED, item->GetSelected()); ListViewItem* parent = item->parent_; while (parent) { if (!parent->GetExpanded()) break; parent = parent->parent_; } if (parent) item->widget_->SetVisibilility(WIDGET_VISIBILITY_GONE); } tb::TBScrollContainer* scroll = (tb::TBScrollContainer*) rootList_->GetInternalWidget()->GetFirstChild(); scroll->OnProcess(); } void UIListView::ScrollToSelectedItem() { if (rootList_.Null()) return; rootList_->ScrollToSelectedItem(); } void UIListView::SelectAllItems(bool select) { for (int i = 0; i < source_->GetNumItems(); i++) { ListViewItem* item = source_->GetItem(i); item->SetSelected(select); } } void UIListView::SetValueFirstSelected() { int index = -1; for (int i = 0; i < source_->GetNumItems(); i++) { ListViewItem* item = source_->GetItem(i); if (item->GetSelected()) { index = i; break; } } rootList_->SetValue(index); } void UIListView::SelectSingleItem(ListViewItem* item, bool expand) { bool dirty = !item->GetSelected(); if (!dirty) { for (unsigned i = 0; i < source_->GetNumItems(); i++) { ListViewItem* sitem = source_->GetItem(i); if (sitem != item && sitem->GetSelected()) { dirty = true; break; } } } if (!dirty) return; for (unsigned i = 0; i < source_->GetNumItems(); i++) { ListViewItem* sitem = source_->GetItem(i); if (sitem->GetSelected()) { sitem->SetSelected(false); SendItemSelectedChanged(sitem); } } if (expand) item->SetExpanded(true); item->SetSelected(true); UpdateItemVisibility(); SetValueFirstSelected(); ScrollToSelectedItem(); SendItemSelectedChanged(item); } void UIListView::Move(tb::SPECIAL_KEY key) { // selected index int index = -1; for (int i = 0; i < source_->GetNumItems(); i++) { ListViewItem* item = source_->GetItem(i); if (item->GetSelected()) { index = i; break; } } // nothing selected if (index == -1) return; if (key == TB_KEY_LEFT) { ListViewItem* item = source_->GetItem(index); if (item->children_.Size() > 0 && item->GetExpanded()) { item->SetExpanded(false); UpdateItemVisibility(); } else { key = TB_KEY_UP; } } if (key == TB_KEY_RIGHT) { ListViewItem* item = source_->GetItem(index); if (item->children_.Size() > 0 && !item->GetExpanded()) { item->SetExpanded(true); UpdateItemVisibility(); } else { key = TB_KEY_DOWN; } } if (key == TB_KEY_UP) { // can't go any further up list if (index == 0) return; for (int i = (int) (index - 1 ); i >= 0; i--) { ListViewItem* item = source_->GetItem(i); if (item->widget_ && item->widget_->GetVisibility() == WIDGET_VISIBILITY_VISIBLE) { SelectSingleItem(item, false); return; } } } if (key == TB_KEY_DOWN) { // can't go any further down list if (index == source_->GetNumItems() - 1) return; for (int i = index + 1; i < source_->GetNumItems(); i++) { ListViewItem* item = source_->GetItem(i); if (item->widget_ && item->widget_->GetVisibility() == WIDGET_VISIBILITY_VISIBLE) { SelectSingleItem(item, false); return; } } } } void UIListView::SendItemSelectedChanged(ListViewItem* item) { UI* ui = GetSubsystem(); VariantMap eventData; String refid; ui->GetTBIDString(item->id, refid); eventData[UIListViewSelectionChanged::P_REFID] = refid; eventData[UIListViewSelectionChanged::P_SELECTED] = item->GetSelected(); this->SendEvent(E_UILISTVIEWSELECTIONCHANGED, eventData); } bool UIListView::OnEvent(const tb::TBWidgetEvent &ev) { if (ev.type == EVENT_TYPE_KEY_UP ) { if (ev.special_key == TB_KEY_DOWN || ev.special_key == TB_KEY_UP || ev.special_key == TB_KEY_LEFT || ev.special_key == TB_KEY_RIGHT) { Move(ev.special_key); return true; } } if (ev.type == EVENT_TYPE_CUSTOM && ev.ref_id == TBIDC("select_list_validation_end")) { UpdateItemVisibility(); return true; } if (ev.type == EVENT_TYPE_CUSTOM && ev.ref_id == TBIDC("select_list_selection_changed")) { for (int i = 0; i < source_->GetNumItems(); i++) { ListViewItem* item = source_->GetItem(i); if (item->id == ev.target->GetID()) { bool multi = false; if (multiSelect_ && (ev.modifierkeys & TB_SHIFT || ev.modifierkeys & TB_CTRL || ev.modifierkeys & TB_SUPER)) multi = true; if (multi) { if (item->GetSelected()) { item->SetSelected(false); UpdateItemVisibility(); SendItemSelectedChanged(item); } else { item->SetSelected(true); UpdateItemVisibility(); SendItemSelectedChanged(item); } SetValueFirstSelected(); } else { SelectSingleItem(item, false); } return true; } } } if (ev.type == EVENT_TYPE_SHORTCUT) { return false; } return UIWidget::OnEvent(ev); } }