OSDN Git Service

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