OSDN Git Service

RAD-500 RLV support for @acceptpermission and @denypermission
[radegast/radegast.git] / Radegast / GUI / Consoles / TabsConsole.cs
index 1476ec4..0a4ddeb 100644 (file)
@@ -1,6 +1,6 @@
 // 
 // Radegast Metaverse Client
-// Copyright (c) 2009-2010, Radegast Development Team
+// Copyright (c) 2009-2014, Radegast Development Team
 // All rights reserved.
 // 
 // Redistribution and use in source and binary forms, with or without
@@ -108,6 +108,7 @@ namespace Radegast
         private GridClient client { get { return instance.Client; } }
         private RadegastNetcom netcom { get { return instance.Netcom; } }
         private ChatTextManager mainChatManger;
+        public ChatTextManager MainChatManger { get { return mainChatManger; } }
 
         private Dictionary<string, RadegastTab> tabs = new Dictionary<string, RadegastTab>();
         public Dictionary<string, RadegastTab> Tabs { get { return tabs; } }
@@ -151,6 +152,10 @@ namespace Radegast
             client.Self.ScriptQuestion += new EventHandler<ScriptQuestionEventArgs>(Self_ScriptQuestion);
             client.Self.ScriptDialog += new EventHandler<ScriptDialogEventArgs>(Self_ScriptDialog);
             client.Self.LoadURL += new EventHandler<LoadUrlEventArgs>(Self_LoadURL);
+            client.Self.SetDisplayNameReply += new EventHandler<SetDisplayNameReplyEventArgs>(Self_SetDisplayNameReply);
+            client.Avatars.DisplayNameUpdate += new EventHandler<DisplayNameUpdateEventArgs>(Avatars_DisplayNameUpdate);
+            client.Network.EventQueueRunning += new EventHandler<EventQueueRunningEventArgs>(Network_EventQueueRunning);
+            client.Network.RegisterCallback(OpenMetaverse.Packets.PacketType.ScriptTeleportRequest, ScriptTeleportRequestHandler);
         }
 
         private void UnregisterClientEvents(GridClient client)
@@ -158,6 +163,10 @@ namespace Radegast
             client.Self.ScriptQuestion -= new EventHandler<ScriptQuestionEventArgs>(Self_ScriptQuestion);
             client.Self.ScriptDialog -= new EventHandler<ScriptDialogEventArgs>(Self_ScriptDialog);
             client.Self.LoadURL -= new EventHandler<LoadUrlEventArgs>(Self_LoadURL);
+            client.Self.SetDisplayNameReply -= new EventHandler<SetDisplayNameReplyEventArgs>(Self_SetDisplayNameReply);
+            client.Avatars.DisplayNameUpdate -= new EventHandler<DisplayNameUpdateEventArgs>(Avatars_DisplayNameUpdate);
+            client.Network.EventQueueRunning -= new EventHandler<EventQueueRunningEventArgs>(Network_EventQueueRunning);
+            client.Network.UnregisterCallback(OpenMetaverse.Packets.PacketType.ScriptTeleportRequest, ScriptTeleportRequestHandler);
         }
 
         void instance_ClientChanged(object sender, ClientChangedEventArgs e)
@@ -177,7 +186,6 @@ namespace Radegast
             netcom.ClientLoginStatus += new EventHandler<LoginProgressEventArgs>(netcom_ClientLoginStatus);
             netcom.ClientLoggedOut += new EventHandler(netcom_ClientLoggedOut);
             netcom.ClientDisconnected += new EventHandler<DisconnectedEventArgs>(netcom_ClientDisconnected);
-            netcom.ChatReceived += new EventHandler<ChatEventArgs>(netcom_ChatReceived);
             netcom.ChatSent += new EventHandler<ChatSentEventArgs>(netcom_ChatSent);
             netcom.AlertMessageReceived += new EventHandler<AlertMessageEventArgs>(netcom_AlertMessageReceived);
             netcom.InstantMessageReceived += new EventHandler<InstantMessageEventArgs>(netcom_InstantMessageReceived);
