1 namespace System.Web.UI.WebControls {
2     using System;
3     using System.Collections;
4     using System.Collections.Generic;
5     using System.ComponentModel;
6     using System.Globalization;
7     using System.Text;
8     using System.Web.UI;
9 
10     public sealed class MenuItemCollection : ICollection, IStateManager {
11         private List<MenuItem> _list;
12         private MenuItem _owner;
13         private int _version;
14 
15         private bool _isTrackingViewState;
16 
17         private List<LogItem> _log;
18 
19 
MenuItemCollection()20         public MenuItemCollection() : this(null) {
21         }
22 
23 
MenuItemCollection(MenuItem owner)24         public MenuItemCollection(MenuItem owner) {
25             _owner = owner;
26             _list = new List<MenuItem>();
27         }
28 
29 
30         public int Count {
31             get {
32                 return _list.Count;
33             }
34         }
35 
36 
37         public bool IsSynchronized {
38             get {
39                 return ((ICollection)_list).IsSynchronized;
40             }
41         }
42 
43         private List<LogItem> Log {
44             get {
45                 if (_log == null) {
46                     _log = new List<LogItem>();
47                 }
48                 return _log;
49             }
50         }
51 
52 
53         public object SyncRoot {
54             get {
55                 return ((ICollection)_list).SyncRoot;
56             }
57         }
58 
59 
60         public MenuItem this[int index] {
61             get {
62                 return _list[index];
63             }
64         }
65 
66 
Add(MenuItem child)67         public void Add(MenuItem child) {
68             AddAt(_list.Count, child);
69         }
70 
71 
AddAt(int index, MenuItem child)72         public void AddAt(int index, MenuItem child) {
73             if (child == null) {
74                 throw new ArgumentNullException("child");
75             }
76 
77             if (child.Owner != null && child.Parent == null) {
78                 child.Owner.Items.Remove(child);
79             }
80             if (child.Parent != null) {
81                 child.Parent.ChildItems.Remove(child);
82             }
83 
84             if (_owner != null) {
85                 child.SetParent(_owner);
86                 child.SetOwner(_owner.Owner);
87             }
88 
89             _list.Insert(index, child);
90             _version++;
91 
92             if (_isTrackingViewState) {
93                 ((IStateManager)child).TrackViewState();
94                 child.SetDirty();
95             }
96             Log.Add(new LogItem(LogItemType.Insert, index, _isTrackingViewState));
97         }
98 
99 
Clear()100         public void Clear() {
101             if (this.Count == 0) return;
102             if (_owner != null) {
103                 Menu owner = _owner.Owner;
104                 if (owner != null) {
105                     MenuItem current = owner.SelectedItem;
106                     // Check if the selected item is under this collection
107                     while (current != null) {
108                         if (this.Contains(current)) {
109                             owner.SetSelectedItem(null);
110                             break;
111                         }
112                         current = current.Parent;
113                     }
114                 }
115             }
116             foreach (MenuItem item in _list) {
117                 item.SetParent(null);
118             }
119             _list.Clear();
120             _version++;
121             if (_isTrackingViewState) {
122                 // Clearing invalidates all previous log entries, so we can just clear them out and save some space
123                 Log.Clear();
124             }
125             Log.Add(new LogItem(LogItemType.Clear, 0, _isTrackingViewState));
126         }
127 
128 
CopyTo(Array array, int index)129         public void CopyTo(Array array, int index) {
130             if (!(array is MenuItem[])) {
131                 throw new ArgumentException(SR.GetString(SR.MenuItemCollection_InvalidArrayType), "array");
132             }
133             _list.CopyTo((MenuItem[])array, index);
134         }
135 
136 
CopyTo(MenuItem[] array, int index)137         public void CopyTo(MenuItem[] array, int index) {
138             _list.CopyTo(array, index);
139         }
140 
141 
Contains(MenuItem c)142         public bool Contains(MenuItem c) {
143             return _list.Contains(c);
144         }
145 
FindItem(string[] path, int pos)146         internal MenuItem FindItem(string[] path, int pos) {
147             if (pos == path.Length) {
148                 return _owner;
149             }
150 
151             string pathPart = TreeView.UnEscape(path[pos]);
152             for (int i = 0; i < Count; i++) {
153                 MenuItem node = _list[i];
154                 if (node.Value == pathPart) {
155                     return node.ChildItems.FindItem(path, pos + 1);
156                 }
157             }
158 
159             return null;
160         }
161 
162 
GetEnumerator()163         public IEnumerator GetEnumerator() {
164             return new MenuItemCollectionEnumerator(this);
165         }
166 
167 
IndexOf(MenuItem value)168         public int IndexOf(MenuItem value) {
169             return _list.IndexOf(value);
170         }
171 
172 
Remove(MenuItem value)173         public void Remove(MenuItem value) {
174             if (value == null) {
175                 throw new ArgumentNullException("value");
176             }
177 
178             int index = _list.IndexOf(value);
179             if (index != -1) {
180                 RemoveAt(index);
181             }
182         }
183 
184 
RemoveAt(int index)185         public void RemoveAt(int index) {
186             MenuItem item = _list[index];
187             Menu owner = item.Owner;
188             if (owner != null) {
189                 MenuItem current = owner.SelectedItem;
190                 // Check if the selected item is under this item
191                 while (current != null) {
192                     if (current == item) {
193                         owner.SetSelectedItem(null);
194                         break;
195                     }
196                     current = current.Parent;
197                 }
198             }
199             item.SetParent(null);
200 
201             _list.RemoveAt(index);
202             _version++;
203             Log.Add(new LogItem(LogItemType.Remove, index, _isTrackingViewState));
204         }
205 
SetDirty()206         internal void SetDirty() {
207             foreach (LogItem item in Log) {
208                 item.Tracked = true;
209             }
210             for (int i = 0; i < Count; i++) {
211                 this[i].SetDirty();
212             }
213         }
214 
215         #region IStateManager implementation
216 
217         /// <internalonly/>
218         bool IStateManager.IsTrackingViewState {
219             get {
220                 return _isTrackingViewState;
221             }
222         }
223 
224 
225         /// <internalonly/>
IStateManager.LoadViewState(object state)226         void IStateManager.LoadViewState(object state) {
227             object[] nodeState = (object[])state;
228             if (nodeState != null) {
229                 if (nodeState[0] != null) {
230                     string logString = (string)nodeState[0];
231                     // Process each log entry
232                     string[] items = logString.Split(',');
233                     for (int i = 0; i < items.Length; i++) {
234                         string[] parts = items[i].Split(':');
235                         LogItemType type = (LogItemType)Int32.Parse(parts[0], CultureInfo.InvariantCulture);
236                         int index = Int32.Parse(parts[1], CultureInfo.InvariantCulture);
237 
238                         if (type == LogItemType.Insert) {
239                             AddAt(index, new MenuItem());
240                         }
241                         else if (type == LogItemType.Remove) {
242                             RemoveAt(index);
243                         }
244                         else if (type == LogItemType.Clear) {
245                             Clear();
246                         }
247                     }
248                 }
249 
250                 for (int i = 0; i < nodeState.Length - 1; i++) {
251                     if ((nodeState[i + 1] != null) && (this[i] != null)) {
252                         ((IStateManager)this[i]).LoadViewState(nodeState[i + 1]);
253                     }
254                 }
255             }
256         }
257 
258 
259         /// <internalonly/>
IStateManager.SaveViewState()260         object IStateManager.SaveViewState() {
261             object[] nodes = new object[Count + 1];
262 
263             bool hasViewState = false;
264 
265             if ((_log != null) && (_log.Count > 0)) {
266                 // Construct a string representation of the log, delimiting entries with commas
267                 // and seperator command and index with a colon
268                 StringBuilder builder = new StringBuilder();
269                 int realLogCount = 0;
270                 for (int i = 0; i < _log.Count; i++) {
271                     LogItem item = (LogItem)_log[i];
272                     if (item.Tracked) {
273                         builder.Append((int)item.Type);
274                         builder.Append(":");
275                         builder.Append(item.Index);
276                         if (i < (_log.Count - 1)) {
277                             builder.Append(",");
278                         }
279 
280                         realLogCount++;
281                     }
282                 }
283 
284                 if (realLogCount > 0) {
285                     nodes[0] = builder.ToString();
286                     hasViewState = true;
287                 }
288             }
289 
290             for (int i = 0; i < Count; i++) {
291                 nodes[i + 1] = ((IStateManager)this[i]).SaveViewState();
292                 if (nodes[i + 1] != null) {
293                     hasViewState = true;
294                 }
295             }
296 
297             return (hasViewState ? nodes : null);
298         }
299 
300 
301         /// <internalonly/>
IStateManager.TrackViewState()302         void IStateManager.TrackViewState() {
303             _isTrackingViewState = true;
304             for (int i = 0; i < Count; i++) {
305                 ((IStateManager)this[i]).TrackViewState();
306             }
307         }
308         #endregion
309 
310         /// <devdoc>
311         ///     Convenience class for storing and using log entries.
312         /// </devdoc>
313         private class LogItem {
314             private LogItemType _type;
315             private int _index;
316             private bool _tracked;
317 
LogItem(LogItemType type, int index, bool tracked)318             public LogItem(LogItemType type, int index, bool tracked) {
319                 _type = type;
320                 _index = index;
321                 _tracked = tracked;
322             }
323 
324             public int Index {
325                 get {
326                     return _index;
327                 }
328             }
329 
330             public bool Tracked {
331                 get {
332                     return _tracked;
333                 }
334                 set {
335                     _tracked = value;
336                 }
337             }
338 
339             public LogItemType Type {
340                 get {
341                     return _type;
342                 }
343             }
344 
345         }
346 
347         /// <devdoc>
348         ///     Convenience enumeration for identifying log commands
349         /// </devdoc>
350         private enum LogItemType {
351             Insert = 0,
352             Remove = 1,
353             Clear = 2
354         }
355 
356         // This is a copy of the ArrayListEnumeratorSimple in ArrayList.cs
357         private class MenuItemCollectionEnumerator : IEnumerator {
358             private MenuItemCollection list;
359             private int index;
360             private int version;
361             private MenuItem currentElement;
362 
MenuItemCollectionEnumerator(MenuItemCollection list)363             internal MenuItemCollectionEnumerator(MenuItemCollection list) {
364                 this.list = list;
365                 this.index = -1;
366                 version = list._version;
367             }
368 
MoveNext()369             public bool MoveNext() {
370                 if (version != list._version)
371                     throw new InvalidOperationException(SR.GetString(SR.ListEnumVersionMismatch));
372 
373                 if (index < (list.Count - 1)) {
374                     index++;
375                     currentElement = list[index];
376                     return true;
377                 }
378                 else
379                     index = list.Count;
380                 return false;
381             }
382 
383             object IEnumerator.Current {
384                 get {
385                     return Current;
386                 }
387             }
388 
389             public MenuItem Current {
390                 get {
391                     if (index == -1)
392                         throw new InvalidOperationException(SR.GetString(SR.ListEnumCurrentOutOfRange));
393                     if (index >= list.Count)
394                         throw new InvalidOperationException(SR.GetString(SR.ListEnumCurrentOutOfRange));
395                     return currentElement;
396                 }
397             }
398 
Reset()399             public void Reset() {
400                 if (version != list._version)
401                     throw new InvalidOperationException(SR.GetString(SR.ListEnumVersionMismatch));
402                 currentElement = null;
403                 index = -1;
404             }
405         }
406     }
407 }
408