OSDN Git Service

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