@@ -188,20 +196,85 @@ namespace Radegast
             netcom.ClientLoginStatus -= new EventHandler<LoginProgressEventArgs>(netcom_ClientLoginStatus);
             netcom.ClientLoggedOut -= new EventHandler(netcom_ClientLoggedOut);
             netcom.ClientDisconnected -= new EventHandler<DisconnectedEventArgs>(netcom_ClientDisconnected);
-            netcom.ChatReceived -= new EventHandler<ChatEventArgs>(netcom_ChatReceived);
             netcom.ChatSent -= new EventHandler<ChatSentEventArgs>(netcom_ChatSent);
             netcom.AlertMessageReceived -= new EventHandler<AlertMessageEventArgs>(netcom_AlertMessageReceived);
             netcom.InstantMessageReceived -= new EventHandler<InstantMessageEventArgs>(netcom_InstantMessageReceived);
         }
 
+        void ScriptTeleportRequestHandler(object sender, PacketReceivedEventArgs e)
+        {
+            if (InvokeRequired)
+            {
+                if (IsHandleCreated || !instance.MonoRuntime)
+                    BeginInvoke(new MethodInvoker(() => ScriptTeleportRequestHandler(sender, e)));
+                return;
+            }
+
+            var msg = (OpenMetaverse.Packets.ScriptTeleportRequestPacket)e.Packet;
+
+            if (TabExists("map"))
+            {
+                Tabs["map"].Select();
+                ((MapConsole)Tabs["map"].Control).CenterOnGlobalPos(
+                    (float)(client.Self.GlobalPosition.X - client.Self.SimPosition.X) + msg.Data.SimPosition.X,
+                    (float)(client.Self.GlobalPosition.Y - client.Self.SimPosition.Y) + msg.Data.SimPosition.Y,
+                    msg.Data.SimPosition.Z);
+            }
+        }
+
+        void Network_EventQueueRunning(object sender, EventQueueRunningEventArgs e)
+        {
+            if (InvokeRequired)
+            {
+                BeginInvoke(new MethodInvoker(() => Network_EventQueueRunning(sender, e)));
+                return;
+            }
+
+            if (TabExists("friends")) return;
+            if (e.Simulator == client.Network.CurrentSim)
+            {
+                client.Self.UpdateAgentLanguage("en", true);
+                InitializeOnlineTabs();
+            }
+        }
+
         void Self_ScriptDialog(object sender, ScriptDialogEventArgs e)
         {
+            if (instance.MainForm.InvokeRequired)
+            {
+                instance.MainForm.BeginInvoke(new MethodInvoker(() => Self_ScriptDialog(sender, e)));
+                return;
+            }
+
+            // Is this object muted
+            if (null != client.Self.MuteList.Find(m => (m.Type == MuteType.Object && m.ID == e.ObjectID) // muted object by id
+                || (m.Type == MuteType.ByName && m.Name == e.ObjectName) // object muted by name
+                )) return;
+
             instance.MainForm.AddNotification(new ntfScriptDialog(instance, e.Message, e.ObjectName, e.ImageID, e.ObjectID, e.FirstName, e.LastName, e.Channel, e.ButtonLabels));
         }
 
         void Self_ScriptQuestion(object sender, ScriptQuestionEventArgs e)
         {
-            instance.MainForm.AddNotification(new ntfPermissions(instance, e.Simulator, e.TaskID, e.ItemID, e.ObjectName, e.ObjectOwnerName, e.Questions));
+            // Is this object muted
+            if (null != client.Self.MuteList.Find(m => (m.Type == MuteType.Object && m.ID == e.TaskID) // muted object by id
+                || (m.Type == MuteType.ByName && m.Name == e.ObjectName) // object muted by name
+                )) return;
+
+            if (instance.GlobalSettings["on_script_question"] == "Auto Decline"
+                || instance.RLV.RestictionActive("denypermission"))
+            {
+                instance.Client.Self.ScriptQuestionReply(e.Simulator, e.ItemID, e.TaskID, 0);
+            }
+            else if (instance.GlobalSettings["on_script_question"] == "Auto Accept"
+                || instance.RLV.RestictionActive("acceptpermission"))
+            {
+                instance.Client.Self.ScriptQuestionReply(e.Simulator, e.ItemID, e.TaskID, e.Questions);
+            }
+            else
+            {
+                instance.MainForm.AddNotification(new ntfPermissions(instance, e.Simulator, e.TaskID, e.ItemID, e.ObjectName, e.ObjectOwnerName, e.Questions));
+            }
         }
 
         private void netcom_ClientLoginStatus(object sender, LoginProgressEventArgs e)
