OSDN Git Service

RAD-490: Add a Automaticly Accept Dialog Function
[radegast/radegast.git] / Radegast / GUI / Consoles / TabsConsole.cs
1 // 
2 // Radegast Metaverse Client
3 // Copyright (c) 2009-2014, 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.Generic;
33 using System.ComponentModel;
34 using System.Drawing;
35 using System.Windows.Forms;
36 using Radegast.Netcom;
37 using OpenMetaverse;
38
39 namespace Radegast
40 {
41     public partial class TabsConsole : UserControl
42     {
43
44         /// <summary>
45         /// List of nearby avatars (radar data)
46         /// </summary>
47         public List<NearbyAvatar> NearbyAvatars
48         {
49             get
50             {
51                 List<NearbyAvatar> res = new List<NearbyAvatar>();
52
53                 if (TabExists("chat"))
54                 {
55                     ChatConsole c = (ChatConsole)Tabs["chat"].Control;
56                     lock (c.agentSimHandle)
57                     {
58                         foreach (ListViewItem item in c.lvwObjects.Items)
59                         {
60                             if (item.Name != client.Self.AgentID.ToString())
61                             {
62                                 res.Add(new NearbyAvatar() { ID = new UUID(item.Name), Name = item.Text });
63                             }
64                         }
65                     }
66                 }
67
68                 return res;
69             }
70         }
71
72         /// <summary>
73         /// Delegate invoked on tab operations
74         /// </summary>
75         /// <param name="sender">Event sender</param>
76         /// <param name="e">Event arguments</param>
77         public delegate void TabCallback(object sender, TabEventArgs e);
78
79         /// <summary>
80         /// Fired when a tab is selected
81         /// </summary>
82         public event TabCallback OnTabSelected;
83
84
85         /// <summary>
86         /// Delegate invoked when chat notification is printed
87         /// </summary>
88         /// <param name="sender">Event sender</param>
89         /// <param name="e">Event arguments</param>
90         public delegate void ChatNotificationCallback(object sender, ChatNotificationEventArgs e);
91
92         /// <summary>
93         /// Fired when a tab is selected
94         /// </summary>
95         public event ChatNotificationCallback OnChatNotification;
96
97         /// <summary>
98         /// Fired when a new tab is added
99         /// </summary>
100         public event TabCallback OnTabAdded;
101
102         /// <summary>
103         /// Fired when a tab is removed
104         /// </summary>
105         public event TabCallback OnTabRemoved;
106
107         private RadegastInstance instance;
108         private GridClient client { get { return instance.Client; } }
109         private RadegastNetcom netcom { get { return instance.Netcom; } }
110         private ChatTextManager mainChatManger;
111         public ChatTextManager MainChatManger { get { return mainChatManger; } }
112
113         private Dictionary<string, RadegastTab> tabs = new Dictionary<string, RadegastTab>();
114         public Dictionary<string, RadegastTab> Tabs { get { return tabs; } }
115
116         private ChatConsole chatConsole;
117
118         private RadegastTab selectedTab;
119
120         /// <summary>
121         /// Currently selected tab
122         /// </summary>
123         public RadegastTab SelectedTab
124         {
125             get
126             {
127                 return selectedTab;
128             }
129         }
130
131         private Form owner;
132
133         public TabsConsole(RadegastInstance instance)
134         {
135             InitializeComponent();
136             Disposed += new EventHandler(TabsConsole_Disposed);
137
138             this.instance = instance;
139             this.instance.ClientChanged += new EventHandler<ClientChangedEventArgs>(instance_ClientChanged);
140
141             AddNetcomEvents();
142
143             InitializeMainTab();
144             InitializeChatTab();
145
146             // Callbacks
147             RegisterClientEvents(client);
148         }
149
150         private void RegisterClientEvents(GridClient client)
151         {
152             client.Self.ScriptQuestion += new EventHandler<ScriptQuestionEventArgs>(Self_ScriptQuestion);
153             client.Self.ScriptDialog += new EventHandler<ScriptDialogEventArgs>(Self_ScriptDialog);
154             client.Self.LoadURL += new EventHandler<LoadUrlEventArgs>(Self_LoadURL);
155             client.Self.SetDisplayNameReply += new EventHandler<SetDisplayNameReplyEventArgs>(Self_SetDisplayNameReply);
156             client.Avatars.DisplayNameUpdate += new EventHandler<DisplayNameUpdateEventArgs>(Avatars_DisplayNameUpdate);
157             client.Network.EventQueueRunning += new EventHandler<EventQueueRunningEventArgs>(Network_EventQueueRunning);
158             client.Network.RegisterCallback(OpenMetaverse.Packets.PacketType.ScriptTeleportRequest, ScriptTeleportRequestHandler);
159         }
160
161         private void UnregisterClientEvents(GridClient client)
162         {
163             client.Self.ScriptQuestion -= new EventHandler<ScriptQuestionEventArgs>(Self_ScriptQuestion);
164             client.Self.ScriptDialog -= new EventHandler<ScriptDialogEventArgs>(Self_ScriptDialog);
165             client.Self.LoadURL -= new EventHandler<LoadUrlEventArgs>(Self_LoadURL);
166             client.Self.SetDisplayNameReply -= new EventHandler<SetDisplayNameReplyEventArgs>(Self_SetDisplayNameReply);
167             client.Avatars.DisplayNameUpdate -= new EventHandler<DisplayNameUpdateEventArgs>(Avatars_DisplayNameUpdate);
168             client.Network.EventQueueRunning -= new EventHandler<EventQueueRunningEventArgs>(Network_EventQueueRunning);
169             client.Network.UnregisterCallback(OpenMetaverse.Packets.PacketType.ScriptTeleportRequest, ScriptTeleportRequestHandler);
170         }
171
172         void instance_ClientChanged(object sender, ClientChangedEventArgs e)
173         {
174             UnregisterClientEvents(e.OldClient);
175             RegisterClientEvents(client);
176         }
177
178         void TabsConsole_Disposed(object sender, EventArgs e)
179         {
180             RemoveNetcomEvents();
181             UnregisterClientEvents(client);
182         }
183
184         private void AddNetcomEvents()
185         {
186             netcom.ClientLoginStatus += new EventHandler<LoginProgressEventArgs>(netcom_ClientLoginStatus);
187             netcom.ClientLoggedOut += new EventHandler(netcom_ClientLoggedOut);
188             netcom.ClientDisconnected += new EventHandler<DisconnectedEventArgs>(netcom_ClientDisconnected);
189             netcom.ChatSent += new EventHandler<ChatSentEventArgs>(netcom_ChatSent);
190             netcom.AlertMessageReceived += new EventHandler<AlertMessageEventArgs>(netcom_AlertMessageReceived);
191             netcom.InstantMessageReceived += new EventHandler<InstantMessageEventArgs>(netcom_InstantMessageReceived);
192         }
193
194         private void RemoveNetcomEvents()
195         {
196             netcom.ClientLoginStatus -= new EventHandler<LoginProgressEventArgs>(netcom_ClientLoginStatus);
197             netcom.ClientLoggedOut -= new EventHandler(netcom_ClientLoggedOut);
198             netcom.ClientDisconnected -= new EventHandler<DisconnectedEventArgs>(netcom_ClientDisconnected);
199             netcom.ChatSent -= new EventHandler<ChatSentEventArgs>(netcom_ChatSent);
200             netcom.AlertMessageReceived -= new EventHandler<AlertMessageEventArgs>(netcom_AlertMessageReceived);
201             netcom.InstantMessageReceived -= new EventHandler<InstantMessageEventArgs>(netcom_InstantMessageReceived);
202         }
203
204         void ScriptTeleportRequestHandler(object sender, PacketReceivedEventArgs e)
205         {
206             if (InvokeRequired)
207             {
208                 if (IsHandleCreated || !instance.MonoRuntime)
209                     BeginInvoke(new MethodInvoker(() => ScriptTeleportRequestHandler(sender, e)));
210                 return;
211             }
212
213             var msg = (OpenMetaverse.Packets.ScriptTeleportRequestPacket)e.Packet;
214
215             if (TabExists("map"))
216             {
217                 Tabs["map"].Select();
218                 ((MapConsole)Tabs["map"].Control).CenterOnGlobalPos(
219                     (float)(client.Self.GlobalPosition.X - client.Self.SimPosition.X) + msg.Data.SimPosition.X,
220                     (float)(client.Self.GlobalPosition.Y - client.Self.SimPosition.Y) + msg.Data.SimPosition.Y,
221                     msg.Data.SimPosition.Z);
222             }
223         }
224
225         void Network_EventQueueRunning(object sender, EventQueueRunningEventArgs e)
226         {
227             if (InvokeRequired)
228             {
229                 BeginInvoke(new MethodInvoker(() => Network_EventQueueRunning(sender, e)));
230                 return;
231             }
232
233             if (TabExists("friends")) return;
234             if (e.Simulator == client.Network.CurrentSim)
235             {
236                 client.Self.UpdateAgentLanguage("en", true);
237                 InitializeOnlineTabs();
238             }
239         }
240
241         void Self_ScriptDialog(object sender, ScriptDialogEventArgs e)
242         {
243             if (instance.MainForm.InvokeRequired)
244             {
245                 instance.MainForm.BeginInvoke(new MethodInvoker(() => Self_ScriptDialog(sender, e)));
246                 return;
247             }
248
249             // Is this object muted
250             if (null != client.Self.MuteList.Find(m => (m.Type == MuteType.Object && m.ID == e.ObjectID) // muted object by id
251                 || (m.Type == MuteType.ByName && m.Name == e.ObjectName) // object muted by name
252                 )) return;
253
254             instance.MainForm.AddNotification(new ntfScriptDialog(instance, e.Message, e.ObjectName, e.ImageID, e.ObjectID, e.FirstName, e.LastName, e.Channel, e.ButtonLabels));
255         }
256
257         void Self_ScriptQuestion(object sender, ScriptQuestionEventArgs e)
258         {
259             // Is this object muted
260             if (null != client.Self.MuteList.Find(m => (m.Type == MuteType.Object && m.ID == e.TaskID) // muted object by id
261                 || (m.Type == MuteType.ByName && m.Name == e.ObjectName) // object muted by name
262                 )) return;
263
264             if (instance.GlobalSettings["on_script_question"] == "Auto Accept")
265             {
266                 instance.Client.Self.ScriptQuestionReply(e.Simulator, e.ItemID, e.TaskID, e.Questions);
267             }
268             else if (instance.GlobalSettings["on_script_question"] == "Auto Decline")
269             {
270                 instance.Client.Self.ScriptQuestionReply(e.Simulator, e.ItemID, e.TaskID, 0);
271             }
272             else
273             {
274                 instance.MainForm.AddNotification(new ntfPermissions(instance, e.Simulator, e.TaskID, e.ItemID, e.ObjectName, e.ObjectOwnerName, e.Questions));
275             }
276         }
277
278         private void netcom_ClientLoginStatus(object sender, LoginProgressEventArgs e)
279         {
280             if (e.Status == LoginStatus.Failed)
281             {
282                 DisplayNotificationInChat("Login error: " + e.Message, ChatBufferTextStyle.Error);
283             }
284             else if (e.Status == LoginStatus.Success)
285             {
286                 DisplayNotificationInChat("Logged in as " + netcom.LoginOptions.FullName + ".", ChatBufferTextStyle.StatusDarkBlue);
287                 DisplayNotificationInChat("Login reply: " + e.Message, ChatBufferTextStyle.StatusDarkBlue);
288
289                 if (tabs.ContainsKey("login"))
290                 {
291                     if (selectedTab.Name == "login")
292                         SelectDefaultTab();
293                     ForceCloseTab("login");
294                 }
295
296                 client.Self.RetrieveInstantMessages();
297             }
298         }
299
300         private void netcom_ClientLoggedOut(object sender, EventArgs e)
301         {
302             DisposeOnlineTabs();
303
304             SelectDefaultTab();
305             DisplayNotificationInChat("Logged out.");
306
307         }
308
309         private void netcom_ClientDisconnected(object sender, DisconnectedEventArgs e)
310         {
311             if (e.Reason == NetworkManager.DisconnectType.ClientInitiated) return;
312
313             DisposeOnlineTabs();
314             SelectDefaultTab();
315             DisplayNotificationInChat("Disconnected: " + e.Message, ChatBufferTextStyle.Error);
316         }
317
318         void Avatars_DisplayNameUpdate(object sender, DisplayNameUpdateEventArgs e)
319         {
320             DisplayNotificationInChat(string.Format("({0}) is now known as {1}", e.DisplayName.UserName, e.DisplayName.DisplayName));
321         }
322
323         void Self_SetDisplayNameReply(object sender, SetDisplayNameReplyEventArgs e)
324         {
325             if (e.Status == 200)
326             {
327                 DisplayNotificationInChat("You are now knows as " + e.DisplayName.DisplayName);
328             }
329             else
330             {
331                 DisplayNotificationInChat("Failed to set a new display name: " + e.Reason, ChatBufferTextStyle.Error);
332             }
333         }
334
335         private void netcom_AlertMessageReceived(object sender, AlertMessageEventArgs e)
336         {
337             tabs["chat"].Highlight();
338         }
339
340         private void netcom_ChatSent(object sender, ChatSentEventArgs e)
341         {
342             tabs["chat"].Highlight();
343         }
344
345         void Self_LoadURL(object sender, LoadUrlEventArgs e)
346         {
347             // Is the object or the owner muted?
348             if (null != client.Self.MuteList.Find(m => (m.Type == MuteType.Object && m.ID == e.ObjectID) // muted object by id 
349                 || (m.Type == MuteType.ByName && m.Name == e.ObjectName) // object muted by name
350                 || (m.Type == MuteType.Resident && m.ID == e.OwnerID) // object's owner muted
351                 )) return;
352
353             instance.MainForm.AddNotification(new ntfLoadURL(instance, e));
354         }
355
356         private void netcom_InstantMessageReceived(object sender, InstantMessageEventArgs e)
357         {
358             // Messaage from someone we muted?
359             if (null != client.Self.MuteList.Find(me => me.Type == MuteType.Resident && me.ID == e.IM.FromAgentID)) return;
360
361             try
362             {
363                 if (instance.State.LSLHelper.ProcessIM(e))
364                 {
365                     return;
366                 }
367             }
368             catch (Exception ex)
369             {
370                 Logger.Log("Failed executing automation action: " + ex.ToString(), Helpers.LogLevel.Warning);
371             }
372
373             switch (e.IM.Dialog)
374             {
375                 case InstantMessageDialog.SessionSend:
376                     if (instance.Groups.ContainsKey(e.IM.IMSessionID))
377                     {
378                         HandleGroupIM(e);
379                     }
380                     else
381                     {
382                         HandleConferenceIM(e);
383                     }
384                     break;
385
386                 case InstantMessageDialog.MessageFromAgent:
387                     if (e.IM.FromAgentName == "Second Life")
388                     {
389                         HandleIMFromObject(e);
390                     }
391                     else if (e.IM.FromAgentID == UUID.Zero)
392                     {
393                         instance.MainForm.AddNotification(new ntfGeneric(instance, e.IM.Message));
394                     }
395                     else if (e.IM.GroupIM || instance.Groups.ContainsKey(e.IM.IMSessionID))
396                     {
397                         HandleGroupIM(e);
398                     }
399                     else if (e.IM.BinaryBucket.Length > 1)
400                     { // conference
401                         HandleConferenceIM(e);
402                     }
403                     else if (e.IM.IMSessionID == UUID.Zero)
404                     {
405                         String msg = string.Format("Message from {0}: {1}", instance.Names.Get(e.IM.FromAgentID, e.IM.FromAgentName), e.IM.Message);
406                         instance.MainForm.AddNotification(new ntfGeneric(instance, msg));
407                         DisplayNotificationInChat(msg);
408                     }
409                     else
410                     {
411                         HandleIM(e);
412                     }
413                     break;
414
415                 case InstantMessageDialog.MessageFromObject:
416                     HandleIMFromObject(e);
417                     break;
418
419                 case InstantMessageDialog.StartTyping:
420                     if (TabExists(e.IM.FromAgentName))
421                     {
422                         RadegastTab tab = tabs[e.IM.FromAgentName.ToLower()];
423                         if (!tab.Highlighted) tab.PartialHighlight();
424                     }
425
426                     break;
427
428                 case InstantMessageDialog.StopTyping:
429                     if (TabExists(e.IM.FromAgentName))
430                     {
431                         RadegastTab tab = tabs[e.IM.FromAgentName.ToLower()];
432                         if (!tab.Highlighted) tab.Unhighlight();
433                     }
434
435                     break;
436
437                 case InstantMessageDialog.MessageBox:
438                     instance.MainForm.AddNotification(new ntfGeneric(instance, e.IM.Message));
439                     break;
440
441                 case InstantMessageDialog.RequestTeleport:
442                     if (instance.RLV.AutoAcceptTP(e.IM.FromAgentID))
443                     {
444                         DisplayNotificationInChat("Auto accepting teleprot from " + e.IM.FromAgentName);
445                         instance.Client.Self.TeleportLureRespond(e.IM.FromAgentID, e.IM.IMSessionID, true);
446                     }
447                     else
448                     {
449                         instance.MainForm.AddNotification(new ntfTeleport(instance, e.IM));
450                     }
451                     break;
452
453                 case InstantMessageDialog.RequestLure:
454                     instance.MainForm.AddNotification(new ntfRequestLure(instance, e.IM));
455                     break;
456
457                 case InstantMessageDialog.GroupInvitation:
458                     instance.MainForm.AddNotification(new ntfGroupInvitation(instance, e.IM));
459                     break;
460
461                 case InstantMessageDialog.FriendshipOffered:
462                     if (e.IM.FromAgentName == "Second Life")
463                     {
464                         HandleIMFromObject(e);
465                     }
466                     else
467                     {
468                         instance.MainForm.AddNotification(new ntfFriendshipOffer(instance, e.IM));
469                     }
470                     break;
471
472                 case InstantMessageDialog.InventoryAccepted:
473                     DisplayNotificationInChat(e.IM.FromAgentName + " accepted your inventory offer.");
474                     break;
475
476                 case InstantMessageDialog.InventoryDeclined:
477                     DisplayNotificationInChat(e.IM.FromAgentName + " declined your inventory offer.");
478                     break;
479
480                 case InstantMessageDialog.GroupNotice:
481                     // Is this group muted?
482                     if (null != client.Self.MuteList.Find(me => me.Type == MuteType.Group && me.ID == e.IM.FromAgentID)) break;
483
484                     instance.MainForm.AddNotification(new ntfGroupNotice(instance, e.IM));
485                     break;
486
487                 case InstantMessageDialog.InventoryOffered:
488                     var ion = new ntfInventoryOffer(instance, e.IM);
489                     instance.MainForm.AddNotification(ion);
490                     if (instance.GlobalSettings["inv_auto_accept_mode"].AsInteger() == 1)
491                     {
492                         ion.btnAccept.PerformClick();
493                     }
494                     else if (instance.GlobalSettings["inv_auto_accept_mode"].AsInteger() == 2)
495                     {
496                         ion.btnDiscard.PerformClick();
497                     }
498                     break;
499
500                 case InstantMessageDialog.TaskInventoryOffered:
501                     // Is the object muted by name?
502                     if (null != client.Self.MuteList.Find(me => me.Type == MuteType.ByName && me.Name == e.IM.FromAgentName)) break;
503
504                     var iont = new ntfInventoryOffer(instance, e.IM);
505                     instance.MainForm.AddNotification(iont);
506                     if (instance.GlobalSettings["inv_auto_accept_mode"].AsInteger() == 1)
507                     {
508                         iont.btnAccept.PerformClick();
509                     }
510                     else if (instance.GlobalSettings["inv_auto_accept_mode"].AsInteger() == 2)
511                     {
512                         iont.btnDiscard.PerformClick();
513                     }
514                     break;
515             }
516         }
517
518         /// <summary>
519         /// Make default tab (chat) active
520         /// </summary>
521         public void SelectDefaultTab()
522         {
523             if (IsHandleCreated && TabExists("chat"))
524                 tabs["chat"].Select();
525         }
526
527         /// <summary>
528         /// Displays notification in the main chat tab
529         /// </summary>
530         /// <param name="msg">Message to be printed in the chat tab</param>
531         public void DisplayNotificationInChat(string msg)
532         {
533             DisplayNotificationInChat(msg, ChatBufferTextStyle.ObjectChat);
534         }
535
536         /// <summary>
537         /// Displays notification in the main chat tab
538         /// </summary>
539         /// <param name="msg">Message to be printed in the chat tab</param>
540         /// <param name="style">Style of the message to be printed, normal, object, etc.</param>
541         public void DisplayNotificationInChat(string msg, ChatBufferTextStyle style)
542         {
543             DisplayNotificationInChat(msg, style, true);
544         }
545
546         /// <summary>
547         /// Displays notification in the main chat tab
548         /// </summary>
549         /// <param name="msg">Message to be printed in the chat tab</param>
550         /// <param name="style">Style of the message to be printed, normal, object, etc.</param>
551         /// <param name="highlightChatTab">Highligt (and flash in taskbar) chat tab if not selected</param>
552         public void DisplayNotificationInChat(string msg, ChatBufferTextStyle style, bool highlightChatTab)
553         {
554             if (InvokeRequired)
555             {
556                 if (!instance.MonoRuntime || IsHandleCreated)
557                     BeginInvoke(new MethodInvoker(() => DisplayNotificationInChat(msg, style, highlightChatTab)));
558                 return;
559             }
560
561             if (style != ChatBufferTextStyle.Invisible)
562             {
563                 ChatBufferItem line = new ChatBufferItem(
564                     DateTime.Now,
565                     string.Empty,
566                     UUID.Zero,
567                     msg,
568                     style
569                 );
570
571                 try
572                 {
573                     mainChatManger.ProcessBufferItem(line, true);
574                     if (highlightChatTab)
575                     {
576                         tabs["chat"].Highlight();
577                     }
578                 }
579                 catch (Exception) { }
580             }
581
582             if (OnChatNotification != null)
583             {
584                 try { OnChatNotification(this, new ChatNotificationEventArgs(msg, style)); }
585                 catch { }
586             }
587         }
588
589         private void HandleIMFromObject(InstantMessageEventArgs e)
590         {
591             // Is the object or the owner muted?
592             if (null != client.Self.MuteList.Find(m => (m.Type == MuteType.Object && m.ID == e.IM.IMSessionID) // muted object by id 
593                 || (m.Type == MuteType.ByName && m.Name == e.IM.FromAgentName) // object muted by name
594                 || (m.Type == MuteType.Resident && m.ID == e.IM.FromAgentID) // object's owner muted
595                 )) return;
596
597             DisplayNotificationInChat(e.IM.FromAgentName + ": " + e.IM.Message);
598         }
599
600         public static Control FindFocusedControl(Control control)
601         {
602             var container = control as ContainerControl;
603             while (container != null)
604             {
605                 control = container.ActiveControl;
606                 container = control as ContainerControl;
607             }
608             return control;
609         }
610
611         /// <summary>
612         /// Creates new IM tab if needed
613         /// </summary>
614         /// <param name="agentID">IM session with agentID</param>
615         /// <param name="label">Tab label</param>
616         /// <param name="makeActive">Should tab be selected and focused</param>
617         /// <returns>True if there was an existing IM tab, false if it was created</returns>
618         public bool ShowIMTab(UUID agentID, string label, bool makeActive)
619         {
620             if (instance.TabConsole.TabExists((client.Self.AgentID ^ agentID).ToString()))
621             {
622                 if (makeActive)
623                 {
624                     instance.TabConsole.SelectTab((client.Self.AgentID ^ agentID).ToString());
625                 }
626                 return false;
627             }
628
629             if (makeActive)
630             {
631                 instance.MediaManager.PlayUISound(UISounds.IMWindow);
632             }
633             else
634             {
635                 instance.MediaManager.PlayUISound(UISounds.IM);
636             }
637
638             Control active = FindFocusedControl(instance.MainForm);
639
640             instance.TabConsole.AddIMTab(agentID, client.Self.AgentID ^ agentID, label);
641
642             if (makeActive)
643             {
644                 instance.TabConsole.SelectTab((client.Self.AgentID ^ agentID).ToString());
645             }
646             else if (active != null)
647             {
648                 active.Focus();
649             }
650
651             return true;
652         }
653
654         private void HandleIM(InstantMessageEventArgs e)
655         {
656             bool isNew = ShowIMTab(e.IM.FromAgentID, e.IM.FromAgentName, false);
657             if (!TabExists(e.IM.IMSessionID.ToString())) return; // this should now exist. sanity check anyway
658             RadegastTab tab = tabs[e.IM.IMSessionID.ToString()];
659             tab.Highlight();
660
661             if (isNew)
662             {
663                 ((IMTabWindow)tab.Control).TextManager.ProcessIM(e);
664             }
665         }
666
667         private void HandleConferenceIM(InstantMessageEventArgs e)
668         {
669             if (TabExists(e.IM.IMSessionID.ToString()))
670             {
671                 RadegastTab tab = tabs[e.IM.IMSessionID.ToString()];
672                 tab.Highlight();
673                 return;
674             }
675
676             instance.MediaManager.PlayUISound(UISounds.IM);
677
678             Control active = FindFocusedControl(instance.MainForm);
679
680             ConferenceIMTabWindow imTab = AddConferenceIMTab(e.IM.IMSessionID, Utils.BytesToString(e.IM.BinaryBucket));
681             tabs[e.IM.IMSessionID.ToString()].Highlight();
682             imTab.TextManager.ProcessIM(e);
683
684             if (active != null)
685             {
686                 active.Focus();
687             }
688         }
689
690         private void HandleGroupIM(InstantMessageEventArgs e)
691         {
692             // Ignore group IM from a muted group
693             if (null != client.Self.MuteList.Find(me => me.Type == MuteType.Group && (me.ID == e.IM.IMSessionID || me.ID == e.IM.FromAgentID))) return;
694
695             if (TabExists(e.IM.IMSessionID.ToString()))
696             {
697                 RadegastTab tab = tabs[e.IM.IMSessionID.ToString()];
698                 tab.Highlight();
699                 return;
700             }
701
702             instance.MediaManager.PlayUISound(UISounds.IM);
703
704             Control active = FindFocusedControl(instance.MainForm);
705
706             GroupIMTabWindow imTab = AddGroupIMTab(e.IM.IMSessionID, Utils.BytesToString(e.IM.BinaryBucket));
707             imTab.TextManager.ProcessIM(e);
708             tabs[e.IM.IMSessionID.ToString()].Highlight();
709
710             if (active != null)
711             {
712                 active.Focus();
713             }
714         }
715
716         public void InitializeMainTab()
717         {
718             if (TabExists("login"))
719             {
720                 ForceCloseTab("login");
721             }
722
723             LoginConsole loginConsole = new LoginConsole(instance);
724
725             RadegastTab tab = AddTab("login", "Login", loginConsole);
726             tab.AllowClose = false;
727             tab.AllowDetach = false;
728             tab.AllowMerge = false;
729             tab.AllowHide = false;
730
731             loginConsole.RegisterTab(tab);
732         }
733
734         private void InitializeChatTab()
735         {
736             chatConsole = new ChatConsole(instance);
737             mainChatManger = chatConsole.ChatManager;
738
739             RadegastTab tab = AddTab("chat", "Chat", chatConsole);
740             tab.AllowClose = false;
741             tab.AllowDetach = false;
742             tab.AllowHide = false;
743         }
744
745
746         /// <summary>
747         /// Create Tabs that only make sense when connected
748         /// </summary>
749         private void InitializeOnlineTabs()
750         {
751             RadegastTab tab = AddTab("friends", "Friends", new FriendsConsole(instance));
752             tab.AllowClose = false;
753             tab.AllowDetach = true;
754             tab.Visible = false;
755
756             tab = AddTab("groups", "Groups", new GroupsConsole(instance));
757             tab.AllowClose = false;
758             tab.AllowDetach = true;
759             tab.Visible = false;
760
761             // Ugly workaround for a mono bug
762             InventoryConsole inv = new InventoryConsole(instance);
763             if (instance.MonoRuntime) inv.invTree.Scrollable = false;
764             tab = AddTab("inventory", "Inventory", inv);
765             if (instance.MonoRuntime) inv.invTree.Scrollable = true;
766             tab.AllowClose = false;
767             tab.AllowDetach = true;
768             tab.Visible = false;
769
770             tab = AddTab("search", "Search", new SearchConsole(instance));
771             tab.AllowClose = false;
772             tab.AllowDetach = true;
773             tab.Visible = false;
774
775             tab = AddTab("map", "Map", new MapConsole(instance));
776             tab.AllowClose = false;
777             tab.AllowDetach = true;
778             tab.Visible = false;
779
780             tab = AddTab("voice", "Voice", new VoiceConsole(instance));
781             tab.AllowClose = false;
782             tab.AllowDetach = true;
783             tab.Visible = false;
784         }
785
786         /// <summary>
787         /// Close tabs that make no sense when not connected
788         /// </summary>
789         private void DisposeOnlineTabs()
790         {
791             lock (tabs)
792             {
793                 ForceCloseTab("voice");
794                 ForceCloseTab("map");
795                 ForceCloseTab("search");
796                 ForceCloseTab("inventory");
797                 ForceCloseTab("groups");
798                 ForceCloseTab("friends");
799             }
800         }
801
802         private void ForceCloseTab(string name)
803         {
804             if (!TabExists(name)) return;
805
806             RadegastTab tab = tabs[name];
807             if (tab.Merged) SplitTab(tab);
808
809             tab.AllowClose = true;
810             tab.Close();
811             tab = null;
812         }
813
814
815         public void RegisterContextAction(Type libomvType, String label, EventHandler handler)
816         {
817             instance.ContextActionManager.RegisterContextAction(libomvType, label, handler);
818         }
819
820         public void RegisterContextAction(ContextAction handler)
821         {
822             instance.ContextActionManager.RegisterContextAction(handler);
823         }
824
825         public void AddTab(RadegastTab tab)
826         {
827             ToolStripButton button = (ToolStripButton)tstTabs.Items.Add(tab.Label);
828             button.DisplayStyle = ToolStripItemDisplayStyle.ImageAndText;
829             button.Image = null;
830             button.AutoToolTip = false;
831             button.Tag = tab.Name;
832             button.Click += new EventHandler(TabButtonClick);
833             tab.Button = button;
834             tabs.Add(tab.Name, tab);
835
836             if (OnTabAdded != null)
837             {
838                 try { OnTabAdded(this, new TabEventArgs(tab)); }
839                 catch (Exception) { }
840             }
841         }
842
843         public RadegastTab AddTab(string name, string label, Control control)
844         {
845             // WORKAROUND: one should never add tab that alrady exists
846             // but under some weird conditions disconnect/connect
847             // fire in the wrong order
848             if (TabExists(name))
849             {
850                 Logger.Log("Force closing tab '" + name + "'", Helpers.LogLevel.Warning, client);
851                 ForceCloseTab(name);
852             }
853
854             control.Visible = false;
855             control.Dock = DockStyle.Fill;
856             toolStripContainer1.ContentPanel.Controls.Add(control);
857
858             ToolStripButton button = (ToolStripButton)tstTabs.Items.Add(label);
859             button.DisplayStyle = ToolStripItemDisplayStyle.ImageAndText;
860             button.Image = null;
861             button.AutoToolTip = false;
862             button.Tag = name.ToLower();
863             button.AllowDrop = true;
864             button.Click += new EventHandler(TabButtonClick);
865
866             RadegastTab tab = new RadegastTab(instance, button, control, name.ToLower(), label);
867             if (control is RadegastTabControl)
868                 ((RadegastTabControl)control).RadegastTab = tab;
869             tab.TabAttached += new EventHandler(tab_TabAttached);
870             tab.TabDetached += new EventHandler(tab_TabDetached);
871             tab.TabSelected += new EventHandler(tab_TabSelected);
872             tab.TabClosed += new EventHandler(tab_TabClosed);
873             tab.TabHidden += new EventHandler(tab_TabHidden);
874             tabs.Add(name.ToLower(), tab);
875
876             if (OnTabAdded != null)
877             {
878                 try { OnTabAdded(this, new TabEventArgs(tab)); }
879                 catch (Exception) { }
880             }
881
882             button.MouseDown += (msender, margs) =>
883             {
884                 if (margs.Button == MouseButtons.Middle)
885                 {
886                     if (tab.AllowClose)
887                     {
888                         tab.Close();
889                     }
890                     else if (tab.AllowHide)
891                     {
892                         tab.Hide();
893                     }
894                 }
895             };
896
897             return tab;
898         }
899
900         private void tab_TabAttached(object sender, EventArgs e)
901         {
902             RadegastTab tab = (RadegastTab)sender;
903             tab.Select();
904         }
905
906         private void tab_TabDetached(object sender, EventArgs e)
907         {
908             RadegastTab tab = (RadegastTab)sender;
909             frmDetachedTab form = (frmDetachedTab)tab.Owner;
910
911             form.ReattachStrip = tstTabs;
912             form.ReattachContainer = toolStripContainer1.ContentPanel;
913         }
914
915         private void tab_TabSelected(object sender, EventArgs e)
916         {
917             RadegastTab tab = (RadegastTab)sender;
918
919             if (selectedTab != null &&
920                 selectedTab != tab)
921             { selectedTab.Deselect(); }
922
923             selectedTab = tab;
924
925             tbtnCloseTab.Enabled = !tab.Merged && (tab.AllowClose || tab.AllowHide);
926
927             if (owner != null)
928             {
929                 owner.AcceptButton = tab.DefaultControlButton;
930             }
931
932             if (OnTabSelected != null)
933             {
934                 try { OnTabSelected(this, new TabEventArgs(selectedTab)); }
935                 catch (Exception) { }
936             }
937         }
938
939         void tab_TabHidden(object sender, EventArgs e)
940         {
941             RadegastTab tab = (RadegastTab)sender;
942
943             if (selectedTab != null && selectedTab == tab)
944             {
945                 tab.Deselect();
946                 SelectDefaultTab();
947             }
948         }
949
950         private void tab_TabClosed(object sender, EventArgs e)
951         {
952             RadegastTab tab = (RadegastTab)sender;
953
954             if (selectedTab != null && selectedTab == tab && tab.Name != "chat")
955             {
956                 tab.Deselect();
957                 SelectDefaultTab();
958             }
959
960             tabs.Remove(tab.Name);
961
962             if (OnTabRemoved != null)
963             {
964                 try { OnTabRemoved(this, new TabEventArgs(tab)); }
965                 catch (Exception) { }
966             }
967
968             tab = null;
969         }
970
971         private void TabButtonClick(object sender, EventArgs e)
972         {
973             ToolStripButton button = (ToolStripButton)sender;
974
975             RadegastTab tab = tabs[button.Tag.ToString()];
976             tab.Select();
977         }
978
979         public void RemoveTabEntry(RadegastTab tab)
980         {
981             if (tstTabs.Items.Contains(tab.Button))
982             {
983                 tstTabs.Items.Remove(tab.Button);
984             }
985
986             tab.Button.Dispose();
987             tabs.Remove(tab.Name);
988         }
989
990         public void RemoveTab(string name)
991         {
992             if (tabs.ContainsKey(name))
993             {
994                 tabs.Remove(name);
995             }
996         }
997
998         //Used for outside classes that have a reference to TabsConsole
999         public void SelectTab(string name)
1000         {
1001             if (TabExists(name.ToLower()))
1002                 tabs[name.ToLower()].Select();
1003         }
1004
1005         public bool TabExists(string name)
1006         {
1007             return tabs.ContainsKey(name.ToLower());
1008         }
1009
1010         public RadegastTab GetTab(string name)
1011         {
1012             if (TabExists(name.ToLower()))
1013                 return tabs[name.ToLower()];
1014             else
1015                 return null;
1016         }
1017
1018         public List<RadegastTab> GetOtherTabs()
1019         {
1020             List<RadegastTab> otherTabs = new List<RadegastTab>();
1021
1022             foreach (ToolStripItem item in tstTabs.Items)
1023             {
1024                 if (item.Tag == null) continue;
1025                 if ((ToolStripItem)item == selectedTab.Button) continue;
1026
1027                 RadegastTab tab = tabs[item.Tag.ToString()];
1028                 if (!tab.AllowMerge) continue;
1029                 if (tab.Merged) continue;
1030
1031                 otherTabs.Add(tab);
1032             }
1033
1034             return otherTabs;
1035         }
1036
1037         /// <summary>
1038         /// Activates the next tab
1039         /// </summary>
1040         public void SelectNextTab()
1041         {
1042             List<ToolStripItem> buttons = new List<ToolStripItem>();
1043
1044             foreach (ToolStripItem item in tstTabs.Items)
1045             {
1046                 if (item.Tag == null || !item.Visible) continue;
1047
1048                 buttons.Add(item);
1049             }
1050
1051             int current = 0;
1052
1053             for (int i = 0; i < buttons.Count; i++)
1054             {
1055                 if (buttons[i] == selectedTab.Button)
1056                 {
1057                     current = i;
1058                     break;
1059                 }
1060             }
1061
1062             current++;
1063
1064             if (current == buttons.Count)
1065                 current = 0;
1066
1067             SelectTab(tabs[buttons[current].Tag.ToString()].Name);
1068         }
1069
1070         /// <summary>
1071         /// Activates the previous tab
1072         /// </summary>
1073         public void SelectPreviousTab()
1074         {
1075             List<ToolStripItem> buttons = new List<ToolStripItem>();
1076
1077             foreach (ToolStripItem item in tstTabs.Items)
1078             {
1079                 if (item.Tag == null || !item.Visible) continue;
1080
1081                 buttons.Add(item);
1082             }
1083
1084             int current = 0;
1085
1086             for (int i = 0; i < buttons.Count; i++)
1087             {
1088                 if (buttons[i] == selectedTab.Button)
1089                 {
1090                     current = i;
1091                     break;
1092                 }
1093             }
1094
1095             current--;
1096
1097             if (current == -1)
1098                 current = buttons.Count - 1;
1099
1100             SelectTab(tabs[buttons[current].Tag.ToString()].Name);
1101         }
1102
1103
1104         public IMTabWindow AddIMTab(UUID target, UUID session, string targetName)
1105         {
1106             IMTabWindow imTab = new IMTabWindow(instance, target, session, targetName);
1107
1108             RadegastTab tab = AddTab(session.ToString(), "IM: " + targetName, imTab);
1109             imTab.SelectIMInput();
1110
1111             return imTab;
1112         }
1113
1114         public ConferenceIMTabWindow AddConferenceIMTab(UUID session, string name)
1115         {
1116             ConferenceIMTabWindow imTab = new ConferenceIMTabWindow(instance, session, name);
1117
1118             RadegastTab tab = AddTab(session.ToString(), name, imTab);
1119             imTab.SelectIMInput();
1120
1121             return imTab;
1122         }
1123
1124         public GroupIMTabWindow AddGroupIMTab(UUID session, string name)
1125         {
1126             GroupIMTabWindow imTab = new GroupIMTabWindow(instance, session, name);
1127
1128             RadegastTab tab = AddTab(session.ToString(), name, imTab);
1129             imTab.SelectIMInput();
1130
1131             return imTab;
1132         }
1133
1134         public OutfitTextures AddOTTab(Avatar avatar)
1135         {
1136             OutfitTextures otTab = new OutfitTextures(instance, avatar);
1137
1138             RadegastTab tab = AddTab("OT: " + avatar.Name, "OT: " + avatar.Name, otTab);
1139             otTab.GetTextures();
1140             return otTab;
1141         }
1142
1143         public MasterTab AddMSTab(Avatar avatar)
1144         {
1145             MasterTab msTab = new MasterTab(instance, avatar);
1146
1147             RadegastTab tab = AddTab("MS: " + avatar.Name, "MS: " + avatar.Name, msTab);
1148             return msTab;
1149         }
1150
1151         public AnimTab AddAnimTab(Avatar avatar)
1152         {
1153             AnimTab animTab = new AnimTab(instance, avatar);
1154
1155             RadegastTab tab = AddTab("Anim: " + avatar.Name, "Anim: " + avatar.Name, animTab);
1156             return animTab;
1157         }
1158
1159         private void tbtnTabOptions_Click(object sender, EventArgs e)
1160         {
1161             tmnuMergeWith.Enabled = selectedTab.AllowMerge;
1162             tmnuDetachTab.Enabled = selectedTab.AllowDetach;
1163
1164             tmnuMergeWith.DropDown.Items.Clear();
1165
1166             if (!selectedTab.AllowMerge) return;
1167             if (!selectedTab.Merged)
1168             {
1169                 tmnuMergeWith.Text = "Merge With";
1170
1171                 List<RadegastTab> otherTabs = GetOtherTabs();
1172
1173                 tmnuMergeWith.Enabled = (otherTabs.Count > 0);
1174                 if (!tmnuMergeWith.Enabled) return;
1175
1176                 foreach (RadegastTab tab in otherTabs)
1177                 {
1178                     ToolStripItem item = tmnuMergeWith.DropDown.Items.Add(tab.Label);
1179                     item.Tag = tab.Name;
1180                     item.Click += new EventHandler(MergeItemClick);
1181                 }
1182             }
1183             else
1184             {
1185                 tmnuMergeWith.Text = "Split";
1186                 tmnuMergeWith.Click += new EventHandler(SplitClick);
1187             }
1188         }
1189
1190         private void MergeItemClick(object sender, EventArgs e)
1191         {
1192             ToolStripItem item = (ToolStripItem)sender;
1193             RadegastTab tab = tabs[item.Tag.ToString()];
1194
1195             selectedTab.MergeWith(tab);
1196
1197             SplitContainer container = (SplitContainer)selectedTab.Control;
1198             toolStripContainer1.ContentPanel.Controls.Add(container);
1199
1200             selectedTab.Select();
1201             RemoveTabEntry(tab);
1202
1203             tabs.Add(tab.Name, selectedTab);
1204         }
1205
1206         private void SplitClick(object sender, EventArgs e)
1207         {
1208             SplitTab(selectedTab);
1209             selectedTab.Select();
1210         }
1211
1212         public void SplitTab(RadegastTab tab)
1213         {
1214             RadegastTab otherTab = tab.Split();
1215             if (otherTab == null) return;
1216
1217             toolStripContainer1.ContentPanel.Controls.Add(tab.Control);
1218             toolStripContainer1.ContentPanel.Controls.Add(otherTab.Control);
1219
1220             tabs.Remove(otherTab.Name);
1221             AddTab(otherTab);
1222         }
1223
1224         private void tmnuDetachTab_Click(object sender, EventArgs e)
1225         {
1226             if (!selectedTab.AllowDetach) return;
1227             RadegastTab tab = selectedTab;
1228             SelectDefaultTab();
1229             tab.Detach(instance);
1230         }
1231
1232         private void tbtnCloseTab_Click(object sender, EventArgs e)
1233         {
1234             RadegastTab tab = selectedTab;
1235             if (tab.Merged)
1236                 return;
1237             else if (tab.AllowClose)
1238                 tab.Close();
1239             else if (tab.AllowHide)
1240                 tab.Hide();
1241         }
1242
1243         private void TabsConsole_Load(object sender, EventArgs e)
1244         {
1245             owner = this.FindForm();
1246         }
1247
1248         private void ctxTabs_Opening(object sender, CancelEventArgs e)
1249         {
1250             e.Cancel = false;
1251
1252             Point pt = this.PointToClient(Cursor.Position);
1253             ToolStripItem stripItem = tstTabs.GetItemAt(pt);
1254
1255             if (stripItem == null)
1256             {
1257                 e.Cancel = true;
1258             }
1259             else
1260             {
1261                 tabs[stripItem.Tag.ToString()].Select();
1262
1263                 ctxBtnClose.Enabled = !selectedTab.Merged && (selectedTab.AllowClose || selectedTab.AllowHide);
1264                 ctxBtnDetach.Enabled = selectedTab.AllowDetach;
1265                 ctxBtnMerge.Enabled = selectedTab.AllowMerge;
1266                 ctxBtnMerge.DropDown.Items.Clear();
1267
1268                 if (!ctxBtnClose.Enabled && !ctxBtnDetach.Enabled && !ctxBtnMerge.Enabled)
1269                 {
1270                     e.Cancel = true;
1271                     return;
1272                 }
1273
1274                 if (!selectedTab.AllowMerge) return;
1275                 if (!selectedTab.Merged)
1276                 {
1277                     ctxBtnMerge.Text = "Merge With";
1278
1279                     List<RadegastTab> otherTabs = GetOtherTabs();
1280
1281                     ctxBtnMerge.Enabled = (otherTabs.Count > 0);
1282                     if (!ctxBtnMerge.Enabled) return;
1283
1284                     foreach (RadegastTab tab in otherTabs)
1285                     {
1286                         ToolStripItem item = ctxBtnMerge.DropDown.Items.Add(tab.Label);
1287                         item.Tag = tab.Name;
1288                         item.Click += new EventHandler(MergeItemClick);
1289                     }
1290                 }
1291                 else
1292                 {
1293                     ctxBtnMerge.Text = "Split";
1294                     ctxBtnMerge.Click += new EventHandler(SplitClick);
1295                 }
1296
1297             }
1298         }
1299
1300     }
1301
1302     /// <summary>
1303     /// Arguments for tab events
1304     /// </summary>
1305     public class TabEventArgs : EventArgs
1306     {
1307         /// <summary>
1308         /// Tab that was manipulated in the event
1309         /// </summary>
1310         public RadegastTab Tab;
1311
1312         public TabEventArgs()
1313             : base()
1314         {
1315         }
1316
1317         public TabEventArgs(RadegastTab tab)
1318             : base()
1319         {
1320             Tab = tab;
1321         }
1322     }
1323
1324     /// <summary>
1325     /// Argument for chat notification events
1326     /// </summary>
1327     public class ChatNotificationEventArgs : EventArgs
1328     {
1329         public string Message;
1330         public ChatBufferTextStyle Style;
1331
1332         public ChatNotificationEventArgs(string message, ChatBufferTextStyle style)
1333         {
1334             Message = message;
1335             Style = style;
1336         }
1337     }
1338
1339     /// <summary>
1340     /// Element of list of nearby avatars
1341     /// </summary>
1342     public class NearbyAvatar
1343     {
1344         public UUID ID { get; set; }
1345         public string Name { get; set; }
1346     }
1347
1348 }