2 using System.Collections.Generic;
7 using System.Text.RegularExpressions;
9 using System.Windows.Forms;
10 using System.Threading;
12 namespace RadegastSpeech.Conversation
15 /// Manages all conversations
17 internal class Control : AreaControl
20 /// Conversations correspond to tabbed panels on the main window.
22 private Dictionary<string, Mode> conversations;
24 /// Interruptions are short-lived conversations about dialog boxes, etc
26 private LinkedList<Mode> interruptions;
28 // The permanent conversations.
29 private Conversation.Chat chat;
30 private Conversation.Closet inventory;
31 private Conversation.Friends friends;
32 private Conversation.Voice voice;
33 private Conversation.Surroundings surroundings;
34 private Mode currentMode;
35 private Mode interrupted;
36 internal string LoginName;
37 private bool firstTime = true;
38 private const string CONVGRAMMAR = "conv";
40 internal Control(PluginControl pc)
43 // Initialize the index to conversations and the list of pending interruptions.
44 interruptions = new LinkedList<Mode>();
45 conversations = new Dictionary<string,Mode>();
48 internal override void Start()
54 control.listener.CreateGrammar(
56 new string[] { "talk to Max",
64 // Automatically handle notifications (blue dialogs)
65 Notification.OnNotificationDisplayed +=
66 new Notification.NotificationCallback(OnNotificationDisplayed);
67 // Notification.OnNotificationClosed +=
68 // new Notification.NotificationCallback(OnNotificationClosed);
70 // Announce connect and disconnect.
71 control.instance.Netcom.ClientConnected +=
72 new EventHandler<EventArgs>(Network_ClientConnected);
73 control.instance.Netcom.ClientDisconnected +=
74 new EventHandler<DisconnectedEventArgs>(Network_Disconnected);
76 control.instance.Netcom.ClientLoginStatus +=
77 new EventHandler<LoginProgressEventArgs>(netcom_ClientLoginStatus);
79 // Notice arrival in a new sim
80 control.instance.Client.Network.SimChanged +=
81 new EventHandler<SimChangedEventArgs>(Network_SimChanged);
83 control.instance.Netcom.ClientLoggingIn +=
84 new EventHandler<Radegast.Netcom.OverrideEventArgs>(Netcom_ClientLoggingIn);
85 // Watch the coming and going of main window tabs.
86 control.instance.TabConsole.OnTabAdded +=
87 new TabsConsole.TabCallback(TabConsole_OnTabAdded);
88 control.instance.TabConsole.OnTabRemoved +=
89 new TabsConsole.TabCallback(TabConsole_OnTabRemoved);
91 // Notice when the active tab changes on the graphics user interface.
92 control.instance.TabConsole.OnTabSelected +=
93 new TabsConsole.TabCallback(OnTabChange);
95 // Handle Instant Messages too
96 control.instance.Client.Self.IM +=
97 new EventHandler<InstantMessageEventArgs>(OnInstantMessage);
99 // Watch for global keys
100 control.instance.MainForm.KeyUp += new KeyEventHandler(MainForm_KeyUp);
102 // System messages in chat window
103 control.instance.TabConsole.OnChatNotification += new TabsConsole.ChatNotificationCallback(TabConsole_OnChatNotification);
105 control.listener.ActivateGrammar(CONVGRAMMAR);
110 /// Say various notifications that come in the chat
112 /// <param name="sender">Message sender</param>
113 /// <param name="e">Event args</param>
114 void TabConsole_OnChatNotification(object sender, ChatNotificationEventArgs e)
116 Talker.Say(e.Message);
120 /// Watch for global function keys.
122 /// <param name="sender"></param>
123 /// <param name="e"></param>
124 void MainForm_KeyUp(object sender, KeyEventArgs e)
126 // Escape clears the speak-ahead queue.
127 if (e.KeyCode == Keys.Escape)
130 Talker.SayMore("Flushed.");
136 void Netcom_ClientLoggingIn(object sender, Radegast.Netcom.OverrideEventArgs e)
138 Talker.SayMore("Logging in. Please wait.");
141 private void netcom_ClientLoginStatus(
143 LoginProgressEventArgs e)
147 case LoginStatus.ConnectingToLogin:
148 // Never seems to happen. See Netcom_ClientLoggingIn
149 Talker.SayMore("Connecting to login server");
152 case LoginStatus.ConnectingToSim:
153 Talker.SayMore("Connecting to region");
156 case LoginStatus.Success:
157 LoginName = control.instance.Netcom.LoginOptions.FullName;
158 //Talker.SayMore("Logged in as " + LoginName);
159 //if (friends != null)
160 // friends.Announce = true;
163 case LoginStatus.Failed:
164 Talker.Say(e.Message +
165 ". Press Enter twice to retry", Talk.BeepType.Bad);
174 /// Switch active conversation as tab focus moves.
176 /// <param name="sender"></param>
177 /// <param name="e"></param>
178 void OnTabChange(object sender, TabEventArgs e)
180 ActivateConversationFromTab(e.Tab);
183 public void ActivateConversationFromTab(RadegastTab Tab)
185 System.Windows.Forms.Control sTabControl = Tab.Control;
187 if (sTabControl is InventoryConsole)
188 SelectConversation(inventory);
189 else if (sTabControl is ChatConsole)
193 chat = new Chat(control);
194 chat.Console = sTabControl;
195 AddConversation(chat);
197 SelectConversation(chat);
199 else if (sTabControl is FriendsConsole)
200 SelectConversation(friends);
201 else if (sTabControl is VoiceConsole)
202 SelectConversation(voice);
203 else if (sTabControl is GroupIMTabWindow)
205 GroupIMTabWindow tab = (GroupIMTabWindow)sTabControl;
207 control.instance.Groups[tab.SessionId].Name);
209 else if (sTabControl is ConferenceIMTabWindow)
211 ConferenceIMTabWindow tab = (ConferenceIMTabWindow)sTabControl;
212 SelectConversation(tab.SessionName);
214 else if (sTabControl is IMTabWindow)
216 IMTabWindow tab = (IMTabWindow)sTabControl;
217 SelectConversation(tab.TargetName);
219 else if (sTabControl is ObjectsConsole)
221 SelectConversation(surroundings);
227 /// Create conversations as tabs are created.
229 /// <param name="sender"></param>
230 /// <param name="e"></param>
231 void TabConsole_OnTabAdded(object sender, TabEventArgs e)
233 CreateConversationFromTab(e.Tab, true);
236 public void CreateConversationFromTab(RadegastTab Tab, bool selectConversation)
238 System.Windows.Forms.Control sTabControl = Tab.Control;
242 // Create a conversation on first appearance of its tab.
243 if (sTabControl is InventoryConsole)
244 newConv = inventory = new Closet(control);
245 else if (sTabControl is ChatConsole)
247 if (chat != null) return;
248 newConv = chat = new Chat(control);
250 else if (sTabControl is FriendsConsole)
251 newConv = friends = new Friends(control);
252 else if (sTabControl is VoiceConsole)
253 newConv = voice = new Voice(control);
254 else if (sTabControl is GroupIMTabWindow)
256 GroupIMTabWindow tab = (GroupIMTabWindow)sTabControl;
257 AddConversation(new GroupIMSession(control, tab.SessionId));
260 else if (sTabControl is ConferenceIMTabWindow)
262 ConferenceIMTabWindow tab = (ConferenceIMTabWindow)sTabControl;
263 AddConversation(new ConferenceIMSession(control, tab.SessionId, tab.SessionName));
266 else if (sTabControl is IMTabWindow)
268 IMTabWindow tab = (IMTabWindow)sTabControl;
269 AddConversation(new SingleIMSession(control, tab.TargetName, tab.TargetId, tab.SessionId));
272 else if (sTabControl is ObjectsConsole)
274 surroundings = new Surroundings(control);
275 AddConversation(surroundings);
278 // If a conversation was created, switch to it.
281 AddConversation(newConv);
282 // Select CHAT as soon as it is created.
283 if (sTabControl is ChatConsole)
284 SelectConversation(newConv);
289 /// Quietly close conversations.
291 /// <param name="sender"></param>
292 /// <param name="e"></param>
293 void TabConsole_OnTabRemoved(object sender, TabEventArgs e)
295 System.Windows.Forms.Control sTabControl = e.Tab.Control;
296 if (sTabControl is InventoryConsole)
297 RemoveConversation(inventory.Title);
298 else if (sTabControl is ChatConsole)
299 RemoveConversation(chat.Title);
300 else if (sTabControl is FriendsConsole)
301 RemoveConversation(friends.Title);
302 else if (sTabControl is VoiceConsole)
303 RemoveConversation(voice.Title);
304 else if (sTabControl is ConferenceIMTabWindow)
305 RemoveConversation(((ConferenceIMTabWindow)e.Tab.Control).SessionName);
306 else if (sTabControl is GroupIMTabWindow ||
307 sTabControl is IMTabWindow)
308 RemoveConversation(sTabControl.Name); // TODO wrong name
312 internal override void Shutdown()
314 // Automatically handle notifications (blue dialogs)
315 Notification.OnNotificationDisplayed -=
316 new Notification.NotificationCallback(OnNotificationDisplayed);
318 // Announce connect and disconnect.
319 control.instance.Netcom.ClientConnected -=
320 new EventHandler<EventArgs>(Network_ClientConnected);
321 control.instance.Netcom.ClientDisconnected -=
322 new EventHandler<DisconnectedEventArgs>(Network_Disconnected);
324 control.instance.Netcom.ClientLoginStatus -=
325 new EventHandler<LoginProgressEventArgs>(netcom_ClientLoginStatus);
327 // Notice arrival in a new sim
328 control.instance.Client.Network.SimChanged -=
329 new EventHandler<SimChangedEventArgs>(Network_SimChanged);
331 control.instance.Netcom.ClientLoggingIn -=
332 new EventHandler<Radegast.Netcom.OverrideEventArgs>(Netcom_ClientLoggingIn);
333 // Watch the coming and going of main window tabs.
334 control.instance.TabConsole.OnTabAdded -=
335 new TabsConsole.TabCallback(TabConsole_OnTabAdded);
336 control.instance.TabConsole.OnTabRemoved -=
337 new TabsConsole.TabCallback(TabConsole_OnTabRemoved);
339 // Notice when the active tab changes on the graphics user interface.
340 control.instance.TabConsole.OnTabSelected -=
341 new TabsConsole.TabCallback(OnTabChange);
343 // Handle Instant Messages too
344 control.instance.Client.Self.IM +=
345 new EventHandler<InstantMessageEventArgs>(OnInstantMessage);
347 // System notifications in chat
348 control.instance.TabConsole.OnChatNotification -= new TabsConsole.ChatNotificationCallback(TabConsole_OnChatNotification);
350 control.listener.DeactivateGrammar(CONVGRAMMAR);
352 foreach (Mode m in conversations.Values)
356 foreach (Mode m in interruptions)
360 conversations.Clear();
361 interruptions.Clear();
368 internal bool amCurrent(Mode m)
370 return (currentMode == m);
374 /// Start an interrupting conversation.
376 void StartInterruption()
378 // Remember what we were talking about.
379 if (interrupted == null)
380 interrupted = currentMode;
382 // Visually they stack up, so we take the last one first.
383 currentMode = interruptions.Last();
388 /// Finish an interruption and resume normal conversation
390 internal void FinishInterruption( Mode m )
394 // Remove the terminating interruption from the list.
395 interruptions.Remove(m);
397 // Let it remove any event hooks, etc
400 // If there are any other interruptions pending, start one.
401 // Otherwise resume the interrupted conversation.
402 if (interruptions.Count > 0)
406 currentMode = interrupted;
413 private void Network_ClientConnected(object sender, EventArgs e)
415 Talker.Say("You are connected.", Talk.BeepType.Good);
418 chat = new Chat(control);
420 AddConversation(chat);
421 SelectConversation(chat);
425 void Network_SimChanged(object sender, SimChangedEventArgs e)
427 Talker.Say("You are now in " +
428 control.instance.Client.Network.CurrentSim.Name,
434 /// Announce reason for disconnect.
436 /// <param name="reason"></param>
437 /// <param name="message"></param>
438 private void Network_Disconnected(object sender, DisconnectedEventArgs e)
442 case NetworkManager.DisconnectType.ClientInitiated:
443 Talker.Say("You are disconnected.");
445 case NetworkManager.DisconnectType.SimShutdown:
446 Talker.Say("The region you were in has been shut down. You are disconnected.",
449 case NetworkManager.DisconnectType.NetworkTimeout:
450 Talker.Say("You have been disconnected by a network timeout.",
453 case NetworkManager.DisconnectType.ServerInitiated:
454 Talker.Say("The server has disconnected you. " + e.Message,
459 private void ListFriends()
461 List<FriendInfo> onlineFriends =
462 control.instance.Client.Friends.FriendList.FindAll(delegate(FriendInfo f) { return f.IsOnline; });
464 foreach (FriendInfo f in onlineFriends)
466 list += f.Name + ", ";
468 list += "are online.";
474 /// Check for general commands
476 /// <param name="message"></param>
477 /// <returns>true if command recognized</returns>
478 private bool Command(string message)
480 switch (message.ToLower())
483 AddInterruption(new Max(control));
485 case "who is online":
488 case "open the closet":
489 control.instance.TabConsole.SelectTab("inventory");
490 SelectConversation(inventory);
493 control.instance.TabConsole.SelectTab("friends");
494 SelectConversation(friends);
498 Talker.SayMore("Flushed.");
501 control.instance.TabConsole.SelectTab("chat");
502 SelectConversation(chat);
505 control.instance.TabConsole.SelectTab("voice");
506 SelectConversation(voice);
515 /// Dispatch recognized text to appropriate conversation.
517 /// <param name="message"></param>
518 internal void Hear(string message)
521 if (Command(message)) return;
523 // Let the current conversation handle it.
524 if (currentMode != null)
525 currentMode.Hear(message);
528 internal void SelectConversation(Mode c)
532 Talker.Say("Trying to start non-existant conversation", Talk.BeepType.Bad );
535 // Avoid multiple starts.
536 if (currentMode == c) return;
538 // Let the old conversation deactivate any event hooks, grammars, etc.
539 if (currentMode != null)
546 internal void SelectConversation(string name)
548 if (conversations.ContainsKey(name))
550 SelectConversation(conversations[name]);
554 Talker.Say("Can not find conversation " + name, Talk.BeepType.Bad);
559 /// Find an existing conversation by name.
561 /// <param name="title"></param>
562 /// <returns></returns>
563 /// <remarks>Used for IM sessions.</remarks>
564 internal Mode GetConversation(string title)
566 if (conversations.ContainsKey(title))
567 return conversations[title];
573 /// Add a conversation context to those we are tracking.
575 /// <param name="m"></param>
576 internal void AddConversation(Mode m)
578 if (!conversations.ContainsKey(m.Title))
579 conversations[m.Title] = m;
583 /// Remove the context for a conversation that is no longer visible.
585 /// <param name="name"></param>
586 internal void RemoveConversation(string name)
592 if (conversations.ContainsKey(name))
594 Mode doomed = conversations[name];
595 if (currentMode == doomed)
600 if (interrupted == doomed)
603 conversations.Remove(name);
605 SelectConversation(currentMode);
611 /// Take note of a new interruption.
613 /// <param name="m"></param>
614 internal void AddInterruption(Mode m)
618 // Add to the end of the list.
619 interruptions.AddLast(m);
621 // If the list WAS empty, start this.
622 if (interruptions.Count == 1)
627 internal void ChangeFocus(Mode toThis)
629 currentMode = toThis;
630 if (currentMode != null)
636 /// Event handler for new blue dialog boxes.
638 /// <param name="sender"></param>
639 /// <param name="e"></param>
640 void OnNotificationDisplayed(object sender, NotificationEventArgs e)
642 AddInterruption(new Conversation.BlueMenu(control,e));
646 /// Handle Instant Messages
648 /// <param name="im"></param>
649 /// <param name="simulator"></param>
650 void OnInstantMessage(object sender, InstantMessageEventArgs e)
652 ThreadPool.QueueUserWorkItem(sync =>
654 Thread.Sleep(100); // Give tab a chance to show up
655 Conversation.IMSession sess = null;
658 // All sorts of things come in as a instant messages. For actual messages
659 // we need to match them up with an existing Conversation. IM Conversations
660 // are keyed by the name of the group or individual involved.
663 case InstantMessageDialog.MessageFromAgent:
664 if (control.instance.Groups.ContainsKey(e.IM.IMSessionID))
666 // Message from a group member
667 groupName = control.instance.Groups[e.IM.IMSessionID].Name;
668 sess = (IMSession)control.converse.GetConversation(groupName);
670 sess.OnMessage(e.IM.FromAgentID, e.IM.FromAgentName, e.IM.Message);
672 Talker.Say(e.IM.FromAgentName + ", " + e.IM.Message);
674 else if (e.IM.BinaryBucket.Length >= 2)
676 // Ad-hoc friend conference
677 sess = (IMSession)control.converse.GetConversation(Utils.BytesToString(e.IM.BinaryBucket));
679 sess.OnMessage(e.IM.FromAgentID, e.IM.FromAgentName, e.IM.Message);
681 Talker.Say(e.IM.FromAgentName + ", " + e.IM.Message);
683 else if (e.IM.FromAgentName == "Second Life")
685 Talker.Say("Second Life says " + e.IM.Message);
689 // Message from an individual
690 sess = (IMSession)control.converse.GetConversation(e.IM.FromAgentName);
692 sess.OnMessage(e.IM.FromAgentID, e.IM.FromAgentName, e.IM.Message);
694 Talker.Say(e.IM.FromAgentName + ", " + e.IM.Message);
698 case InstantMessageDialog.SessionSend:
699 if (control.instance.Groups.ContainsKey(e.IM.IMSessionID))
701 // Message from a group member
702 groupName = control.instance.Groups[e.IM.IMSessionID].Name;
703 sess = (IMSession)control.converse.GetConversation(groupName);
705 else if (e.IM.BinaryBucket.Length >= 2) // ad hoc friends conference
707 sess = (IMSession)control.converse.GetConversation(Utils.BytesToString(e.IM.BinaryBucket));
711 sess.OnMessage(e.IM.FromAgentID, e.IM.FromAgentName, e.IM.Message);
714 case InstantMessageDialog.FriendshipOffered:
715 Talker.Say(e.IM.FromAgentName + " is offering friendship.");