@@ -215,8 +288,6 @@ namespace Radegast
                 DisplayNotificationInChat("Logged in as " + netcom.LoginOptions.FullName + ".", ChatBufferTextStyle.StatusDarkBlue);
                 DisplayNotificationInChat("Login reply: " + e.Message, ChatBufferTextStyle.StatusDarkBlue);
 
-                InitializeOnlineTabs();
-
                 if (tabs.ContainsKey("login"))
                 {
                     if (selectedTab.Name == "login")
@@ -246,30 +317,61 @@ namespace Radegast
             DisplayNotificationInChat("Disconnected: " + e.Message, ChatBufferTextStyle.Error);
         }
 
-        private void netcom_AlertMessageReceived(object sender, AlertMessageEventArgs e)
+        void Avatars_DisplayNameUpdate(object sender, DisplayNameUpdateEventArgs e)
         {
-            tabs["chat"].Highlight();
+            DisplayNotificationInChat(string.Format("({0}) is now known as {1}", e.DisplayName.UserName, e.DisplayName.DisplayName));
         }
 
-        private void netcom_ChatSent(object sender, ChatSentEventArgs e)
+        void Self_SetDisplayNameReply(object sender, SetDisplayNameReplyEventArgs e)
         {
-            tabs["chat"].Highlight();
+            if (e.Status == 200)
+            {
+                DisplayNotificationInChat("You are now knows as " + e.DisplayName.DisplayName);
+            }
+            else
+            {
+                DisplayNotificationInChat("Failed to set a new display name: " + e.Reason, ChatBufferTextStyle.Error);
+            }
         }
 
-        private void netcom_ChatReceived(object sender, ChatEventArgs e)
+        private void netcom_AlertMessageReceived(object sender, AlertMessageEventArgs e)
         {
-            if (string.IsNullOrEmpty(e.Message)) return;
+            tabs["chat"].Highlight();
+        }
 
