-
Notifications
You must be signed in to change notification settings - Fork 6
/
item_list.go
261 lines (228 loc) · 7.57 KB
/
item_list.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
package control
import (
"sort"
"strconv"
"strings"
"fmt"
"reflect"
)
// IDer is an object that can embed a list.
type IDer interface {
ID() string
}
// IDSetter is an interface for an item that sets an id.
type IDSetter interface {
SetID(id string)
}
// ItemListI is the interface for all controls that display a list of ListItems.
type ItemListI interface {
AddItem(label string, value ...interface{}) ListItemI
AddItemAt(index int, label string, value ...interface{})
AddListItemAt(index int, item ListItemI)
AddListItems(items ...interface{})
GetItemAt(index int) ListItemI
ListItems() []ListItemI
Clear()
RemoveItemAt(index int)
Len() int
GetItem(id string) (foundItem ListItemI)
GetItemByValue(value interface{}) (id string, foundItem ListItemI)
reindex(start int)
}
// ItemList manages a list of ListItemI list items. ItemList is designed to be embedded in another structure, and will
// turn that object into a manager of list items. ItemList will manage the id's of the items in its list, you do not
// have control of that, and it needs to manage those ids in order to efficiently manage the selection process.
type ItemList struct {
owner IDer
items []ListItemI
}
// NewItemList creates a new item list. "owner" is the object that has the list embedded in it, and must be
// an IDer.
func NewItemList(owner IDer) ItemList {
return ItemList{owner: owner}
}
// AddItem adds the given item to the end of the list. The value is optional, but should only be one or zero values.
func (l *ItemList) AddItem(label string, value ...interface{}) ListItemI {
i := NewListItem(label, value...)
l.AddListItemAt(-1, i)
return i
}
// AddItemAt adds the item at the given index. If the index is negative, it counts from the end. If the index is
// -1 or bigger than the number of items, it adds it to the end. If the index is zero, or is negative and smaller than
// the negative value of the number of items, it adds to the beginning. This can be an expensive operation in a long
// hierarchical list, so use sparingly.
func (l *ItemList) AddItemAt(index int, label string, value ...interface{}) {
l.AddListItemAt(index, NewListItem(label, value...))
}
// AddItemAt adds the item at the given index. If the index is negative, it counts from the end. If the index is
// -1 or bigger than the number of items, it adds it to the end. If the index is zero, or is negative and smaller than
// the negative value of the number of items, it adds to the beginning. This can be an expensive operation in a long
// hierarchical list, so use sparingly.
func (l *ItemList) AddListItemAt(index int, item ListItemI) {
if index < 0 || index > len(l.items) {
index = len(l.items)
}
l.items = append(l.items, nil)
copy(l.items[index+1:], l.items[index:])
l.items[index] = item
l.reindex(index)
}
// AddListItems adds one or more objects to the end of the list. items should be a list of ListItemI,
// ItemLister, ItemIDer, Labeler or Stringer types. This function can accept one or more lists of items, or
// single items
func (l *ItemList) AddListItems(items ...interface{}) {
var start int
if items == nil {
return
}
for _,item := range items {
kind := reflect.TypeOf(item).Kind()
if kind == reflect.Array || kind == reflect.Slice {
listValue := reflect.ValueOf(item)
for i := 0; i < listValue.Len(); i++ {
itemI := listValue.Index(i).Interface()
l.addListItem(itemI)
}
} else {
l.addListItem(item)
}
}
l.reindex(start)
}
// Private function to add an interface item to the end of the list. Will need to be reindexed eventually.
func (l *ItemList) addListItem(item interface{}) {
switch v := item.(type) {
case ListItemI:
l.items = append(l.items, v)
case ItemLister:
item := NewItemFromItemLister(v)
l.items = append(l.items, item)
case ItemIDer:
item := NewItemFromItemIDer(v)
l.items = append(l.items, item)
case Labeler:
item := NewItemFromLabeler(v)
l.items = append(l.items, item)
case fmt.Stringer:
item := NewItemFromStringer(v)
l.items = append(l.items, item)
default:
panic("Unknown object type")
}
}
// reindex is internal and should get called whenever an item gets added to the list out of order or an id changes.
func (l *ItemList) reindex(start int) {
if l.owner.ID() == "" || l.items == nil || len(l.items) == 0 || start >= len(l.items) {
return
}
for i := start; i < len(l.items); i++ {
id := l.owner.ID() + "_" + strconv.Itoa(i)
l.items[i].SetID(id)
}
}
// GetItemAt retrieves an item by index.
func (l *ItemList) GetItemAt(index int) ListItemI {
if index >= len(l.items) {
return nil
}
return l.items[index]
}
// ListItems returns a slice of the ListItemI items, in the order they were added or arranged.
func (l *ItemList) ListItems() []ListItemI {
return l.items
}
// Clear removes all the items from the list.
func (l *ItemList) Clear() {
l.items = nil
}
// RemoveItemAt removes an item at the given index.
func (l *ItemList) RemoveItemAt(index int) {
if index < 0 || index >= len(l.items) {
panic("Index out of range.")
}
l.items = append(l.items[:index], l.items[index+1:]...)
}
// Len returns the length of the item list at the current level. In other words, it does not count items in sublists.
func (l *ItemList) Len() int {
if l.items == nil {
return 0
}
return len(l.items)
}
// GetItem recursively searches for and returns the item corresponding to the given id. Since we are managing the
// id numbers, we can efficiently find the item. Note that if you add items to the list, the ids may change.
func (l *ItemList) GetItem(id string) (foundItem ListItemI) {
if l.items == nil {
return nil
}
parts := strings.SplitN(id, "_", 3) // first item is our own id, 2nd is id from the list, 3rd is a level beyond the list
l1Id, err := strconv.Atoi(parts[1])
if err != nil || l1Id < 0 {
panic("Bad id")
}
var countParts int
if countParts = len(parts); countParts <= 1 || l1Id >= len(l.items) {
return nil
}
item := l.items[l1Id]
if countParts == 2 {
return item
}
return item.GetItem(parts[1] + "_" + parts[2])
}
// GetItemByValue recursively searches the list to find the item with the given value.
// It starts with the current list, and if not found, will search in sublists.
func (l *ItemList) GetItemByValue(value interface{}) (id string, foundItem ListItemI) {
if l.items == nil || len(l.items) == 0 {
return "", nil
}
for _, foundItem = range l.items {
v := foundItem.Value()
if v == value {
id = foundItem.ID()
return
}
}
for _, item := range l.items {
id, foundItem = item.GetItemByValue(value)
if foundItem != nil {
return
}
}
return "", nil
}
// SortIds sorts a list of auto-generated ids in numerical and hierarchical order.
// This is normally just called by the framework.
func SortIds(ids []string) {
if len(ids) > 1 {
sort.Sort(IdSlice(ids))
}
}
// IdSlice is a slice of string ids, and is used to sort a list of ids
// that the item list uses.
type IdSlice []string
func (p IdSlice) Len() int { return len(p) }
func (p IdSlice) Less(i, j int) bool {
// First ones are always the main control id, and should be equal
vals1 := strings.SplitN(p[i], "_", 2)
vals2 := strings.SplitN(p[j], "_", 2)
if vals1[0] != vals2[0] {
panic("The first part of an id should be equal when sorting.")
}
for {
vals1 = strings.SplitN(vals1[1], "_", 2)
vals2 = strings.SplitN(vals2[1], "_", 2)
i1, _ := strconv.Atoi(vals1[0])
i2, _ := strconv.Atoi(vals2[0])
if i1 < i2 {
return true
} else if i1 > i2 {
return false
} else if len(vals1) < len(vals2) {
return true
} else if len(vals1) > len(vals2) || len(vals2) <= 1 {
return false
}
}
}
func (p IdSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }