//
// Radegast Metaverse Client
-// Copyright (c) 2009, Radegast Development Team
+// Copyright (c) 2009-2014, Radegast Development Team
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
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; } }
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)
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)
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);
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)
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")
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:
{
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);
{ // 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);
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
{
}
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:
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;
}
}
/// </summary>
public void SelectDefaultTab()
{
- if (TabExists("chat"))
+ if (IsHandleCreated && TabExists("chat"))
tabs["chat"].Select();
}
/// <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;
}
{
ChatBufferItem line = new ChatBufferItem(
DateTime.Now,
+ string.Empty,
+ UUID.Zero,
msg,
style
);
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)
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);
tab.AllowDetach = true;
tab.Visible = false;
- tab = AddTab("inventory", "Inventory", new InventoryConsole(instance));
+ // Ugly workaround for a mono bug
+ InventoryConsole inv = new InventoryConsole(instance);
+ if (instance.MonoRuntime) inv.invTree.Scrollable = false;
+ tab = AddTab("inventory", "Inventory", inv);
+ if (instance.MonoRuntime) inv.invTree.Scrollable = true;
tab.AllowClose = false;
tab.AllowDetach = true;
tab.Visible = false;
tab.AllowDetach = true;
tab.Visible = false;
- if (!TabExists("map"))
- {
- tab = AddTab("map", "Map", new MapConsole(instance));
- tab.AllowClose = false;
- tab.AllowDetach = true;
- tab.Visible = false;
- }
+ tab = AddTab("map", "Map", new MapConsole(instance));
+ tab.AllowClose = false;
+ tab.AllowDetach = true;
+ tab.Visible = false;
tab = AddTab("voice", "Voice", new VoiceConsole(instance));
tab.AllowClose = false;
lock (tabs)
{
ForceCloseTab("voice");
-
- // Mono crashes if we try to open map for the second time
- if (!instance.MonoRuntime)
- ForceCloseTab("map");
- else if (TabExists("map"))
- tabs["map"].Hide();
-
+ ForceCloseTab("map");
ForceCloseTab("search");
ForceCloseTab("inventory");
ForceCloseTab("groups");
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);
catch (Exception) { }
}
+ button.MouseDown += (msender, margs) =>
+ {
+ if (margs.Button == MouseButtons.Middle)
+ {
+ if (tab.AllowClose)
+ {
+ tab.Close();
+ }
+ else if (tab.AllowHide)
+ {
+ tab.Hide();
+ }
+ }
+ };
+
return tab;
}
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)
{
RadegastTab tab = AddTab(session.ToString(), "IM: " + targetName, imTab);
imTab.SelectIMInput();
- tab.Highlight();
return imTab;
}
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);
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);
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);
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();
private void ctxTabs_Opening(object sender, CancelEventArgs e)
{
+ e.Cancel = false;
+
Point pt = this.PointToClient(Cursor.Position);
ToolStripItem stripItem = tstTabs.GetItemAt(pt);
{
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();