OSDN Git Service

2279bfd70b0a318afc21856fef9a5d2712120844
[radegast/radegast.git] / Radegast / GUI / Consoles / Inventory / InventoryConsole.cs
1 // 
2 // Radegast Metaverse Client
3 // Copyright (c) 2009-2013, Radegast Development Team
4 // All rights reserved.
5 // 
6 // Redistribution and use in source and binary forms, with or without
7 // modification, are permitted provided that the following conditions are met:
8 // 
9 //     * Redistributions of source code must retain the above copyright notice,
10 //       this list of conditions and the following disclaimer.
11 //     * Redistributions in binary form must reproduce the above copyright
12 //       notice, this list of conditions and the following disclaimer in the
13 //       documentation and/or other materials provided with the distribution.
14 //     * Neither the name of the application "Radegast", nor the names of its
15 //       contributors may be used to endorse or promote products derived from
16 //       this software without specific prior written permission.
17 // 
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 //
29 // $Id$
30 //
31 using System;
32 using System.Collections;
33 using System.Collections.Generic;
34 using System.ComponentModel;
35 using System.Drawing;
36 using System.Linq;
37 using System.Windows.Forms;
38 #if (COGBOT_LIBOMV || USE_STHREADS)
39 using ThreadPoolUtil;
40 using Thread = ThreadPoolUtil.Thread;
41 using ThreadPool = ThreadPoolUtil.ThreadPool;
42 using Monitor = ThreadPoolUtil.Monitor;
43 #endif
44 using System.Threading;
45
46 using OpenMetaverse;
47 using OpenMetaverse.StructuredData;
48
49 namespace Radegast
50 {
51
52     public partial class InventoryConsole : UserControl
53     {
54         RadegastInstance instance;
55         GridClient client { get { return instance.Client; } }
56         Dictionary<UUID, TreeNode> FolderNodes = new Dictionary<UUID, TreeNode>();
57
58         private InventoryManager Manager;
59         private OpenMetaverse.Inventory Inventory;
60         private TreeNode invRootNode;
61         private string newItemName = string.Empty;
62         private List<UUID> fetchedFolders = new List<UUID>();
63         private System.Threading.Timer _EditTimer;
64         private TreeNode _EditNode;
65         private Dictionary<UUID, AttachmentInfo> attachments = new Dictionary<UUID, AttachmentInfo>();
66         private System.Timers.Timer TreeUpdateTimer;
67         private Queue<InventoryBase> ItemsToAdd = new Queue<InventoryBase>();
68         private Queue<InventoryBase> ItemsToUpdate = new Queue<InventoryBase>();
69         private bool TreeUpdateInProgress = false;
70         private Dictionary<UUID, TreeNode> UUID2NodeCache = new Dictionary<UUID, TreeNode>();
71         private int updateInterval = 1000;
72         private Thread InventoryUpdate;
73         private List<UUID> WornItems = new List<UUID>();
74         private bool appearnceWasBusy;
75         private InvNodeSorter sorter;
76         private List<UUID> QueuedFolders = new List<UUID>();
77         private Dictionary<UUID, int> FolderFetchRetries = new Dictionary<UUID, int>();
78
79         #region Construction and disposal
80         public InventoryConsole(RadegastInstance instance)
81         {
82             InitializeComponent();
83             Disposed += new EventHandler(InventoryConsole_Disposed);
84
85             TreeUpdateTimer = new System.Timers.Timer()
86             {
87                 Interval = updateInterval,
88                 Enabled = false,
89                 SynchronizingObject = invTree
90             };
91             TreeUpdateTimer.Elapsed += TreeUpdateTimerTick;
92
93             this.instance = instance;
94             Manager = client.Inventory;
95             Inventory = Manager.Store;
96             Inventory.RootFolder.OwnerID = client.Self.AgentID;
97             invTree.ImageList = frmMain.ResourceImages;
98             invRootNode = AddDir(null, Inventory.RootFolder);
99             UpdateStatus("Reading cache");
100             Init1();
101         }
102
103         public void Init1()
104         {
105             WorkPool.QueueUserWorkItem(sync =>
106             {
107                 Logger.Log("Reading inventory cache from " + instance.InventoryCacheFileName, Helpers.LogLevel.Debug, client);
108                 Inventory.RestoreFromDisk(instance.InventoryCacheFileName);
109                 Init2();
110             });
111         }
112
113         public void Init2()
114         {
115             if (instance.MainForm.InvokeRequired)
116             {
117                 instance.MainForm.BeginInvoke(new MethodInvoker(() => Init2()));
118                 return;
119             }
120
121             AddFolderFromStore(invRootNode, Inventory.RootFolder);
122
123             sorter = new InvNodeSorter();
124
125             if (!instance.GlobalSettings.ContainsKey("inv_sort_bydate"))
126                 instance.GlobalSettings["inv_sort_bydate"] = OSD.FromBoolean(true);
127             if (!instance.GlobalSettings.ContainsKey("inv_sort_sysfirst"))
128                 instance.GlobalSettings["inv_sort_sysfirst"] = OSD.FromBoolean(true);
129
130             sorter.ByDate = instance.GlobalSettings["inv_sort_bydate"].AsBoolean();
131             sorter.SystemFoldersFirst = instance.GlobalSettings["inv_sort_sysfirst"].AsBoolean();
132
133             tbtnSortByDate.Checked = sorter.ByDate;
134             tbtbSortByName.Checked = !sorter.ByDate;
135             tbtnSystemFoldersFirst.Checked = sorter.SystemFoldersFirst;
136
137             invTree.TreeViewNodeSorter = sorter;
138
139             if (instance.MonoRuntime)
140             {
141                 invTree.BackColor = Color.FromKnownColor(KnownColor.Window);
142                 invTree.ForeColor = invTree.LineColor = Color.FromKnownColor(KnownColor.WindowText);
143                 InventoryFolder f = new InventoryFolder(UUID.Random());
144                 f.Name = "";
145                 f.ParentUUID = UUID.Zero;
146                 f.PreferredType = AssetType.Unknown;
147                 TreeNode dirNode = new TreeNode();
148                 dirNode.Name = f.UUID.ToString();
149                 dirNode.Text = f.Name;
150                 dirNode.Tag = f;
151                 dirNode.ImageIndex = GetDirImageIndex(f.PreferredType.ToString().ToLower());
152                 dirNode.SelectedImageIndex = dirNode.ImageIndex;
153                 invTree.Nodes.Add(dirNode);
154                 invTree.Sort();
155             }
156
157             saveAllTToolStripMenuItem.Enabled = false;
158             InventoryUpdate = new Thread(new ThreadStart(StartTraverseNodes));
159             InventoryUpdate.Name = "InventoryUpdate";
160             InventoryUpdate.IsBackground = true;
161             InventoryUpdate.Start();
162
163             invRootNode.Expand();
164
165             invTree.AfterExpand += new TreeViewEventHandler(TreeView_AfterExpand);
166             invTree.NodeMouseClick += new TreeNodeMouseClickEventHandler(invTree_MouseClick);
167             invTree.NodeMouseDoubleClick += new TreeNodeMouseClickEventHandler(invTree_NodeMouseDoubleClick);
168
169             _EditTimer = new System.Threading.Timer(OnLabelEditTimer, null, System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
170
171             // Callbacks
172             Inventory.InventoryObjectAdded += new EventHandler<InventoryObjectAddedEventArgs>(Inventory_InventoryObjectAdded);
173             Inventory.InventoryObjectUpdated += new EventHandler<InventoryObjectUpdatedEventArgs>(Inventory_InventoryObjectUpdated);
174             Inventory.InventoryObjectRemoved += new EventHandler<InventoryObjectRemovedEventArgs>(Inventory_InventoryObjectRemoved);
175
176             client.Objects.ObjectUpdate += new EventHandler<PrimEventArgs>(Objects_AttachmentUpdate);
177             client.Objects.KillObject += new EventHandler<KillObjectEventArgs>(Objects_KillObject);
178             client.Appearance.AppearanceSet += new EventHandler<AppearanceSetEventArgs>(Appearance_AppearanceSet);
179         }
180
181         void InventoryConsole_Disposed(object sender, EventArgs e)
182         {
183             if (TreeUpdateTimer != null)
184             {
185                 TreeUpdateTimer.Stop();
186                 TreeUpdateTimer.Dispose();
187                 TreeUpdateTimer = null;
188             }
189             if (InventoryUpdate != null)
190             {
191                 if (InventoryUpdate.IsAlive)
192                     InventoryUpdate.Abort();
193                 InventoryUpdate = null;
194             }
195
196             Inventory.InventoryObjectAdded -= new EventHandler<InventoryObjectAddedEventArgs>(Inventory_InventoryObjectAdded);
197             Inventory.InventoryObjectUpdated -= new EventHandler<InventoryObjectUpdatedEventArgs>(Inventory_InventoryObjectUpdated);
198             Inventory.InventoryObjectRemoved -= new EventHandler<InventoryObjectRemovedEventArgs>(Inventory_InventoryObjectRemoved);
199
200             client.Objects.ObjectUpdate -= new EventHandler<PrimEventArgs>(Objects_AttachmentUpdate);
201             client.Objects.KillObject -= new EventHandler<KillObjectEventArgs>(Objects_KillObject);
202             client.Appearance.AppearanceSet -= new EventHandler<AppearanceSetEventArgs>(Appearance_AppearanceSet);
203         }
204         #endregion
205
206         #region Network callbacks
207         void Appearance_AppearanceSet(object sender, AppearanceSetEventArgs e)
208         {
209             UpdateWornLabels();
210             if (appearnceWasBusy)
211             {
212                 appearnceWasBusy = false;
213                 client.Appearance.RequestSetAppearance(true);
214             }
215         }
216
217         void Objects_KillObject(object sender, KillObjectEventArgs e)
218         {
219             AttachmentInfo attachment = null;
220             lock (attachments)
221             {
222                 foreach (AttachmentInfo att in attachments.Values)
223                 {
224                     if (att.Prim != null && att.Prim.LocalID == e.ObjectLocalID)
225                     {
226                         attachment = att;
227                         break;
228                     }
229                 }
230
231                 if (attachment != null)
232                 {
233                     attachments.Remove(attachment.InventoryID);
234                     UpdateNodeLabel(attachment.InventoryID);
235                 }
236             }
237         }
238
239         void Objects_AttachmentUpdate(object sender, PrimEventArgs e)
240         {
241             Primitive prim = e.Prim;
242
243             if (client.Self.LocalID == 0 ||
244                 prim.ParentID != client.Self.LocalID ||
245                 prim.NameValues == null) return;
246
247             for (int i = 0; i < prim.NameValues.Length; i++)
248             {
249                 if (prim.NameValues[i].Name == "AttachItemID")
250                 {
251                     AttachmentInfo attachment = new AttachmentInfo();
252                     attachment.Prim = prim;
253                     attachment.InventoryID = new UUID(prim.NameValues[i].Value.ToString());
254                     attachment.PrimID = prim.ID;
255
256                     lock (attachments)
257                     {
258                         // Add new attachment info
259                         if (!attachments.ContainsKey(attachment.InventoryID))
260                         {
261                             attachments.Add(attachment.InventoryID, attachment);
262
263                         }
264                         else
265                         {
266                             attachment = attachments[attachment.InventoryID];
267                             if (attachment.Prim == null)
268                             {
269                                 attachment.Prim = prim;
270                             }
271                         }
272
273                         // Don't update the tree yet if we're still updating invetory tree from server
274                         if (!TreeUpdateInProgress)
275                         {
276                             if (Inventory.Contains(attachment.InventoryID))
277                             {
278                                 if (attachment.Item == null)
279                                 {
280                                     InventoryItem item = (InventoryItem)Inventory[attachment.InventoryID];
281                                     attachment.Item = item;
282                                 }
283                                 if (!attachment.MarkedAttached)
284                                 {
285                                     attachment.MarkedAttached = true;
286                                     UpdateNodeLabel(attachment.InventoryID);
287                                 }
288                             }
289                             else
290                             {
291                                 client.Inventory.RequestFetchInventory(attachment.InventoryID, client.Self.AgentID);
292                             }
293                         }
294                     }
295                     break;
296                 }
297             }
298         }
299
300         void Inventory_InventoryObjectAdded(object sender, InventoryObjectAddedEventArgs e)
301         {
302             if (TreeUpdateInProgress)
303             {
304                 lock (ItemsToAdd)
305                 {
306                     ItemsToAdd.Enqueue(e.Obj);
307                 }
308             }
309             else
310             {
311                 Exec_OnInventoryObjectAdded(e.Obj);
312             }
313         }
314
315         void Exec_OnInventoryObjectAdded(InventoryBase obj)
316         {
317             if (InvokeRequired)
318             {
319                 Invoke(new MethodInvoker(delegate()
320                     {
321                         Exec_OnInventoryObjectAdded(obj);
322                     }
323                 ));
324                 return;
325             }
326
327             lock (attachments)
328             {
329                 if (attachments.ContainsKey(obj.UUID))
330                 {
331                     attachments[obj.UUID].Item = (InventoryItem)obj;
332                 }
333             }
334
335             TreeNode parent = findNodeForItem(obj.ParentUUID);
336
337             if (parent != null)
338             {
339                 TreeNode newNode = AddBase(parent, obj);
340                 if (obj.Name == newItemName)
341                 {
342                     if (newNode.Parent.IsExpanded)
343                     {
344                         newNode.BeginEdit();
345                     }
346                     else
347                     {
348                         newNode.Parent.Expand();
349                     }
350                 }
351             }
352             newItemName = string.Empty;
353         }
354
355         void Inventory_InventoryObjectRemoved(object sender, InventoryObjectRemovedEventArgs e)
356         {
357             if (InvokeRequired)
358             {
359                 BeginInvoke(new MethodInvoker(() => Inventory_InventoryObjectRemoved(sender, e)));
360                 return;
361             }
362
363             lock (attachments)
364             {
365                 if (attachments.ContainsKey(e.Obj.UUID))
366                 {
367                     attachments.Remove(e.Obj.UUID);
368                 }
369             }
370
371             TreeNode currentNode = findNodeForItem(e.Obj.UUID);
372             if (currentNode != null)
373             {
374                 removeNode(currentNode);
375             }
376         }
377
378         void Inventory_InventoryObjectUpdated(object sender, InventoryObjectUpdatedEventArgs e)
379         {
380             if (TreeUpdateInProgress)
381             {
382                 lock (ItemsToUpdate)
383                 {
384                     if (e.NewObject is InventoryFolder)
385                     {
386                         TreeNode currentNode = findNodeForItem(e.NewObject.UUID);
387                         if (currentNode != null && currentNode.Text == e.NewObject.Name) return;
388                     }
389
390                     if (!ItemsToUpdate.Contains(e.NewObject))
391                     {
392                         ItemsToUpdate.Enqueue(e.NewObject);
393                     }
394                 }
395             }
396             else
397             {
398                 Exec_OnInventoryObjectUpdated(e.OldObject, e.NewObject);
399             }
400         }
401
402         void Exec_OnInventoryObjectUpdated(InventoryBase oldObject, InventoryBase newObject)
403         {
404             if (newObject == null) return;
405
406             if (InvokeRequired)
407             {
408                 BeginInvoke(new MethodInvoker(() => Exec_OnInventoryObjectUpdated(oldObject, newObject)));
409                 return;
410             }
411
412             lock (attachments)
413             {
414                 if (attachments.ContainsKey(newObject.UUID))
415                 {
416                     attachments[newObject.UUID].Item = (InventoryItem)newObject;
417                 }
418             }
419
420             // Find our current node in the tree
421             TreeNode currentNode = findNodeForItem(newObject.UUID);
422
423             // Find which node should be our parrent
424             TreeNode parent = findNodeForItem(newObject.ParentUUID);
425
426             if (parent == null) return;
427
428             if (currentNode != null)
429             {
430                 // Did we move to a different folder
431                 if (currentNode.Parent != parent)
432                 {
433                     TreeNode movedNode = (TreeNode)currentNode.Clone();
434                     movedNode.Tag = newObject;
435                     parent.Nodes.Add(movedNode);
436                     removeNode(currentNode);
437                     cacheNode(movedNode);
438                 }
439                 else // Update
440                 {
441                     currentNode.Tag = newObject;
442                     currentNode.Text = ItemLabel(newObject, false);
443                     currentNode.Name = newObject.Name;
444                 }
445             }
446             else // We are not in the tree already, add
447             {
448                 AddBase(parent, newObject);
449             }
450         }
451         #endregion
452
453         #region Node manipulation
454         public static int GetDirImageIndex(string t)
455         {
456             t = System.Text.RegularExpressions.Regex.Replace(t, @"folder$", "");
457             int res = frmMain.ImageNames.IndexOf("inv_folder_" + t);
458             if (res == -1)
459             {
460                 switch (t)
461                 {
462                     case "currentoutfit":
463                     case "myoutfits":
464                         return frmMain.ImageNames.IndexOf("inv_folder_outfit");
465                     case "lsltext":
466                         return frmMain.ImageNames.IndexOf("inv_folder_script");
467                 }
468                 return frmMain.ImageNames.IndexOf("inv_folder_plain_closed");
469             }
470             return res;
471         }
472
473         public static int GetItemImageIndex(string t)
474         {
475             int res = frmMain.ImageNames.IndexOf("inv_item_" + t);
476             if (res == -1)
477             {
478                 if (t == "lsltext")
479                 {
480                     return frmMain.ImageNames.IndexOf("inv_item_script");
481                 }
482                 else if (t == "callingcard")
483                 {
484                     return frmMain.ImageNames.IndexOf("inv_item_callingcard_offline");
485                 }
486             }
487             return res;
488         }
489
490         TreeNode AddBase(TreeNode parent, InventoryBase obj)
491         {
492             if (obj is InventoryItem)
493             {
494                 return AddItem(parent, (InventoryItem)obj);
495             }
496             else
497             {
498                 return AddDir(parent, (InventoryFolder)obj);
499             }
500         }
501
502         TreeNode AddDir(TreeNode parentNode, InventoryFolder f)
503         {
504             TreeNode dirNode = new TreeNode();
505             dirNode.Name = f.UUID.ToString();
506             dirNode.Text = f.Name;
507             dirNode.Tag = f;
508             dirNode.ImageIndex = GetDirImageIndex(f.PreferredType.ToString().ToLower());
509             dirNode.SelectedImageIndex = dirNode.ImageIndex;
510             if (parentNode == null)
511             {
512                 invTree.Nodes.Add(dirNode);
513             }
514             else
515             {
516                 parentNode.Nodes.Add(dirNode);
517             }
518             lock (UUID2NodeCache)
519             {
520                 UUID2NodeCache[f.UUID] = dirNode;
521             }
522             return dirNode;
523         }
524
525
526         TreeNode AddItem(TreeNode parent, InventoryItem item)
527         {
528             TreeNode itemNode = new TreeNode();
529             itemNode.Name = item.UUID.ToString();
530             itemNode.Text = ItemLabel(item, false);
531             itemNode.Tag = item;
532             int img = -1;
533             InventoryItem linkedItem = null;
534
535             if (item.IsLink() && Inventory.Contains(item.AssetUUID) && Inventory[item.AssetUUID] is InventoryItem)
536             {
537                 linkedItem = (InventoryItem)Inventory[item.AssetUUID];
538             }
539             else
540             {
541                 linkedItem = item;
542             }
543
544             if (linkedItem is InventoryWearable)
545             {
546                 InventoryWearable w = linkedItem as InventoryWearable;
547                 img = GetItemImageIndex(w.WearableType.ToString().ToLower());
548             }
549             else
550             {
551                 img = GetItemImageIndex(linkedItem.AssetType.ToString().ToLower());
552             }
553
554             itemNode.ImageIndex = img;
555             itemNode.SelectedImageIndex = img;
556             parent.Nodes.Add(itemNode);
557             lock (UUID2NodeCache)
558             {
559                 UUID2NodeCache[item.UUID] = itemNode;
560             }
561             return itemNode;
562         }
563
564         TreeNode findNodeForItem(UUID itemID)
565         {
566             lock (UUID2NodeCache)
567             {
568                 if (UUID2NodeCache.ContainsKey(itemID))
569                 {
570                     return UUID2NodeCache[itemID];
571                 }
572             }
573             return null;
574         }
575
576         void cacheNode(TreeNode node)
577         {
578             InventoryBase item = (InventoryBase)node.Tag;
579             if (item != null)
580             {
581                 foreach (TreeNode child in node.Nodes)
582                 {
583                     cacheNode(child);
584                 }
585                 lock (UUID2NodeCache)
586                 {
587                     UUID2NodeCache[item.UUID] = node;
588                 }
589             }
590         }
591
592         void removeNode(TreeNode node)
593         {
594             InventoryBase item = (InventoryBase)node.Tag;
595             if (item != null)
596             {
597                 foreach (TreeNode child in node.Nodes)
598                 {
599                     removeNode(child);
600                 }
601
602                 lock (UUID2NodeCache)
603                 {
604                     UUID2NodeCache.Remove(item.UUID);
605                 }
606             }
607             node.Remove();
608         }
609
610         #endregion
611
612         #region Private methods
613         private void UpdateStatus(string text)
614         {
615             if (InvokeRequired)
616             {
617                 Invoke(new MethodInvoker(delegate() { UpdateStatus(text); }));
618                 return;
619             }
620
621             if (text == "OK")
622             {
623                 saveAllTToolStripMenuItem.Enabled = true;
624             }
625
626             tlabelStatus.Text = text;
627         }
628
629         private void UpdateNodeLabel(UUID itemID)
630         {
631             if (instance.MainForm.InvokeRequired)
632             {
633                 instance.MainForm.BeginInvoke(new MethodInvoker(() => UpdateNodeLabel(itemID)));
634                 return;
635             }
636
637             TreeNode node = findNodeForItem(itemID);
638             if (node != null)
639             {
640                 node.Text = ItemLabel((InventoryBase)node.Tag, false);
641             }
642         }
643
644         private void AddFolderFromStore(TreeNode parent, InventoryFolder f)
645         {
646             List<InventoryBase> contents = Inventory.GetContents(f);
647             foreach (InventoryBase item in contents)
648             {
649                 TreeNode node = AddBase(parent, item);
650                 if (item is InventoryFolder)
651                 {
652                     AddFolderFromStore(node, (InventoryFolder)item);
653                 }
654             }
655         }
656
657         private void TraverseAndQueueNodes(InventoryNode start)
658         {
659             bool has_items = false;
660
661             foreach (InventoryNode node in start.Nodes.Values)
662             {
663                 if (node.Data is InventoryItem)
664                 {
665                     has_items = true;
666                     break;
667                 }
668             }
669
670             if (!has_items || start.NeedsUpdate)
671             {
672                 lock (QueuedFolders)
673                 {
674                     lock (FolderFetchRetries)
675                     {
676                         int retries = 0;
677                         FolderFetchRetries.TryGetValue(start.Data.UUID, out retries);
678                         if (retries < 3)
679                         {
680                             if (!QueuedFolders.Contains(start.Data.UUID))
681                             {
682                                 QueuedFolders.Add(start.Data.UUID);
683                             }
684                         }
685                         FolderFetchRetries[start.Data.UUID] = retries + 1;
686                     }
687                 }
688             }
689
690             foreach (InventoryBase item in Inventory.GetContents((InventoryFolder)start.Data))
691             {
692                 if (item is InventoryFolder)
693                 {
694                     TraverseAndQueueNodes(Inventory.GetNodeFor(item.UUID));
695                 }
696             }
697         }
698
699         private void TraverseNodes(InventoryNode start)
700         {
701             bool has_items = false;
702
703             foreach (InventoryNode node in start.Nodes.Values)
704             {
705                 if (node.Data is InventoryItem)
706                 {
707                     has_items = true;
708                     break;
709                 }
710             }
711
712             if (!has_items || start.NeedsUpdate)
713             {
714                 InventoryFolder f = (InventoryFolder)start.Data;
715                 AutoResetEvent gotFolderEvent = new AutoResetEvent(false);
716                 bool success = false;
717
718                 EventHandler<FolderUpdatedEventArgs> callback = delegate(object sender, FolderUpdatedEventArgs ea)
719                 {
720                     if (f.UUID == ea.FolderID)
721                     {
722                         if (((InventoryFolder)Inventory.Items[ea.FolderID].Data).DescendentCount <= Inventory.Items[ea.FolderID].Nodes.Count)
723                         {
724                             success = true;
725                             gotFolderEvent.Set();
726                         }
727                     }
728                 };
729
730                 client.Inventory.FolderUpdated += callback;
731                 fetchFolder(f.UUID, f.OwnerID, true);
732                 gotFolderEvent.WaitOne(30 * 1000, false);
733                 client.Inventory.FolderUpdated -= callback;
734
735                 if (!success)
736                 {
737                     Logger.Log(string.Format("Failed fetching folder {0}, got {1} items out of {2}", f.Name, Inventory.Items[f.UUID].Nodes.Count, ((InventoryFolder)Inventory.Items[f.UUID].Data).DescendentCount), Helpers.LogLevel.Error, client);
738                 }
739             }
740
741             foreach (InventoryBase item in Inventory.GetContents((InventoryFolder)start.Data))
742             {
743                 if (item is InventoryFolder)
744                 {
745                     TraverseNodes(Inventory.GetNodeFor(item.UUID));
746                 }
747             }
748         }
749
750         private void StartTraverseNodes()
751         {
752             if (!client.Network.CurrentSim.Caps.IsEventQueueRunning)
753             {
754                 AutoResetEvent EQRunning = new AutoResetEvent(false);
755                 EventHandler<EventQueueRunningEventArgs> handler = (sender, e) =>
756                     {
757                         EQRunning.Set();
758                     };
759                 client.Network.EventQueueRunning += handler;
760                 EQRunning.WaitOne(10 * 1000, false);
761                 client.Network.EventQueueRunning -= handler;
762             }
763
764             if (!client.Network.CurrentSim.Caps.IsEventQueueRunning)
765             {
766                 return;
767             }
768
769             UpdateStatus("Loading...");
770             TreeUpdateInProgress = true;
771             TreeUpdateTimer.Start();
772
773             lock (FolderFetchRetries)
774             {
775                 FolderFetchRetries.Clear();
776             }
777
778             do
779             {
780                 lock (QueuedFolders)
781                 {
782                     QueuedFolders.Clear();
783                 }
784                 TraverseAndQueueNodes(Inventory.RootNode);
785                 if (QueuedFolders.Count == 0) break;
786                 Logger.DebugLog(string.Format("Queued {0} folders for update", QueuedFolders.Count));
787
788                 Parallel.ForEach<UUID>(Math.Min(QueuedFolders.Count, 6), QueuedFolders, folderID =>
789                 {
790                     bool success = false;
791
792                     AutoResetEvent gotFolder = new AutoResetEvent(false);
793                     EventHandler<FolderUpdatedEventArgs> handler = (sender, ev) =>
794                         {
795                             if (ev.FolderID == folderID)
796                             {
797                                 success = ev.Success;
798                                 gotFolder.Set();
799                             }
800                         };
801
802                     client.Inventory.FolderUpdated += handler;
803                     client.Inventory.RequestFolderContents(folderID, client.Self.AgentID, true, true, InventorySortOrder.ByDate);
804                     if (!gotFolder.WaitOne(15 * 1000, false))
805                     {
806                         success = false;
807                     }
808                     client.Inventory.FolderUpdated -= handler;
809                 });
810             }
811             while (QueuedFolders.Count > 0);
812
813             TreeUpdateTimer.Stop();
814             if (IsHandleCreated)
815             {
816                 Invoke(new MethodInvoker(() => TreeUpdateTimerTick(null, null)));
817             }
818             TreeUpdateInProgress = false;
819             UpdateStatus("OK");
820             instance.TabConsole.DisplayNotificationInChat("Inventory update completed.");
821
822             // Updated labels on clothes that we are wearing
823             UpdateWornLabels();
824
825             // Update attachments now that we are done
826             lock (attachments)
827             {
828                 foreach (AttachmentInfo a in attachments.Values)
829                 {
830                     if (a.Item == null)
831                     {
832                         if (Inventory.Contains(a.InventoryID))
833                         {
834                             a.MarkedAttached = true;
835                             a.Item = (InventoryItem)Inventory[a.InventoryID];
836                             UpdateNodeLabel(a.InventoryID);
837                         }
838                         else
839                         {
840                             client.Inventory.RequestFetchInventory(a.InventoryID, client.Self.AgentID);
841                             return;
842                         }
843                     }
844                 }
845             }
846
847             Logger.Log("Finished updating invenory folders, saving cache...", Helpers.LogLevel.Debug, client);
848             WorkPool.QueueUserWorkItem((object state) => Inventory.SaveToDisk(instance.InventoryCacheFileName));
849
850             if (!instance.MonoRuntime || IsHandleCreated)
851                 Invoke(new MethodInvoker(() =>
852                     {
853                         invTree.Sort();
854                     }
855             ));
856
857
858         }
859
860         public void ReloadInventory()
861         {
862             if (TreeUpdateInProgress)
863             {
864                 TreeUpdateTimer.Stop();
865                 InventoryUpdate.Abort();
866                 InventoryUpdate = null;
867             }
868
869             saveAllTToolStripMenuItem.Enabled = false;
870
871             Inventory.Items = new Dictionary<UUID, InventoryNode>();
872             Inventory.RootFolder = Inventory.RootFolder;
873
874             invTree.Nodes.Clear();
875             UUID2NodeCache.Clear();
876             invRootNode = AddDir(null, Inventory.RootFolder);
877             Inventory.RootNode.NeedsUpdate = true;
878
879             InventoryUpdate = new Thread(new ThreadStart(StartTraverseNodes));
880             InventoryUpdate.Name = "InventoryUpdate";
881             InventoryUpdate.IsBackground = true;
882             InventoryUpdate.Start();
883             invRootNode.Expand();
884         }
885
886         private void reloadInventoryToolStripMenuItem_Click(object sender, EventArgs e)
887         {
888             ReloadInventory();
889         }
890
891         private void TreeUpdateTimerTick(Object sender, EventArgs e)
892         {
893             lock (ItemsToAdd)
894             {
895                 if (ItemsToAdd.Count > 0)
896                 {
897                     invTree.BeginUpdate();
898                     while (ItemsToAdd.Count > 0)
899                     {
900                         InventoryBase item = ItemsToAdd.Dequeue();
901                         TreeNode node = findNodeForItem(item.ParentUUID);
902                         if (node != null)
903                         {
904                             AddBase(node, item);
905                         }
906                     }
907                     invTree.EndUpdate();
908                 }
909             }
910
911             lock (ItemsToUpdate)
912             {
913                 if (ItemsToUpdate.Count > 0)
914                 {
915                     invTree.BeginUpdate();
916                     while (ItemsToUpdate.Count > 0)
917                     {
918                         InventoryBase item = ItemsToUpdate.Dequeue();
919                         Exec_OnInventoryObjectUpdated(item, item);
920                     }
921                     invTree.EndUpdate();
922                 }
923             }
924
925             UpdateStatus("Loading... " + UUID2NodeCache.Count.ToString() + " items");
926         }
927
928         #endregion
929
930         private void btnProfile_Click(object sender, EventArgs e)
931         {
932             instance.MainForm.ShowAgentProfile(txtCreator.Text, txtCreator.AgentID);
933         }
934
935         void UpdateItemInfo(InventoryItem item)
936         {
937             foreach (Control c in pnlDetail.Controls)
938             {
939                 c.Dispose();
940             }
941             pnlDetail.Controls.Clear();
942             pnlItemProperties.Tag = item;
943
944             if (item == null)
945             {
946                 pnlItemProperties.Visible = false;
947                 return;
948             }
949
950             pnlItemProperties.Visible = true;
951             btnProfile.Enabled = true;
952             txtItemName.Text = item.Name;
953             txtItemDescription.Text = item.Description;
954             txtCreator.AgentID = item.CreatorID;
955             txtCreator.Tag = item.CreatorID;
956             txtCreated.Text = item.CreationDate.ToString();
957
958             if (item.AssetUUID != UUID.Zero)
959             {
960                 txtAssetID.Text = item.AssetUUID.ToString();
961             }
962             else
963             {
964                 txtAssetID.Text = String.Empty;
965             }
966
967             txtInvID.Text = item.UUID.ToString();
968
969             Permissions p = item.Permissions;
970             cbOwnerModify.Checked = (p.OwnerMask & PermissionMask.Modify) != 0;
971             cbOwnerCopy.Checked = (p.OwnerMask & PermissionMask.Copy) != 0;
972             cbOwnerTransfer.Checked = (p.OwnerMask & PermissionMask.Transfer) != 0;
973
974             cbNextOwnModify.CheckedChanged -= cbNextOwnerUpdate_CheckedChanged;
975             cbNextOwnCopy.CheckedChanged -= cbNextOwnerUpdate_CheckedChanged;
976             cbNextOwnTransfer.CheckedChanged -= cbNextOwnerUpdate_CheckedChanged;
977
978             cbNextOwnModify.Checked = (p.NextOwnerMask & PermissionMask.Modify) != 0;
979             cbNextOwnCopy.Checked = (p.NextOwnerMask & PermissionMask.Copy) != 0;
980             cbNextOwnTransfer.Checked = (p.NextOwnerMask & PermissionMask.Transfer) != 0;
981
982             cbNextOwnModify.CheckedChanged += cbNextOwnerUpdate_CheckedChanged;
983             cbNextOwnCopy.CheckedChanged += cbNextOwnerUpdate_CheckedChanged;
984             cbNextOwnTransfer.CheckedChanged += cbNextOwnerUpdate_CheckedChanged;
985
986
987             switch (item.AssetType)
988             {
989                 case AssetType.Texture:
990                     SLImageHandler image = new SLImageHandler(instance, item.AssetUUID, item.Name, IsFullPerm(item));
991                     image.Dock = DockStyle.Fill;
992                     pnlDetail.Controls.Add(image);
993                     break;
994
995                 case AssetType.Notecard:
996                     Notecard note = new Notecard(instance, (InventoryNotecard)item);
997                     note.Dock = DockStyle.Fill;
998                     note.TabIndex = 3;
999                     note.TabStop = true;
1000                     pnlDetail.Controls.Add(note);
1001                     note.rtbContent.Focus();
1002                     break;
1003
1004                 case AssetType.Landmark:
1005                     Landmark landmark = new Landmark(instance, (InventoryLandmark)item);
1006                     landmark.Dock = DockStyle.Fill;
1007                     pnlDetail.Controls.Add(landmark);
1008                     break;
1009
1010                 case AssetType.LSLText:
1011                     ScriptEditor script = new ScriptEditor(instance, (InventoryLSL)item);
1012                     script.Dock = DockStyle.Fill;
1013                     script.TabIndex = 3;
1014                     script.TabStop = true;
1015                     pnlDetail.Controls.Add(script);
1016                     break;
1017
1018                 case AssetType.Gesture:
1019                     Guesture gesture = new Guesture(instance, (InventoryGesture)item);
1020                     gesture.Dock = DockStyle.Fill;
1021                     pnlDetail.Controls.Add(gesture);
1022                     break;
1023
1024             }
1025
1026             tabsInventory.SelectedTab = tabDetail;
1027         }
1028
1029         void cbNextOwnerUpdate_CheckedChanged(object sender, EventArgs e)
1030         {
1031             InventoryItem item = null;
1032             if (pnlItemProperties.Tag != null && pnlItemProperties.Tag is InventoryItem)
1033             {
1034                 item = (InventoryItem)pnlItemProperties.Tag;
1035             }
1036             if (item == null) return;
1037
1038             PermissionMask pm = PermissionMask.Move;
1039             if (cbNextOwnCopy.Checked) pm |= PermissionMask.Copy;
1040             if (cbNextOwnModify.Checked) pm |= PermissionMask.Modify;
1041             if (cbNextOwnTransfer.Checked) pm |= PermissionMask.Transfer;
1042             item.Permissions.NextOwnerMask = pm;
1043
1044             client.Inventory.RequestUpdateItem(item);
1045             client.Inventory.RequestFetchInventory(item.UUID, item.OwnerID);
1046         }
1047
1048         private void txtItemName_Leave(object sender, EventArgs e)
1049         {
1050             InventoryItem item = null;
1051             if (pnlItemProperties.Tag != null && pnlItemProperties.Tag is InventoryItem)
1052             {
1053                 item = (InventoryItem)pnlItemProperties.Tag;
1054             }
1055             if (item == null) return;
1056
1057             item.Name = txtItemName.Text;
1058
1059             client.Inventory.RequestUpdateItem(item);
1060             client.Inventory.RequestFetchInventory(item.UUID, item.OwnerID);
1061         }
1062
1063         private void txtItemDescription_Leave(object sender, EventArgs e)
1064         {
1065             InventoryItem item = null;
1066             if (pnlItemProperties.Tag != null && pnlItemProperties.Tag is InventoryItem)
1067             {
1068                 item = (InventoryItem)pnlItemProperties.Tag;
1069             }
1070             if (item == null) return;
1071
1072             item.Description = txtItemDescription.Text;
1073
1074             client.Inventory.RequestUpdateItem(item);
1075             client.Inventory.RequestFetchInventory(item.UUID, item.OwnerID);
1076         }
1077
1078         void invTree_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
1079         {
1080             if (invTree.SelectedNode.Tag is InventoryItem)
1081             {
1082                 InventoryItem item = invTree.SelectedNode.Tag as InventoryItem;
1083                 switch (item.AssetType)
1084                 {
1085
1086                     case AssetType.Landmark:
1087                         instance.TabConsole.DisplayNotificationInChat("Teleporting to " + item.Name);
1088                         client.Self.RequestTeleport(item.AssetUUID);
1089                         break;
1090
1091                     case AssetType.Gesture:
1092                         client.Self.PlayGesture(item.AssetUUID);
1093                         break;
1094
1095                     case AssetType.Notecard:
1096                         Notecard note = new Notecard(instance, (InventoryNotecard)item);
1097                         note.Dock = DockStyle.Fill;
1098                         note.ShowDetached();
1099                         break;
1100
1101                     case AssetType.LSLText:
1102                         ScriptEditor script = new ScriptEditor(instance, (InventoryLSL)item);
1103                         script.Dock = DockStyle.Fill;
1104                         script.ShowDetached();
1105                         break;
1106                 }
1107             }
1108         }
1109
1110         private void fetchFolder(UUID folderID, UUID ownerID, bool force)
1111         {
1112             if (force || !fetchedFolders.Contains(folderID))
1113             {
1114                 if (!fetchedFolders.Contains(folderID))
1115                 {
1116                     fetchedFolders.Add(folderID);
1117                 }
1118
1119                 client.Inventory.RequestFolderContents(folderID, ownerID, true, true, InventorySortOrder.ByDate);
1120             }
1121         }
1122
1123         public bool IsWorn(InventoryItem item)
1124         {
1125             bool worn = client.Appearance.IsItemWorn(item) != WearableType.Invalid;
1126
1127             lock (WornItems)
1128             {
1129                 if (worn && !WornItems.Contains(item.UUID))
1130                     WornItems.Add(item.UUID);
1131             }
1132             return worn;
1133         }
1134
1135         public AttachmentPoint AttachedTo(InventoryItem item)
1136         {
1137             lock (attachments)
1138             {
1139                 if (attachments.ContainsKey(item.UUID))
1140                 {
1141                     return attachments[item.UUID].Point;
1142                 }
1143             }
1144
1145             return AttachmentPoint.Default;
1146         }
1147
1148         public bool IsAttached(InventoryItem item)
1149         {
1150             List<Primitive> myAtt = client.Network.CurrentSim.ObjectsPrimitives.FindAll((Primitive p) => p.ParentID == client.Self.LocalID);
1151             foreach (Primitive prim in myAtt)
1152             {
1153                 if (prim.NameValues == null) continue;
1154                 UUID invID = UUID.Zero;
1155                 for (int i = 0; i < prim.NameValues.Length; i++)
1156                 {
1157                     if (prim.NameValues[i].Name == "AttachItemID")
1158                     {
1159                         invID = (UUID)prim.NameValues[i].Value.ToString();
1160                         break;
1161                     }
1162                 }
1163                 if (invID == item.UUID)
1164                 {
1165                     lock (attachments)
1166                     {
1167                         AttachmentInfo inf = new AttachmentInfo();
1168                         inf.InventoryID = item.UUID;
1169                         inf.Item = item;
1170                         inf.MarkedAttached = true;
1171                         inf.Prim = prim;
1172                         inf.PrimID = prim.ID;
1173                         attachments[invID] = inf;
1174                     }
1175                     return true;
1176                 }
1177             }
1178
1179             return false;
1180         }
1181
1182         public InventoryItem AttachmentAt(AttachmentPoint point)
1183         {
1184             lock (attachments)
1185             {
1186                 foreach (KeyValuePair<UUID, AttachmentInfo> att in attachments)
1187                 {
1188                     if (att.Value.Point == point)
1189                     {
1190                         return att.Value.Item;
1191                     }
1192                 }
1193             }
1194             return null;
1195         }
1196
1197         /// <summary>
1198         /// Returns text of the label
1199         /// </summary>
1200         /// <param name="invBase">Inventory item</param>
1201         /// <param name="returnRaw">Should we return raw text, or if false decorated text with (worn) info, and (no copy) etc. permission info</param>
1202         /// <returns></returns>
1203         public string ItemLabel(InventoryBase invBase, bool returnRaw)
1204         {
1205             if (returnRaw || (invBase is InventoryFolder))
1206                 return invBase.Name;
1207
1208             InventoryItem item = (InventoryItem)invBase;
1209
1210             string raw = item.Name;
1211
1212             if (item.IsLink())
1213             {
1214                 raw += " (link)";
1215                 item = instance.COF.RealInventoryItem(item);
1216                 if (Inventory.Contains(item.AssetUUID) && Inventory[item.AssetUUID] is InventoryItem)
1217                 {
1218                     item = (InventoryItem)Inventory[item.AssetUUID];
1219                 }
1220             }
1221
1222             if ((item.Permissions.OwnerMask & PermissionMask.Modify) == 0)
1223                 raw += " (no modify)";
1224
1225             if ((item.Permissions.OwnerMask & PermissionMask.Copy) == 0)
1226                 raw += " (no copy)";
1227
1228             if ((item.Permissions.OwnerMask & PermissionMask.Transfer) == 0)
1229                 raw += " (no transfer)";
1230
1231             if (IsWorn(item))
1232                 raw += " (worn)";
1233
1234             if (IsAttached(item))
1235             {
1236                 raw += " (worn on " + AttachedTo(item).ToString() + ")";
1237             }
1238
1239             return raw;
1240         }
1241
1242         public static bool IsFullPerm(InventoryItem item)
1243         {
1244             if (
1245                 ((item.Permissions.OwnerMask & PermissionMask.Modify) != 0) &&
1246                 ((item.Permissions.OwnerMask & PermissionMask.Copy) != 0) &&
1247                 ((item.Permissions.OwnerMask & PermissionMask.Transfer) != 0)
1248                 )
1249             {
1250                 return true;
1251             }
1252             else
1253             {
1254                 return false;
1255             }
1256         }
1257
1258         void invTree_MouseClick(object sender, TreeNodeMouseClickEventArgs e)
1259         {
1260             TreeNode node = e.Node;
1261
1262             if (e.Button == MouseButtons.Left)
1263             {
1264                 invTree.SelectedNode = node;
1265                 if (node.Tag is InventoryItem)
1266                 {
1267                     UpdateItemInfo(instance.COF.RealInventoryItem(node.Tag as InventoryItem));
1268                 }
1269                 else
1270                 {
1271                     UpdateItemInfo(null);
1272                 }
1273             }
1274             else if (e.Button == MouseButtons.Right)
1275             {
1276                 invTree.SelectedNode = node;
1277                 ctxInv.Show(invTree, e.X, e.Y);
1278             }
1279         }
1280
1281         private void ctxInv_Opening(object sender, CancelEventArgs e)
1282         {
1283             e.Cancel = false;
1284             TreeNode node = invTree.SelectedNode;
1285             if (node == null)
1286             {
1287                 e.Cancel = true;
1288             }
1289             else
1290             {
1291                 #region Folder context menu
1292                 if (node.Tag is InventoryFolder)
1293                 {
1294                     InventoryFolder folder = (InventoryFolder)node.Tag;
1295                     ctxInv.Items.Clear();
1296
1297                     ToolStripMenuItem ctxItem;
1298
1299                     if ((int)folder.PreferredType >= (int)AssetType.EnsembleStart &&
1300                         (int)folder.PreferredType <= (int)AssetType.EnsembleEnd)
1301                     {
1302                         ctxItem = new ToolStripMenuItem("Fix type", null, OnInvContextClick);
1303                         ctxItem.Name = "fix_type";
1304                         ctxInv.Items.Add(ctxItem);
1305                         ctxInv.Items.Add(new ToolStripSeparator());
1306                     }
1307
1308                     ctxItem = new ToolStripMenuItem("New Folder", null, OnInvContextClick);
1309                     ctxItem.Name = "new_folder";
1310                     ctxInv.Items.Add(ctxItem);
1311
1312                     ctxItem = new ToolStripMenuItem("New Note", null, OnInvContextClick);
1313                     ctxItem.Name = "new_notecard";
1314                     ctxInv.Items.Add(ctxItem);
1315
1316                     ctxItem = new ToolStripMenuItem("New Script", null, OnInvContextClick);
1317                     ctxItem.Name = "new_script";
1318                     ctxInv.Items.Add(ctxItem);
1319
1320                     ctxItem = new ToolStripMenuItem("Refresh", null, OnInvContextClick);
1321                     ctxItem.Name = "refresh";
1322                     ctxInv.Items.Add(ctxItem);
1323
1324                     ctxItem = new ToolStripMenuItem("Backup...", null, OnInvContextClick);
1325                     ctxItem.Name = "backup";
1326                     ctxInv.Items.Add(ctxItem);
1327
1328                     ctxInv.Items.Add(new ToolStripSeparator());
1329
1330                     ctxItem = new ToolStripMenuItem("Expand", null, OnInvContextClick);
1331                     ctxItem.Name = "expand";
1332                     ctxInv.Items.Add(ctxItem);
1333
1334                     ctxItem = new ToolStripMenuItem("Expand All", null, OnInvContextClick);
1335                     ctxItem.Name = "expand_all";
1336                     ctxInv.Items.Add(ctxItem);
1337
1338                     ctxItem = new ToolStripMenuItem("Collapse", null, OnInvContextClick);
1339                     ctxItem.Name = "collapse";
1340                     ctxInv.Items.Add(ctxItem);
1341
1342                     if (folder.PreferredType == AssetType.TrashFolder)
1343                     {
1344                         ctxItem = new ToolStripMenuItem("Empty Trash", null, OnInvContextClick);
1345                         ctxItem.Name = "empty_trash";
1346                         ctxInv.Items.Add(ctxItem);
1347                     }
1348
1349                     if (folder.PreferredType == AssetType.LostAndFoundFolder)
1350                     {
1351                         ctxItem = new ToolStripMenuItem("Empty Lost and Found", null, OnInvContextClick);
1352                         ctxItem.Name = "empty_lost_found";
1353                         ctxInv.Items.Add(ctxItem);
1354                     }
1355
1356                     if (folder.PreferredType == AssetType.Unknown ||
1357                         folder.PreferredType == AssetType.OutfitFolder)
1358                     {
1359                         ctxItem = new ToolStripMenuItem("Rename", null, OnInvContextClick);
1360                         ctxItem.Name = "rename_folder";
1361                         ctxInv.Items.Add(ctxItem);
1362
1363                         ctxInv.Items.Add(new ToolStripSeparator());
1364
1365                         ctxItem = new ToolStripMenuItem("Cut", null, OnInvContextClick);
1366                         ctxItem.Name = "cut_folder";
1367                         ctxInv.Items.Add(ctxItem);
1368
1369                         ctxItem = new ToolStripMenuItem("Copy", null, OnInvContextClick);
1370                         ctxItem.Name = "copy_folder";
1371                         ctxInv.Items.Add(ctxItem);
1372                     }
1373
1374                     if (instance.InventoryClipboard != null)
1375                     {
1376                         ctxItem = new ToolStripMenuItem("Paste", null, OnInvContextClick);
1377                         ctxItem.Name = "paste_folder";
1378                         ctxInv.Items.Add(ctxItem);
1379
1380                         if (instance.InventoryClipboard.Item is InventoryItem)
1381                         {
1382                             ctxItem = new ToolStripMenuItem("Paste as Link", null, OnInvContextClick);
1383                             ctxItem.Name = "paste_folder_link";
1384                             ctxInv.Items.Add(ctxItem);
1385                         }
1386                     }
1387
1388                     if (folder.PreferredType == AssetType.Unknown ||
1389                         folder.PreferredType == AssetType.OutfitFolder)
1390                     {
1391                         ctxItem = new ToolStripMenuItem("Delete", null, OnInvContextClick);
1392                         ctxItem.Name = "delete_folder";
1393                         ctxInv.Items.Add(ctxItem);
1394
1395                         ctxInv.Items.Add(new ToolStripSeparator());
1396                     }
1397
1398                     if (folder.PreferredType == AssetType.Unknown || folder.PreferredType == AssetType.OutfitFolder)
1399                     {
1400                         ctxItem = new ToolStripMenuItem("Take off Items", null, OnInvContextClick);
1401                         ctxItem.Name = "outfit_take_off";
1402                         ctxInv.Items.Add(ctxItem);
1403
1404                         ctxItem = new ToolStripMenuItem("Add to Outfit", null, OnInvContextClick);
1405                         ctxItem.Name = "outfit_add";
1406                         ctxInv.Items.Add(ctxItem);
1407
1408                         ctxItem = new ToolStripMenuItem("Replace Outfit", null, OnInvContextClick);
1409                         ctxItem.Name = "outfit_replace";
1410                         ctxInv.Items.Add(ctxItem);
1411                     }
1412
1413                     instance.ContextActionManager.AddContributions(ctxInv, folder);
1414                 #endregion Folder context menu
1415                 }
1416                 else if (node.Tag is InventoryItem)
1417                 {
1418                     #region Item context menu
1419                     InventoryItem item = instance.COF.RealInventoryItem((InventoryItem)node.Tag);
1420                     ctxInv.Items.Clear();
1421
1422                     ToolStripMenuItem ctxItem;
1423
1424                     if (item.InventoryType == InventoryType.LSL)
1425                     {
1426                         ctxItem = new ToolStripMenuItem("Edit script", null, OnInvContextClick);
1427                         ctxItem.Name = "edit_script";
1428                         ctxInv.Items.Add(ctxItem);
1429                     }
1430
1431                     if (item.AssetType == AssetType.Texture)
1432                     {
1433                         ctxItem = new ToolStripMenuItem("View", null, OnInvContextClick);
1434                         ctxItem.Name = "view_image";
1435                         ctxInv.Items.Add(ctxItem);
1436                     }
1437
1438                     if (item.InventoryType == InventoryType.Landmark)
1439                     {
1440                         ctxItem = new ToolStripMenuItem("Teleport", null, OnInvContextClick);
1441                         ctxItem.Name = "lm_teleport";
1442                         ctxInv.Items.Add(ctxItem);
1443
1444                         ctxItem = new ToolStripMenuItem("Info", null, OnInvContextClick);
1445                         ctxItem.Name = "lm_info";
1446                         ctxInv.Items.Add(ctxItem);
1447                     }
1448
1449                     if (item.InventoryType == InventoryType.Notecard)
1450                     {
1451                         ctxItem = new ToolStripMenuItem("Open", null, OnInvContextClick);
1452                         ctxItem.Name = "notecard_open";
1453                         ctxInv.Items.Add(ctxItem);
1454                     }
1455
1456                     if (item.InventoryType == InventoryType.Gesture)
1457                     {
1458                         ctxItem = new ToolStripMenuItem("Play", null, OnInvContextClick);
1459                         ctxItem.Name = "gesture_play";
1460                         ctxInv.Items.Add(ctxItem);
1461
1462                         ctxItem = new ToolStripMenuItem("Info", null, OnInvContextClick);
1463                         ctxItem.Name = "gesture_info";
1464                         ctxInv.Items.Add(ctxItem);
1465                     }
1466
1467                     if (item.InventoryType == InventoryType.Animation)
1468                     {
1469                         if (!client.Self.SignaledAnimations.ContainsKey(item.AssetUUID))
1470                         {
1471                             ctxItem = new ToolStripMenuItem("Play", null, OnInvContextClick);
1472                             ctxItem.Name = "animation_play";
1473                             ctxInv.Items.Add(ctxItem);
1474                         }
1475                         else
1476                         {
1477                             ctxItem = new ToolStripMenuItem("Stop", null, OnInvContextClick);
1478                             ctxItem.Name = "animation_stop";
1479                             ctxInv.Items.Add(ctxItem);
1480                         }
1481                     }
1482
1483                     if (item.InventoryType == InventoryType.Object)
1484                     {
1485                         ctxItem = new ToolStripMenuItem("Rez inworld", null, OnInvContextClick);
1486                         ctxItem.Name = "rez_inworld";
1487                         ctxInv.Items.Add(ctxItem);
1488                     }
1489
1490                     ctxItem = new ToolStripMenuItem("Rename", null, OnInvContextClick);
1491                     ctxItem.Name = "rename_item";
1492                     ctxInv.Items.Add(ctxItem);
1493
1494                     ctxInv.Items.Add(new ToolStripSeparator());
1495
1496                     ctxItem = new ToolStripMenuItem("Cut", null, OnInvContextClick);
1497                     ctxItem.Name = "cut_item";
1498                     ctxInv.Items.Add(ctxItem);
1499
1500                     ctxItem = new ToolStripMenuItem("Copy", null, OnInvContextClick);
1501                     ctxItem.Name = "copy_item";
1502                     ctxInv.Items.Add(ctxItem);
1503
1504                     if (instance.InventoryClipboard != null)
1505                     {
1506                         ctxItem = new ToolStripMenuItem("Paste", null, OnInvContextClick);
1507                         ctxItem.Name = "paste_item";
1508                         ctxInv.Items.Add(ctxItem);
1509
1510                         if (instance.InventoryClipboard.Item is InventoryItem)
1511                         {
1512                             ctxItem = new ToolStripMenuItem("Paste as Link", null, OnInvContextClick);
1513                             ctxItem.Name = "paste_item_link";
1514                             ctxInv.Items.Add(ctxItem);
1515                         }
1516                     }
1517
1518                     ctxItem = new ToolStripMenuItem("Delete", null, OnInvContextClick);
1519                     ctxItem.Name = "delete_item";
1520
1521                     if (IsAttached(item) || IsWorn(item))
1522                     {
1523                         ctxItem.Enabled = false;
1524                     }
1525                     ctxInv.Items.Add(ctxItem);
1526
1527                     if (IsAttached(item) && instance.RLV.AllowDetach(attachments[item.UUID]))
1528                     {
1529                         ctxItem = new ToolStripMenuItem("Detach from yourself", null, OnInvContextClick);
1530                         ctxItem.Name = "detach";
1531                         ctxInv.Items.Add(ctxItem);
1532                     }
1533
1534                     if (!IsAttached(item) && (item.InventoryType == InventoryType.Object || item.InventoryType == InventoryType.Attachment))
1535                     {
1536                         ToolStripMenuItem ctxItemAttach = new ToolStripMenuItem("Attach to");
1537                         ctxInv.Items.Add(ctxItemAttach);
1538
1539                         ToolStripMenuItem ctxItemAttachHUD = new ToolStripMenuItem("Attach to HUD");
1540                         ctxInv.Items.Add(ctxItemAttachHUD);
1541
1542                         foreach (AttachmentPoint pt in Enum.GetValues(typeof(AttachmentPoint)))
1543                         {
1544                             if (!pt.ToString().StartsWith("HUD"))
1545                             {
1546                                 string name = Utils.EnumToText(pt);
1547
1548                                 InventoryItem alreadyAttached = null;
1549                                 if ((alreadyAttached = AttachmentAt(pt)) != null)
1550                                 {
1551                                     name += " (" + alreadyAttached.Name + ")";
1552                                 }
1553
1554                                 ToolStripMenuItem ptItem = new ToolStripMenuItem(name, null, OnInvContextClick);
1555                                 ptItem.Name = pt.ToString();
1556                                 ptItem.Tag = pt;
1557                                 ptItem.Name = "attach_to";
1558                                 ctxItemAttach.DropDownItems.Add(ptItem);
1559                             }
1560                             else
1561                             {
1562                                 string name = Utils.EnumToText(pt).Substring(3);
1563
1564                                 InventoryItem alreadyAttached = null;
1565                                 if ((alreadyAttached = AttachmentAt(pt)) != null)
1566                                 {
1567                                     name += " (" + alreadyAttached.Name + ")";
1568                                 }
1569
1570                                 ToolStripMenuItem ptItem = new ToolStripMenuItem(name, null, OnInvContextClick);
1571                                 ptItem.Name = pt.ToString();
1572                                 ptItem.Tag = pt;
1573                                 ptItem.Name = "attach_to";
1574                                 ctxItemAttachHUD.DropDownItems.Add(ptItem);
1575                             }
1576                         }
1577
1578                         ctxItem = new ToolStripMenuItem("Add to Worn", null, OnInvContextClick);
1579                         ctxItem.Name = "wear_attachment_add";
1580                         ctxInv.Items.Add(ctxItem);
1581
1582                         ctxItem = new ToolStripMenuItem("Wear", null, OnInvContextClick);
1583                         ctxItem.Name = "wear_attachment";
1584                         ctxInv.Items.Add(ctxItem);
1585                     }
1586
1587                     if (item is InventoryWearable)
1588                     {
1589                         ctxInv.Items.Add(new ToolStripSeparator());
1590
1591                         if (IsWorn(item))
1592                         {
1593                             ctxItem = new ToolStripMenuItem("Take off", null, OnInvContextClick);
1594                             ctxItem.Name = "item_take_off";
1595                             ctxInv.Items.Add(ctxItem);
1596                         }
1597                         else
1598                         {
1599                             ctxItem = new ToolStripMenuItem("Wear", null, OnInvContextClick);
1600                             ctxItem.Name = "item_wear";
1601                             ctxInv.Items.Add(ctxItem);
1602                         }
1603                     }
1604
1605                     instance.ContextActionManager.AddContributions(ctxInv, item);
1606                     #endregion Item context menu
1607                 }
1608             }
1609         }
1610
1611         #region Context menu folder
1612         private void OnInvContextClick(object sender, EventArgs e)
1613         {
1614             if (invTree.SelectedNode == null || !(invTree.SelectedNode.Tag is InventoryBase))
1615             {
1616                 return;
1617             }
1618
1619             string cmd = ((ToolStripMenuItem)sender).Name;
1620
1621             if (invTree.SelectedNode.Tag is InventoryFolder)
1622             {
1623                 #region Folder actions
1624                 InventoryFolder f = (InventoryFolder)invTree.SelectedNode.Tag;
1625
1626                 switch (cmd)
1627                 {
1628                     case "refresh":
1629                         foreach (TreeNode old in invTree.SelectedNode.Nodes)
1630                         {
1631                             if (!(old.Tag is InventoryFolder))
1632                             {
1633                                 removeNode(old);
1634                             }
1635                         }
1636                         fetchFolder(f.UUID, f.OwnerID, true);
1637                         break;
1638
1639                     case "backup":
1640                         (new InventoryBackup(instance, f.UUID)).Show();
1641                         break;
1642
1643                     case "expand":
1644                         invTree.SelectedNode.Expand();
1645                         break;
1646
1647                     case "expand_all":
1648                         invTree.SelectedNode.ExpandAll();
1649                         break;
1650
1651                     case "collapse":
1652                         invTree.SelectedNode.Collapse();
1653                         break;
1654
1655                     case "new_folder":
1656                         newItemName = "New folder";
1657                         client.Inventory.CreateFolder(f.UUID, "New folder");
1658                         break;
1659
1660                     case "fix_type":
1661                         client.Inventory.UpdateFolderProperties(f.UUID, f.ParentUUID, f.Name, AssetType.Unknown);
1662                         invTree.Sort();
1663                         break;
1664
1665                     case "new_notecard":
1666                         client.Inventory.RequestCreateItem(f.UUID, "New Note", "Radegast note: " + DateTime.Now.ToString(),
1667                             AssetType.Notecard, UUID.Zero, InventoryType.Notecard, PermissionMask.All, NotecardCreated);
1668                         break;
1669
1670                     case "new_script":
1671                         client.Inventory.RequestCreateItem(f.UUID, "New script", "Radegast script: " + DateTime.Now.ToString(),
1672                             AssetType.LSLText, UUID.Zero, InventoryType.LSL, PermissionMask.All, ScriptCreated);
1673                         break;
1674
1675                     case "cut_folder":
1676                         instance.InventoryClipboard = new InventoryClipboard(ClipboardOperation.Cut, f);
1677                         break;
1678
1679                     case "copy_folder":
1680                         instance.InventoryClipboard = new InventoryClipboard(ClipboardOperation.Copy, f);
1681                         break;
1682
1683                     case "paste_folder":
1684                         PerformClipboardOperation(invTree.SelectedNode.Tag as InventoryFolder);
1685                         break;
1686
1687                     case "paste_folder_link":
1688                         PerformLinkOperation(invTree.SelectedNode.Tag as InventoryFolder);
1689                         break;
1690
1691
1692                     case "delete_folder":
1693                         client.Inventory.MoveFolder(f.UUID, client.Inventory.FindFolderForType(AssetType.TrashFolder), f.Name);
1694                         break;
1695
1696                     case "empty_trash":
1697                         {
1698                             DialogResult res = MessageBox.Show("Are you sure you want to empty your trash?", "Confirmation", MessageBoxButtons.OKCancel);
1699                             if (res == DialogResult.OK)
1700                             {
1701                                 client.Inventory.EmptyTrash();
1702                             }
1703                         }
1704                         break;
1705
1706                     case "empty_lost_found":
1707                         {
1708                             DialogResult res = MessageBox.Show("Are you sure you want to empty your lost and found folder?", "Confirmation", MessageBoxButtons.OKCancel);
1709                             if (res == DialogResult.OK)
1710                             {
1711                                 client.Inventory.EmptyLostAndFound();
1712                             }
1713                         }
1714                         break;
1715
1716                     case "rename_folder":
1717                         invTree.SelectedNode.BeginEdit();
1718                         break;
1719
1720                     case "outfit_replace":
1721                         List<InventoryItem> newOutfit = new List<InventoryItem>();
1722                         foreach (InventoryBase item in Inventory.GetContents(f))
1723                         {
1724                             if (item is InventoryItem)
1725                                 newOutfit.Add((InventoryItem)item);
1726                         }
1727                         appearnceWasBusy = client.Appearance.ManagerBusy;
1728                         instance.COF.ReplaceOutfit(newOutfit);
1729                         UpdateWornLabels();
1730                         break;
1731
1732                     case "outfit_add":
1733                         List<InventoryItem> addToOutfit = new List<InventoryItem>();
1734                         foreach (InventoryBase item in Inventory.GetContents(f))
1735                         {
1736                             if (item is InventoryItem)
1737                                 addToOutfit.Add((InventoryItem)item);
1738                         }
1739                         appearnceWasBusy = client.Appearance.ManagerBusy;
1740                         instance.COF.AddToOutfit(addToOutfit, true);
1741                         UpdateWornLabels();
1742                         break;
1743
1744                     case "outfit_take_off":
1745                         List<InventoryItem> removeFromOutfit = new List<InventoryItem>();
1746                         foreach (InventoryBase item in Inventory.GetContents(f))
1747                         {
1748                             if (item is InventoryItem)
1749                                 removeFromOutfit.Add((InventoryItem)item);
1750                         }
1751                         appearnceWasBusy = client.Appearance.ManagerBusy;
1752                         instance.COF.RemoveFromOutfit(removeFromOutfit);
1753                         UpdateWornLabels();
1754                         break;
1755                 }
1756                 #endregion
1757             }
1758             else if (invTree.SelectedNode.Tag is InventoryItem)
1759             {
1760                 #region Item actions
1761                 InventoryItem item = (InventoryItem)invTree.SelectedNode.Tag;
1762
1763                 // Copy, cut, and delete works on links directly
1764                 // The rest operate on the item that is pointed by the link
1765                 if (cmd != "copy_item" && cmd != "cut_item" && cmd != "delete_item")
1766                 {
1767                     item = instance.COF.RealInventoryItem(item);
1768                 }
1769
1770                 switch (cmd)
1771                 {
1772                     case "copy_item":
1773                         instance.InventoryClipboard = new InventoryClipboard(ClipboardOperation.Copy, item);
1774                         break;
1775
1776                     case "cut_item":
1777                         instance.InventoryClipboard = new InventoryClipboard(ClipboardOperation.Cut, item);
1778                         break;
1779
1780                     case "paste_item":
1781                         PerformClipboardOperation(invTree.SelectedNode.Parent.Tag as InventoryFolder);
1782                         break;
1783
1784                     case "paste_item_link":
1785                         PerformLinkOperation(invTree.SelectedNode.Parent.Tag as InventoryFolder);
1786                         break;
1787
1788                     case "delete_item":
1789                         client.Inventory.MoveItem(item.UUID, client.Inventory.FindFolderForType(AssetType.TrashFolder), item.Name);
1790                         break;
1791
1792                     case "rename_item":
1793                         invTree.SelectedNode.BeginEdit();
1794                         break;
1795
1796                     case "detach":
1797                         instance.COF.Detach(item);
1798                         lock (attachments) attachments.Remove(item.UUID);
1799                         invTree.SelectedNode.Text = ItemLabel(item, false);
1800                         break;
1801
1802                     case "wear_attachment":
1803                         instance.COF.Attach(item, AttachmentPoint.Default, true);
1804                         break;
1805
1806                     case "wear_attachment_add":
1807                         instance.COF.Attach(item, AttachmentPoint.Default, false);
1808                         break;
1809
1810                     case "attach_to":
1811                         AttachmentPoint pt = (AttachmentPoint)((ToolStripMenuItem)sender).Tag;
1812                         instance.COF.Attach(item, pt, true);
1813                         break;
1814
1815                     case "edit_script":
1816                         ScriptEditor se = new ScriptEditor(instance, (InventoryLSL)item);
1817                         se.Detached = true;
1818                         return;
1819
1820                     case "view_image":
1821                         UpdateItemInfo(item);
1822                         break;
1823
1824                     case "item_take_off":
1825                         appearnceWasBusy = client.Appearance.ManagerBusy;
1826                         instance.COF.RemoveFromOutfit(item);
1827                         invTree.SelectedNode.Text = ItemLabel(item, false);
1828                         lock (WornItems)
1829                         {
1830                             if (WornItems.Contains(item.UUID))
1831                             {
1832                                 WornItems.Remove(item.UUID);
1833                             }
1834                         }
1835                         break;
1836
1837                     case "item_wear":
1838                         appearnceWasBusy = client.Appearance.ManagerBusy;
1839                         instance.COF.AddToOutfit(item, true);
1840                         invTree.SelectedNode.Text = ItemLabel(item, false);
1841                         break;
1842
1843                     case "lm_teleport":
1844                         instance.TabConsole.DisplayNotificationInChat("Teleporting to " + item.Name);
1845                         client.Self.RequestTeleport(item.AssetUUID);
1846                         break;
1847
1848                     case "lm_info":
1849                         UpdateItemInfo(item);
1850                         break;
1851
1852                     case "notecard_open":
1853                         UpdateItemInfo(item);
1854                         break;
1855
1856                     case "gesture_info":
1857                         UpdateItemInfo(item);
1858                         break;
1859
1860                     case "gesture_play":
1861                         client.Self.PlayGesture(item.AssetUUID);
1862                         break;
1863
1864                     case "animation_play":
1865                         Dictionary<UUID, bool> anim = new Dictionary<UUID, bool>();
1866                         anim.Add(item.AssetUUID, true);
1867                         client.Self.Animate(anim, true);
1868                         break;
1869
1870                     case "animation_stop":
1871                         Dictionary<UUID, bool> animStop = new Dictionary<UUID, bool>();
1872                         animStop.Add(item.AssetUUID, false);
1873                         client.Self.Animate(animStop, true);
1874                         break;
1875
1876                     case "rez_inworld":
1877                         instance.MediaManager.PlayUISound(UISounds.ObjectRez);
1878                         Vector3 rezpos = new Vector3(2, 0, 0);
1879                         rezpos = client.Self.SimPosition + rezpos * client.Self.Movement.BodyRotation;
1880                         client.Inventory.RequestRezFromInventory(client.Network.CurrentSim, Quaternion.Identity, rezpos, item);
1881                         break;
1882                 }
1883                 #endregion
1884             }
1885         }
1886
1887         void NotecardCreated(bool success, InventoryItem item)
1888         {
1889             if (InvokeRequired)
1890             {
1891                 BeginInvoke(new MethodInvoker(() => NotecardCreated(success, item)));
1892                 return;
1893             }
1894
1895             if (!success)
1896             {
1897                 instance.TabConsole.DisplayNotificationInChat("Creation of notecard failed");
1898                 return;
1899             }
1900
1901             instance.TabConsole.DisplayNotificationInChat("New notecard created, enter notecard name and press enter", ChatBufferTextStyle.Invisible);
1902             var node = findNodeForItem(item.ParentUUID);
1903             if (node != null) node.Expand();
1904             node = findNodeForItem(item.UUID);
1905             if (node != null)
1906             {
1907                 invTree.SelectedNode = node;
1908                 node.BeginEdit();
1909             }
1910         }
1911
1912         void ScriptCreated(bool success, InventoryItem item)
1913         {
1914             if (InvokeRequired)
1915             {
1916                 BeginInvoke(new MethodInvoker(() => ScriptCreated(success, item)));
1917                 return;
1918             }
1919
1920             if (!success)
1921             {
1922                 instance.TabConsole.DisplayNotificationInChat("Creation of script failed");
1923                 return;
1924             }
1925
1926             instance.TabConsole.DisplayNotificationInChat("New script created, enter script name and press enter", ChatBufferTextStyle.Invisible);
1927             var node = findNodeForItem(item.ParentUUID);
1928             if (node != null) node.Expand();
1929             node = findNodeForItem(item.UUID);
1930             if (node != null)
1931             {
1932                 invTree.SelectedNode = node;
1933                 node.BeginEdit();
1934             }
1935         }
1936
1937         void PerformClipboardOperation(InventoryFolder dest)
1938         {
1939             if (instance.InventoryClipboard == null) return;
1940
1941             if (dest == null) return;
1942
1943             if (instance.InventoryClipboard.Operation == ClipboardOperation.Cut)
1944             {
1945                 if (instance.InventoryClipboard.Item is InventoryItem)
1946                 {
1947                     client.Inventory.MoveItem(instance.InventoryClipboard.Item.UUID, dest.UUID, instance.InventoryClipboard.Item.Name);
1948                 }
1949                 else if (instance.InventoryClipboard.Item is InventoryFolder)
1950                 {
1951                     if (instance.InventoryClipboard.Item.UUID != dest.UUID)
1952                     {
1953                         client.Inventory.MoveFolder(instance.InventoryClipboard.Item.UUID, dest.UUID, instance.InventoryClipboard.Item.Name);
1954                     }
1955                 }
1956
1957                 instance.InventoryClipboard = null;
1958             }
1959             else if (instance.InventoryClipboard.Operation == ClipboardOperation.Copy)
1960             {
1961                 if (instance.InventoryClipboard.Item is InventoryItem)
1962                 {
1963                     client.Inventory.RequestCopyItem(instance.InventoryClipboard.Item.UUID, dest.UUID, instance.InventoryClipboard.Item.Name, instance.InventoryClipboard.Item.OwnerID, (InventoryBase target) =>
1964                     {
1965                     }
1966                     );
1967                 }
1968                 else if (instance.InventoryClipboard.Item is InventoryFolder)
1969                 {
1970                     WorkPool.QueueUserWorkItem((object state) =>
1971                         {
1972                             UUID newFolderID = client.Inventory.CreateFolder(dest.UUID, instance.InventoryClipboard.Item.Name, AssetType.Unknown);
1973                             Thread.Sleep(500);
1974
1975                             // FIXME: for some reason copying a bunch of items in one operation does not work
1976
1977                             //List<UUID> items = new List<UUID>();
1978                             //List<UUID> folders = new List<UUID>();
1979                             //List<string> names = new List<string>();
1980                             //UUID oldOwner = UUID.Zero;
1981
1982                             foreach (InventoryBase oldItem in Inventory.GetContents((InventoryFolder)instance.InventoryClipboard.Item))
1983                             {
1984                                 //folders.Add(newFolderID);
1985                                 //names.Add(oldItem.Name);
1986                                 //items.Add(oldItem.UUID);
1987                                 //oldOwner = oldItem.OwnerID;
1988                                 client.Inventory.RequestCopyItem(oldItem.UUID, newFolderID, oldItem.Name, oldItem.OwnerID, (InventoryBase target) => { });
1989                             }
1990
1991                             //if (folders.Count > 0)
1992                             //{
1993                             //    client.Inventory.RequestCopyItems(items, folders, names, oldOwner, (InventoryBase target) => { });
1994                             //}
1995                         }
1996                     );
1997                 }
1998             }
1999         }
2000
2001         void PerformLinkOperation(InventoryFolder dest)
2002         {
2003             if (instance.InventoryClipboard == null) return;
2004
2005             if (dest == null) return;
2006
2007             client.Inventory.CreateLink(dest.UUID, instance.InventoryClipboard.Item, (bool success, InventoryItem item) => { });
2008         }
2009
2010         #endregion
2011
2012         private void UpdateWornLabels()
2013         {
2014             if (InvokeRequired)
2015             {
2016                 BeginInvoke(new MethodInvoker(UpdateWornLabels));
2017                 return;
2018             }
2019
2020             invTree.BeginUpdate();
2021             foreach (UUID itemID in WornItems)
2022             {
2023                 TreeNode node = findNodeForItem(itemID);
2024                 if (node != null)
2025                 {
2026                     node.Text = ItemLabel((InventoryBase)node.Tag, false);
2027                 }
2028             }
2029             WornItems.Clear();
2030             foreach (AppearanceManager.WearableData wearable in client.Appearance.GetWearables().Values)
2031             {
2032                 TreeNode node = findNodeForItem(wearable.ItemID);
2033                 if (node != null)
2034                 {
2035                     node.Text = ItemLabel((InventoryBase)node.Tag, false);
2036                 }
2037             }
2038             invTree.EndUpdate();
2039         }
2040
2041         void TreeView_AfterExpand(object sender, TreeViewEventArgs e)
2042         {
2043             // Check if we need to go into edit mode for new items
2044             if (newItemName != string.Empty)
2045             {
2046                 foreach (TreeNode n in e.Node.Nodes)
2047                 {
2048                     if (n.Name == newItemName)
2049                     {
2050                         n.BeginEdit();
2051                         break;
2052                     }
2053                 }
2054                 newItemName = string.Empty;
2055             }
2056         }
2057
2058         private bool _EditingNode = false;
2059
2060         private void OnLabelEditTimer(object sender)
2061         {
2062             if (_EditNode == null || !(_EditNode.Tag is InventoryBase))
2063                 return;
2064
2065             if (InvokeRequired)
2066             {
2067                 BeginInvoke(new MethodInvoker(delegate()
2068                     {
2069                         OnLabelEditTimer(sender);
2070                     }
2071                 ));
2072                 return;
2073             }
2074
2075             _EditingNode = true;
2076             _EditNode.Text = ItemLabel((InventoryBase)_EditNode.Tag, true);
2077             _EditNode.BeginEdit();
2078         }
2079
2080         private void invTree_BeforeLabelEdit(object sender, NodeLabelEditEventArgs e)
2081         {
2082             if (e.Node == null ||
2083                 !(e.Node.Tag is InventoryBase) ||
2084                 (e.Node.Tag is InventoryFolder &&
2085                 ((InventoryFolder)e.Node.Tag).PreferredType != AssetType.Unknown &&
2086                 ((InventoryFolder)e.Node.Tag).PreferredType != AssetType.OutfitFolder)
2087                 )
2088             {
2089                 e.CancelEdit = true;
2090                 return;
2091             }
2092
2093             if (_EditingNode)
2094             {
2095                 _EditingNode = false;
2096             }
2097             else
2098             {
2099                 e.CancelEdit = true;
2100                 _EditNode = e.Node;
2101                 _EditTimer.Change(20, System.Threading.Timeout.Infinite);
2102             }
2103         }
2104
2105         private void invTree_AfterLabelEdit(object sender, NodeLabelEditEventArgs e)
2106         {
2107             if (string.IsNullOrEmpty(e.Label))
2108             {
2109                 if (e.Node.Tag is InventoryBase)
2110                 {
2111                     e.Node.Text = ItemLabel((InventoryBase)e.Node.Tag, false);
2112                 }
2113                 e.CancelEdit = true;
2114                 return;
2115             }
2116
2117             if (e.Node.Tag is InventoryFolder)
2118             {
2119                 InventoryFolder f = (InventoryFolder)e.Node.Tag;
2120                 f.Name = e.Label;
2121                 client.Inventory.MoveFolder(f.UUID, f.ParentUUID, f.Name);
2122             }
2123             else if (e.Node.Tag is InventoryItem)
2124             {
2125                 InventoryItem item = (InventoryItem)e.Node.Tag;
2126                 item.Name = e.Label;
2127                 e.Node.Text = ItemLabel((InventoryBase)item, false);
2128                 client.Inventory.MoveItem(item.UUID, item.ParentUUID, item.Name);
2129                 UpdateItemInfo(item);
2130             }
2131
2132         }
2133
2134         private void invTree_KeyUp(object sender, KeyEventArgs e)
2135         {
2136             if (e.KeyCode == Keys.F2 && invTree.SelectedNode != null)
2137             {
2138                 invTree.SelectedNode.BeginEdit();
2139             }
2140             else if (e.KeyCode == Keys.F5 && invTree.SelectedNode != null)
2141             {
2142                 if (invTree.SelectedNode.Tag is InventoryFolder)
2143                 {
2144                     InventoryFolder f = (InventoryFolder)invTree.SelectedNode.Tag;
2145                     fetchFolder(f.UUID, f.OwnerID, true);
2146                 }
2147             }
2148             else if (e.KeyCode == Keys.Delete && invTree.SelectedNode != null)
2149             {
2150                 if (invTree.SelectedNode.Tag is InventoryItem)
2151                 {
2152                     InventoryItem item = invTree.SelectedNode.Tag as InventoryItem;
2153                     client.Inventory.MoveItem(item.UUID, client.Inventory.FindFolderForType(AssetType.TrashFolder), item.Name);
2154                 }
2155                 else if (invTree.SelectedNode.Tag is InventoryFolder)
2156                 {
2157                     InventoryFolder f = invTree.SelectedNode.Tag as InventoryFolder;
2158                     client.Inventory.MoveFolder(f.UUID, client.Inventory.FindFolderForType(AssetType.TrashFolder), f.Name);
2159                 }
2160             }
2161             else if (e.KeyCode == Keys.Apps && invTree.SelectedNode != null)
2162             {
2163                 ctxInv.Show();
2164             }
2165         }
2166
2167         #region Drag and Drop
2168         private void invTree_ItemDrag(object sender, ItemDragEventArgs e)
2169         {
2170             invTree.SelectedNode = e.Item as TreeNode;
2171             if (invTree.SelectedNode.Tag is InventoryFolder && ((InventoryFolder)invTree.SelectedNode.Tag).PreferredType != AssetType.Unknown)
2172             {
2173                 return;
2174             }
2175             invTree.DoDragDrop(e.Item, DragDropEffects.Move);
2176         }
2177
2178         private void invTree_DragDrop(object sender, DragEventArgs e)
2179         {
2180             if (highlightedNode != null)
2181             {
2182                 highlightedNode.BackColor = invTree.BackColor;
2183                 highlightedNode = null;
2184             }
2185
2186             TreeNode sourceNode = e.Data.GetData(typeof(TreeNode)) as TreeNode;
2187             if (sourceNode == null) return;
2188
2189             Point pt = ((TreeView)sender).PointToClient(new Point(e.X, e.Y));
2190             TreeNode destinationNode = ((TreeView)sender).GetNodeAt(pt);
2191
2192             if (destinationNode == null) return;
2193
2194             if (sourceNode == destinationNode) return;
2195
2196             // If droping to item within folder drop to its folder
2197             if (destinationNode.Tag is InventoryItem)
2198             {
2199                 destinationNode = destinationNode.Parent;
2200             }
2201
2202             InventoryFolder dest = destinationNode.Tag as InventoryFolder;
2203
2204             if (dest == null) return;
2205
2206             if (sourceNode.Tag is InventoryItem)
2207             {
2208                 InventoryItem item = (InventoryItem)sourceNode.Tag;
2209                 client.Inventory.MoveItem(item.UUID, dest.UUID, item.Name);
2210             }
2211             else if (sourceNode.Tag is InventoryFolder)
2212             {
2213                 InventoryFolder f = (InventoryFolder)sourceNode.Tag;
2214                 client.Inventory.MoveFolder(f.UUID, dest.UUID, f.Name);
2215             }
2216         }
2217
2218         private void invTree_DragEnter(object sender, DragEventArgs e)
2219         {
2220             TreeNode node = e.Data.GetData(typeof(TreeNode)) as TreeNode;
2221             if (node == null)
2222             {
2223                 e.Effect = DragDropEffects.None;
2224             }
2225             else
2226             {
2227                 e.Effect = DragDropEffects.Move;
2228             }
2229         }
2230
2231         TreeNode highlightedNode = null;
2232
2233         private void invTree_DragOver(object sender, DragEventArgs e)
2234         {
2235             TreeNode node = e.Data.GetData(typeof(TreeNode)) as TreeNode;
2236             if (node == null)
2237             {
2238                 e.Effect = DragDropEffects.None;
2239             }
2240
2241             Point pt = ((TreeView)sender).PointToClient(new Point(e.X, e.Y));
2242             TreeNode destinationNode = ((TreeView)sender).GetNodeAt(pt);
2243
2244             if (highlightedNode != destinationNode)
2245             {
2246                 if (highlightedNode != null)
2247                 {
2248                     highlightedNode.BackColor = invTree.BackColor;
2249                     highlightedNode = null;
2250                 }
2251
2252                 if (destinationNode != null)
2253                 {
2254                     highlightedNode = destinationNode;
2255                     highlightedNode.BackColor = Color.LightSlateGray;
2256                 }
2257             }
2258
2259             if (destinationNode == null)
2260             {
2261                 e.Effect = DragDropEffects.None;
2262                 return;
2263             }
2264
2265             e.Effect = DragDropEffects.Move;
2266
2267         }
2268         #endregion
2269
2270         private void saveAllTToolStripMenuItem_Click(object sender, EventArgs e)
2271         {
2272             (new InventoryBackup(instance, Inventory.RootFolder.UUID)).Show();
2273         }
2274
2275         private void tbtnSystemFoldersFirst_Click(object sender, EventArgs e)
2276         {
2277             sorter.SystemFoldersFirst = tbtnSystemFoldersFirst.Checked = !sorter.SystemFoldersFirst;
2278             instance.GlobalSettings["inv_sort_sysfirst"] = OSD.FromBoolean(sorter.SystemFoldersFirst);
2279             invTree.Sort();
2280         }
2281
2282         private void tbtbSortByName_Click(object sender, EventArgs e)
2283         {
2284             if (tbtbSortByName.Checked) return;
2285
2286             tbtbSortByName.Checked = true;
2287             tbtnSortByDate.Checked = sorter.ByDate = false;
2288             instance.GlobalSettings["inv_sort_bydate"] = OSD.FromBoolean(sorter.ByDate);
2289
2290             invTree.Sort();
2291         }
2292
2293         private void tbtnSortByDate_Click(object sender, EventArgs e)
2294         {
2295             if (tbtnSortByDate.Checked) return;
2296
2297             tbtbSortByName.Checked = false;
2298             tbtnSortByDate.Checked = sorter.ByDate = true;
2299             instance.GlobalSettings["inv_sort_bydate"] = OSD.FromBoolean(sorter.ByDate);
2300
2301             invTree.Sort();
2302         }
2303
2304         #region Search
2305
2306         public class SearchResult
2307         {
2308             public InventoryBase Inv;
2309             public int Level;
2310
2311             public SearchResult(InventoryBase inv, int level)
2312             {
2313                 this.Inv = inv;
2314                 this.Level = level;
2315             }
2316         }
2317
2318         List<SearchResult> searchRes;
2319         string searchString;
2320         Dictionary<int, ListViewItem> searchItemCache = new Dictionary<int, ListViewItem>();
2321         ListViewItem emptyItem = null;
2322         int found;
2323
2324         void PerformRecursiveSearch(int level, UUID folderID)
2325         {
2326             var me = Inventory.Items[folderID].Data;
2327             searchRes.Add(new SearchResult(me, level));
2328             var sorted = Inventory.GetContents(folderID);
2329
2330             sorted.Sort((InventoryBase b1, InventoryBase b2) =>
2331             {
2332                 if (b1 is InventoryFolder && !(b2 is InventoryFolder))
2333                 {
2334                     return -1;
2335                 }
2336                 else if (!(b1 is InventoryFolder) && b2 is InventoryFolder)
2337                 {
2338                     return 1;
2339                 }
2340                 else
2341                 {
2342                     return string.Compare(b1.Name, b2.Name);
2343                 }
2344             });
2345
2346             foreach (var item in sorted)
2347             {
2348                 if (item is InventoryFolder)
2349                 {
2350                     PerformRecursiveSearch(level + 1, item.UUID);
2351                 }
2352                 else
2353                 {
2354                     var it = item as InventoryItem;
2355                     bool add = false;
2356
2357                     if (cbSrchName.Checked && it.Name.ToLower().Contains(searchString))
2358                     {
2359                         add = true;
2360                     }
2361                     else if (cbSrchDesc.Checked && it.Description.ToLower().Contains(searchString))
2362                     {
2363                         add = true;
2364                     }
2365
2366                     if (cbSrchWorn.Checked && add &&
2367                             !(
2368                                 (it.InventoryType == InventoryType.Wearable && IsWorn(it)) ||
2369                                 ((it.InventoryType == InventoryType.Attachment || it.InventoryType == InventoryType.Object) && IsAttached(it))
2370                             )
2371                     )
2372                     {
2373                         add = false;
2374                     }
2375
2376                     if (cbSrchRecent.Checked && add && it.CreationDate < instance.StartupTimeUTC)
2377                     {
2378                         add = false;
2379                     }
2380
2381                     if (add)
2382                     {
2383                         found++;
2384                         searchRes.Add(new SearchResult(it, level + 1));
2385                     }
2386                 }
2387             }
2388
2389             if (searchRes[searchRes.Count - 1].Inv == me)
2390             {
2391                 searchRes.RemoveAt(searchRes.Count - 1);
2392             }
2393         }
2394
2395         public void UpdateSearch()
2396         {
2397             found = 0;
2398
2399             if (instance.MonoRuntime)
2400             {
2401                 lstInventorySearch.VirtualMode = false;
2402                 lstInventorySearch.Items.Clear();
2403                 lstInventorySearch.VirtualMode = true;
2404             }
2405
2406             lstInventorySearch.VirtualListSize = 0;
2407             searchString = txtSearch.Text.Trim().ToLower();
2408
2409             //if (searchString == string.Empty && rbSrchAll.Checked)
2410             //{
2411             //    lblSearchStatus.Text = "0 results";
2412             //    return;
2413             //}
2414
2415             if (emptyItem == null)
2416             {
2417                 emptyItem = new ListViewItem(string.Empty);
2418             }
2419
2420             searchRes = new List<SearchResult>(Inventory.Items.Count);
2421             searchItemCache.Clear();
2422             PerformRecursiveSearch(0, Inventory.RootFolder.UUID);
2423             lstInventorySearch.VirtualListSize = searchRes.Count;
2424             lblSearchStatus.Text = string.Format("{0} results", found);
2425         }
2426
2427         private void lstInventorySearch_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
2428         {
2429             if (searchItemCache.ContainsKey(e.ItemIndex))
2430             {
2431                 e.Item = searchItemCache[e.ItemIndex];
2432             }
2433             else if (e.ItemIndex < searchRes.Count)
2434             {
2435                 InventoryBase inv = searchRes[e.ItemIndex].Inv;
2436                 string desc = inv.Name;
2437                 if (inv is InventoryItem)
2438                 {
2439                     desc += string.Format(" - {0}", ((InventoryItem)inv).Description);
2440                 }
2441                 ListViewItem item = new ListViewItem(desc);
2442                 item.Tag = searchRes[e.ItemIndex];
2443                 e.Item = item;
2444                 searchItemCache[e.ItemIndex] = item;
2445             }
2446             else
2447             {
2448                 e.Item = emptyItem;
2449             }
2450         }
2451
2452         private void btnInvSearch_Click(object sender, EventArgs e)
2453         {
2454             UpdateSearch();
2455         }
2456
2457         private void cbSrchName_CheckedChanged(object sender, EventArgs e)
2458         {
2459             if (!cbSrchName.Checked && !cbSrchDesc.Checked && !cbSrchCreator.Checked)
2460             {
2461                 cbSrchName.Checked = true;
2462             }
2463             UpdateSearch();
2464         }
2465
2466         private void cbSrchWorn_CheckedChanged(object sender, EventArgs e)
2467         {
2468             UpdateSearch();
2469         }
2470
2471         private void txtSearch_KeyDown(object sender, KeyEventArgs e)
2472         {
2473             if (e.KeyCode == Keys.Enter)
2474             {
2475                 e.Handled = e.SuppressKeyPress = true;
2476                 if (txtSearch.Text.Trim().Length > 0)
2477                 {
2478                     UpdateSearch();
2479                 }
2480             }
2481         }
2482
2483         private void lstInventorySearch_DrawItem(object sender, DrawListViewItemEventArgs e)
2484         {
2485             Graphics g = e.Graphics;
2486             e.DrawBackground();
2487
2488             if (!(e.Item.Tag is SearchResult))
2489                 return;
2490
2491             if (e.Item.Selected)
2492             {
2493                 g.FillRectangle(SystemBrushes.Highlight, e.Bounds);
2494             }
2495
2496             SearchResult res = e.Item.Tag as SearchResult;
2497             int offset = 20 * (res.Level + 1);
2498             Rectangle rec = new Rectangle(e.Bounds.X + offset, e.Bounds.Y, e.Bounds.Width - offset, e.Bounds.Height);
2499
2500             Image icon = null;
2501             int iconIx = 0;
2502
2503             if (res.Inv is InventoryFolder)
2504             {
2505                 iconIx = GetDirImageIndex(((InventoryFolder)res.Inv).PreferredType.ToString().ToLower());
2506             }
2507             else if (res.Inv is InventoryWearable)
2508             {
2509                 iconIx = GetItemImageIndex(((InventoryWearable)res.Inv).WearableType.ToString().ToLower());
2510             }
2511             else if (res.Inv is InventoryItem)
2512             {
2513                 iconIx = GetItemImageIndex(((InventoryItem)res.Inv).AssetType.ToString().ToLower());
2514             }
2515
2516             if (iconIx < 0)
2517             {
2518                 iconIx = 0;
2519             }
2520
2521             try
2522             {
2523                 icon = frmMain.ResourceImages.Images[iconIx];
2524                 g.DrawImageUnscaled(icon, e.Bounds.X + offset - 18, e.Bounds.Y);
2525             }
2526             catch { }
2527
2528             using (StringFormat sf = new StringFormat(StringFormatFlags.NoWrap | StringFormatFlags.LineLimit))
2529             {
2530                 string label = ItemLabel(res.Inv, false);
2531                 SizeF len = e.Graphics.MeasureString(label, lstInventorySearch.Font, rec.Width, sf);
2532
2533                 e.Graphics.DrawString(
2534                     ItemLabel(res.Inv, false),
2535                     lstInventorySearch.Font,
2536                     e.Item.Selected ? SystemBrushes.HighlightText : SystemBrushes.WindowText,
2537                     rec,
2538                     sf);
2539
2540                 if (res.Inv is InventoryItem)
2541                 {
2542                     string desc = ((InventoryItem)res.Inv).Description.Trim();
2543                     if (desc != string.Empty)
2544                     {
2545                         using (Font descFont = new Font(lstInventorySearch.Font, FontStyle.Italic))
2546                         {
2547                             e.Graphics.DrawString(desc,
2548                                 descFont,
2549                                 e.Item.Selected ? SystemBrushes.HighlightText : SystemBrushes.GrayText,
2550                                 rec.X + len.Width + 5,
2551                                 rec.Y,
2552                                 sf);
2553                         }
2554                     }
2555                 }
2556             }
2557
2558         }
2559
2560         private void lstInventorySearch_SizeChanged(object sender, EventArgs e)
2561         {
2562             chResItemName.Width = lstInventorySearch.Width - 30;
2563         }
2564
2565         private void txtSearch_TextChanged(object sender, EventArgs e)
2566         {
2567             UpdateSearch();
2568         }
2569
2570         private void rbSrchAll_CheckedChanged(object sender, EventArgs e)
2571         {
2572             UpdateSearch();
2573         }
2574
2575         private void lstInventorySearch_KeyDown(object sender, KeyEventArgs e)
2576         {
2577             if ((e.KeyCode == Keys.Apps) || (e.Control && e.KeyCode == RadegastContextMenuStrip.ContexMenuKeyCode))
2578             {
2579                 lstInventorySearch_MouseClick(sender, new MouseEventArgs(MouseButtons.Right, 1, 50, 150, 0));
2580             }
2581         }
2582
2583         /// <summary>
2584         /// Finds and higlights inventory node
2585         /// </summary>
2586         /// <param name="itemID">Inventory of ID of the item to select</param>
2587         public void SelectInventoryNode(UUID itemID)
2588         {
2589             TreeNode node = findNodeForItem(itemID);
2590             if (node == null)
2591                 return;
2592             invTree.SelectedNode = node;
2593             if (node.Tag is InventoryItem)
2594             {
2595                 UpdateItemInfo(node.Tag as InventoryItem);
2596             }
2597         }
2598
2599         private void lstInventorySearch_MouseClick(object sender, MouseEventArgs e)
2600         {
2601             if (lstInventorySearch.SelectedIndices.Count != 1)
2602                 return;
2603
2604             try
2605             {
2606                 SearchResult res = searchRes[lstInventorySearch.SelectedIndices[0]];
2607                 TreeNode node = findNodeForItem(res.Inv.UUID);
2608                 if (node == null)
2609                     return;
2610                 invTree.SelectedNode = node;
2611                 if (e.Button == MouseButtons.Right)
2612                 {
2613                     ctxInv.Show(lstInventorySearch, e.X, e.Y);
2614                 }
2615             }
2616             catch { }
2617         }
2618
2619         private void lstInventorySearch_MouseDoubleClick(object sender, MouseEventArgs e)
2620         {
2621             if (lstInventorySearch.SelectedIndices.Count != 1)
2622                 return;
2623
2624             try
2625             {
2626                 SearchResult res = searchRes[lstInventorySearch.SelectedIndices[0]];
2627                 TreeNode node = findNodeForItem(res.Inv.UUID);
2628                 if (node == null)
2629                     return;
2630                 invTree.SelectedNode = node;
2631                 invTree_NodeMouseDoubleClick(null, null);
2632             }
2633             catch { }
2634
2635         }
2636         #endregion Search
2637
2638         private void txtAssetID_Enter(object sender, EventArgs e)
2639         {
2640             txtAssetID.SelectAll();
2641         }
2642
2643         private void txtInvID_Enter(object sender, EventArgs e)
2644         {
2645             txtInvID.SelectAll();
2646         }
2647
2648         private void copyInitialOutfitsToolStripMenuItem_Click(object sender, EventArgs e)
2649         {
2650             var c = new FolderCopy(instance);
2651             c.GetFolders("Initial Outfits");
2652         }
2653
2654
2655     }
2656
2657     #region Sorter class
2658     // Create a node sorter that implements the IComparer interface.
2659     public class InvNodeSorter : System.Collections.IComparer
2660     {
2661         bool _sysfirst = true;
2662         bool _bydate = true;
2663
2664         int CompareFolders(InventoryFolder x, InventoryFolder y)
2665         {
2666             if (_sysfirst)
2667             {
2668                 if (x.PreferredType != AssetType.Unknown && y.PreferredType == AssetType.Unknown)
2669                 {
2670                     return -1;
2671                 }
2672                 else if (x.PreferredType == AssetType.Unknown && y.PreferredType != AssetType.Unknown)
2673                 {
2674                     return 1;
2675                 }
2676             }
2677             return String.Compare(x.Name, y.Name);
2678         }
2679
2680         public bool SystemFoldersFirst { set { _sysfirst = value; } get { return _sysfirst; } }
2681         public bool ByDate { set { _bydate = value; } get { return _bydate; } }
2682
2683         public int Compare(object x, object y)
2684         {
2685             TreeNode tx = x as TreeNode;
2686             TreeNode ty = y as TreeNode;
2687
2688             if (tx.Tag is InventoryFolder && ty.Tag is InventoryFolder)
2689             {
2690                 return CompareFolders(tx.Tag as InventoryFolder, ty.Tag as InventoryFolder);
2691             }
2692             else if (tx.Tag is InventoryFolder && ty.Tag is InventoryItem)
2693             {
2694                 return -1;
2695             }
2696             else if (tx.Tag is InventoryItem && ty.Tag is InventoryFolder)
2697             {
2698                 return 1;
2699             }
2700
2701             // Two items
2702             if (!(tx.Tag is InventoryItem) || !(ty.Tag is InventoryItem))
2703             {
2704                 return 0;
2705             }
2706
2707             InventoryItem item1 = (InventoryItem)tx.Tag;
2708             InventoryItem item2 = (InventoryItem)ty.Tag;
2709
2710             if (_bydate)
2711             {
2712                 if (item1.CreationDate < item2.CreationDate)
2713                 {
2714                     return 1;
2715                 }
2716                 else if (item1.CreationDate > item2.CreationDate)
2717                 {
2718                     return -1;
2719                 }
2720             }
2721             return string.Compare(item1.Name, item2.Name);
2722         }
2723     }
2724     #endregion
2725
2726     public class AttachmentInfo
2727     {
2728         public Primitive Prim;
2729         public InventoryItem Item;
2730         public UUID InventoryID;
2731         public UUID PrimID;
2732         public bool MarkedAttached = false;
2733
2734         public AttachmentPoint Point
2735         {
2736             get
2737             {
2738                 if (Prim != null)
2739                 {
2740                     return Prim.PrimData.AttachmentPoint;
2741                 }
2742                 else
2743                 {
2744                     return AttachmentPoint.Default;
2745                 }
2746             }
2747         }
2748     }
2749
2750
2751 }