+        private void netcom_ChatSent(object sender, ChatSentEventArgs e)
+        {
             tabs["chat"].Highlight();
         }
 
         void Self_LoadURL(object sender, LoadUrlEventArgs e)
         {
+            // Is the object or the owner muted?
+            if (null != client.Self.MuteList.Find(m => (m.Type == MuteType.Object && m.ID == e.ObjectID) // muted object by id 
+                || (m.Type == MuteType.ByName && m.Name == e.ObjectName) // object muted by name
+                || (m.Type == MuteType.Resident && m.ID == e.OwnerID) // object's owner muted
+                )) return;
+
             instance.MainForm.AddNotification(new ntfLoadURL(instance, e));
         }
 
         private void netcom_InstantMessageReceived(object sender, InstantMessageEventArgs e)
         {
+            // Messaage from someone we muted?
+            if (null != client.Self.MuteList.Find(me => me.Type == MuteType.Resident && me.ID == e.IM.FromAgentID)) return;
+
+            try
+            {
+                if (instance.State.LSLHelper.ProcessIM(e))
+                {
+                    return;
+                }
+            }
+            catch (Exception ex)
+            {
+                Logger.Log("Failed executing automation action: " + ex.ToString(), Helpers.LogLevel.Warning);
+            }
+
             switch (e.IM.Dialog)
             {
                 case InstantMessageDialog.SessionSend:
@@ -288,6 +390,10 @@ namespace Radegast
                     {
                         HandleIMFromObject(e);
                     }
+                    else if (e.IM.FromAgentID == UUID.Zero)
+                    {
+                        instance.MainForm.AddNotification(new ntfGeneric(instance, e.IM.Message));
+                    }
                     else if (e.IM.GroupIM || instance.Groups.ContainsKey(e.IM.IMSessionID))
                     {
                         HandleGroupIM(e);
@@ -296,6 +402,12 @@ namespace Radegast
                     { // conference
                         HandleConferenceIM(e);
                     }
+                    else if (e.IM.IMSessionID == UUID.Zero)
+                    {
+                        String msg = string.Format("Message from {0}: {1}", instance.Names.Get(e.IM.FromAgentID, e.IM.FromAgentName), e.IM.Message);
+                        instance.MainForm.AddNotification(new ntfGeneric(instance, msg));
+                        DisplayNotificationInChat(msg);
+                    }
                     else
                     {
                         HandleIM(e);
@@ -332,7 +444,7 @@ namespace Radegast
                     if (instance.RLV.AutoAcceptTP(e.IM.FromAgentID))
                     {
                         DisplayNotificationInChat("Auto accepting teleprot from " + e.IM.FromAgentName);
-                        instance.Client.Self.TeleportLureRespond(e.IM.FromAgentID, true);
+                        instance.Client.Self.TeleportLureRespond(e.IM.FromAgentID, e.IM.IMSessionID, true);
                     }
                     else
                     {
@@ -340,12 +452,23 @@ namespace Radegast
                     }
                     break;
 
+                case InstantMessageDialog.RequestLure:
+                    instance.MainForm.AddNotification(new ntfRequestLure(instance, e.IM));
+                    break;
+
                 case InstantMessageDialog.GroupInvitation:
                     instance.MainForm.AddNotification(new ntfGroupInvitation(instance, e.IM));
                     break;
 
                 case InstantMessageDialog.FriendshipOffered:
-                    instance.MainForm.AddNotification(new ntfFriendshipOffer(instance, e.IM));
+                    if (e.IM.FromAgentName == "Second Life")
+                    {
+                        HandleIMFromObject(e);
+                    }
+                    else
+                    {
+                        instance.MainForm.AddNotification(new ntfFriendshipOffer(instance, e.IM));
+                    }
                     break;
 
                 case InstantMessageDialog.InventoryAccepted:
@@ -357,12 +480,39 @@ namespace Radegast
                     break;
 
                 case InstantMessageDialog.GroupNotice:
+                    // Is this group muted?
+                    if (null != client.Self.MuteList.Find(me => me.Type == MuteType.Group && me.ID == e.IM.FromAgentID)) break;
+
                     instance.MainForm.AddNotification(new ntfGroupNotice(instance, e.IM));
                     break;
 
                 case InstantMessageDialog.InventoryOffered:
+                    var ion = new ntfInventoryOffer(instance, e.IM);
+                    instance.MainForm.AddNotification(ion);
+                    if (instance.GlobalSettings["inv_auto_accept_mode"].AsInteger() == 1)
+                    {
+                        ion.btnAccept.PerformClick();
+                    }
+                    else if (instance.GlobalSettings["inv_auto_accept_mode"].AsInteger() == 2)
+                    {
+                        ion.btnDiscard.PerformClick();
+                    }
+                    break;
+
                 case InstantMessageDialog.TaskInventoryOffered:
-                    instance.MainForm.AddNotification(new ntfInventoryOffer(instance, e.IM));
+                    // Is the object muted by name?
+                    if (null != client.Self.MuteList.Find(me => me.Type == MuteType.ByName && me.Name == e.IM.FromAgentName)) break;
+
+                    var iont = new ntfInventoryOffer(instance, e.IM);
+                    instance.MainForm.AddNotification(iont);
+                    if (instance.GlobalSettings["inv_auto_accept_mode"].AsInteger() == 1)
+                    {
+                        iont.btnAccept.PerformClick();
+                    }
+                    else if (instance.GlobalSettings["inv_auto_accept_mode"].AsInteger() == 2)
+                    {
+                        iont.btnDiscard.PerformClick();
+                    }
                     break;
             }
         }
@@ -372,7 +522,7 @@ namespace Radegast
         /// </summary>
         public void SelectDefaultTab()
         {
-            if (TabExists("chat"))
+            if (IsHandleCreated && TabExists("chat"))
                 tabs["chat"].Select();
         }
 
@@ -403,11 +553,10 @@ namespace Radegast
         /// <param name="highlightChatTab">Highligt (and flash in taskbar) chat tab if not selected</param>
         public void DisplayNotificationInChat(string msg, ChatBufferTextStyle style, bool highlightChatTab)
         {
-            if (!instance.MainForm.IsHandleCreated) return;
-
             if (InvokeRequired)
             {
-                BeginInvoke(new MethodInvoker(() => DisplayNotificationInChat(msg, style, highlightChatTab)));
+                if (!instance.MonoRuntime || IsHandleCreated)
+                    BeginInvoke(new MethodInvoker(() => DisplayNotificationInChat(msg, style, highlightChatTab)));
                 return;
             }
 
@@ -415,6 +564,8 @@ namespace Radegast
             {
                 ChatBufferItem line = new ChatBufferItem(
                     DateTime.Now,
+                    string.Empty,
+                    UUID.Zero,
                     msg,
                     style
                 );
@@ -439,20 +590,80 @@ namespace Radegast
 
         private void HandleIMFromObject(InstantMessageEventArgs e)
         {
+            // Is the object or the owner muted?
+            if (null != client.Self.MuteList.Find(m => (m.Type == MuteType.Object && m.ID == e.IM.IMSessionID) // muted object by id 
+                || (m.Type == MuteType.ByName && m.Name == e.IM.FromAgentName) // object muted by name
+                || (m.Type == MuteType.Resident && m.ID == e.IM.FromAgentID) // object's owner muted
+                )) return;
+
             DisplayNotificationInChat(e.IM.FromAgentName + ": " + e.IM.Message);
         }
 
-        private void HandleIM(InstantMessageEventArgs e)
+        public static Control FindFocusedControl(Control control)
         {
-            if (TabExists(e.IM.IMSessionID.ToString()))
+            var container = control as ContainerControl;
+            while (container != null)
             {
-                RadegastTab tab = tabs[e.IM.IMSessionID.ToString()];
-                if (!tab.Selected) tab.Highlight();
-                return;
+                control = container.ActiveControl;
+                container = control as ContainerControl;
             }
+            return control;
+        }
 
-            IMTabWindow imTab = AddIMTab(e);
-            tabs[e.IM.IMSessionID.ToString()].Highlight();
+        /// <summary>
+        /// Creates new IM tab if needed
+        /// </summary>
+        /// <param name="agentID">IM session with agentID</param>
+        /// <param name="label">Tab label</param>
+        /// <param name="makeActive">Should tab be selected and focused</param>
+        /// <returns>True if there was an existing IM tab, false if it was created</returns>
+        public bool ShowIMTab(UUID agentID, string label, bool makeActive)
+        {
+            if (instance.TabConsole.TabExists((client.Self.AgentID ^ agentID).ToString()))
+            {
+                if (makeActive)
+                {
+                    instance.TabConsole.SelectTab((client.Self.AgentID ^ agentID).ToString());
+                }
+                return false;
+            }
+
+            if (makeActive)
+            {
+                instance.MediaManager.PlayUISound(UISounds.IMWindow);
+            }
+            else
+            {
+                instance.MediaManager.PlayUISound(UISounds.IM);
+            }
+
+            Control active = FindFocusedControl(instance.MainForm);
+
+            instance.TabConsole.AddIMTab(agentID, client.Self.AgentID ^ agentID, label);
+
+            if (makeActive)
+            {
+                instance.TabConsole.SelectTab((client.Self.AgentID ^ agentID).ToString());
+            }
+            else if (active != null)
+            {
+                active.Focus();
+            }
+
+            return true;
+        }
+
+        private void HandleIM(InstantMessageEventArgs e)
+        {
+            bool isNew = ShowIMTab(e.IM.FromAgentID, e.IM.FromAgentName, false);
+            if (!TabExists(e.IM.IMSessionID.ToString())) return; // this should now exist. sanity check anyway
+            RadegastTab tab = tabs[e.IM.IMSessionID.ToString()];
+            tab.Highlight();
+
+            if (isNew)
+            {
+                ((IMTabWindow)tab.Control).TextManager.ProcessIM(e);
+            }
         }
 
         private void HandleConferenceIM(InstantMessageEventArgs e)
@@ -460,29 +671,57 @@ namespace Radegast
             if (TabExists(e.IM.IMSessionID.ToString()))
             {
                 RadegastTab tab = tabs[e.IM.IMSessionID.ToString()];
-                if (!tab.Selected) tab.Highlight();
+                tab.Highlight();
                 return;
             }
 
-            ConferenceIMTabWindow imTab = AddConferenceIMTab(e);
+            instance.MediaManager.PlayUISound(UISounds.IM);
+
+            Control active = FindFocusedControl(instance.MainForm);
+
+            ConferenceIMTabWindow imTab = AddConferenceIMTab(e.IM.IMSessionID, Utils.BytesToString(e.IM.BinaryBucket));
             tabs[e.IM.IMSessionID.ToString()].Highlight();
+            imTab.TextManager.ProcessIM(e);
+
+            if (active != null)
+            {
+                active.Focus();
+            }
         }
 
         private void HandleGroupIM(InstantMessageEventArgs e)
         {
+            // Ignore group IM from a muted group
+            if (null != client.Self.MuteList.Find(me => me.Type == MuteType.Group && (me.ID == e.IM.IMSessionID || me.ID == e.IM.FromAgentID))) return;
+
             if (TabExists(e.IM.IMSessionID.ToString()))
             {
                 RadegastTab tab = tabs[e.IM.IMSessionID.ToString()];
-                if (!tab.Selected) tab.Highlight();
+                tab.Highlight();
                 return;
             }
 
-            GroupIMTabWindow imTab = AddGroupIMTab(e);
+            instance.MediaManager.PlayUISound(UISounds.IM);
+
+            Control active = FindFocusedControl(instance.MainForm);
+
+            GroupIMTabWindow imTab = AddGroupIMTab(e.IM.IMSessionID, Utils.BytesToString(e.IM.BinaryBucket));
+            imTab.TextManager.ProcessIM(e);
             tabs[e.IM.IMSessionID.ToString()].Highlight();
+
+            if (active != null)
+            {
+                active.Focus();
+            }
         }
 
-        private void InitializeMainTab()
+        public void InitializeMainTab()
         {
+            if (TabExists("login"))
+            {
+                ForceCloseTab("login");
+            }
+
             LoginConsole loginConsole = new LoginConsole(instance);
 
             RadegastTab tab = AddTab("login", "Login", loginConsole);
@@ -623,6 +862,7 @@ namespace Radegast
             button.Image = null;
             button.AutoToolTip = false;
             button.Tag = name.ToLower();
+            button.AllowDrop = true;
             button.Click += new EventHandler(TabButtonClick);
 
             RadegastTab tab = new RadegastTab(instance, button, control, name.ToLower(), label);
@@ -641,6 +881,21 @@ namespace Radegast
                 catch (Exception) { }
             }
 
+            button.MouseDown += (msender, margs) =>
+            {
+                if (margs.Button == MouseButtons.Middle)
+                {
+                    if (tab.AllowClose)
+                    {
+                        tab.Close();
+                    }
+                    else if (tab.AllowHide)
+                    {
+                        tab.Hide();
+                    }
+                }
+            };
+
             return tab;
         }
 
@@ -669,8 +924,12 @@ namespace Radegast
 
             selectedTab = tab;
 
-            tbtnCloseTab.Enabled = tab.AllowClose || tab.AllowHide;
-            owner.AcceptButton = tab.DefaultControlButton;
+            tbtnCloseTab.Enabled = !tab.Merged && (tab.AllowClose || tab.AllowHide);
+
+            if (owner != null)
+            {
+                owner.AcceptButton = tab.DefaultControlButton;
+            }
 
             if (OnTabSelected != null)
             {
@@ -850,7 +1109,6 @@ namespace Radegast
 
             RadegastTab tab = AddTab(session.ToString(), "IM: " + targetName, imTab);
             imTab.SelectIMInput();
-            tab.Highlight();
 
             return imTab;
         }
@@ -865,14 +1123,6 @@ namespace Radegast
             return imTab;
         }
 
-
-        public ConferenceIMTabWindow AddConferenceIMTab(InstantMessageEventArgs e)
-        {
-            ConferenceIMTabWindow imTab = AddConferenceIMTab(e.IM.IMSessionID, Utils.BytesToString(e.IM.BinaryBucket));
-            imTab.TextManager.ProcessIM(e);
-            return imTab;
-        }
-
         public GroupIMTabWindow AddGroupIMTab(UUID session, string name)
         {
             GroupIMTabWindow imTab = new GroupIMTabWindow(instance, session, name);
@@ -883,20 +1133,6 @@ namespace Radegast
             return imTab;
         }
 
-        public GroupIMTabWindow AddGroupIMTab(InstantMessageEventArgs e)
-        {
-            GroupIMTabWindow imTab = AddGroupIMTab(e.IM.IMSessionID, Utils.BytesToString(e.IM.BinaryBucket));
-            imTab.TextManager.ProcessIM(e);
-            return imTab;
-        }
-
-        public IMTabWindow AddIMTab(InstantMessageEventArgs e)
-        {
-            IMTabWindow imTab = AddIMTab(e.IM.FromAgentID, e.IM.IMSessionID, e.IM.FromAgentName);
-            imTab.TextManager.ProcessIM(e);
-            return imTab;
-        }
-
         public OutfitTextures AddOTTab(Avatar avatar)
         {
             OutfitTextures otTab = new OutfitTextures(instance, avatar);
@@ -914,14 +1150,6 @@ namespace Radegast
             return msTab;
         }
 
-        public AttachmentTab AddATTab(Avatar avatar)
-        {
-            AttachmentTab atTab = new AttachmentTab(instance, avatar);
-
-            RadegastTab tab = AddTab("AT: " + avatar.Name, "AT: " + avatar.Name, atTab);
-            return atTab;
-        }
-
         public AnimTab AddAnimTab(Avatar avatar)
         {
             AnimTab animTab = new AnimTab(instance, avatar);
@@ -1006,7 +1234,9 @@ namespace Radegast
         private void tbtnCloseTab_Click(object sender, EventArgs e)
         {
             RadegastTab tab = selectedTab;
-            if (tab.AllowClose)
+            if (tab.Merged)
+                return;
+            else if (tab.AllowClose)
                 tab.Close();
             else if (tab.AllowHide)
                 tab.Hide();
@@ -1019,6 +1249,8 @@ namespace Radegast
 
         private void ctxTabs_Opening(object sender, CancelEventArgs e)
         {
+            e.Cancel = false;
+
             Point pt = this.PointToClient(Cursor.Position);
             ToolStripItem stripItem = tstTabs.GetItemAt(pt);
 
@@ -1030,7 +1262,7 @@ namespace Radegast
             {
                 tabs[stripItem.Tag.ToString()].Select();
 
-                ctxBtnClose.Enabled = selectedTab.AllowClose || selectedTab.AllowHide;
+                ctxBtnClose.Enabled = !selectedTab.Merged && (selectedTab.AllowClose || selectedTab.AllowHide);
                 ctxBtnDetach.Enabled = selectedTab.AllowDetach;
                 ctxBtnMerge.Enabled = selectedTab.AllowMerge;
                 ctxBtnMerge.DropDown.Items.Clear();