using System; using System.Collections.Generic; using System.Linq; using System.Text; using OpenMetaverse; using Radegast; using System.Text.RegularExpressions; using System.Net; using System.Windows.Forms; namespace RadegastSpeech.Conversation { /// /// Manages all conversations /// internal class Control : AreaControl { /// /// Conversations correspond to tabbed panels on the main window. /// private Dictionary conversations; /// /// Interruptions are short-lived conversations about dialog boxes, etc /// private LinkedList interruptions; // The permanent conversations. private Conversation.Chat chat; private Conversation.Closet inventory; private Conversation.Friends friends; private Conversation.Voice voice; private Conversation.Surroundings surroundings; private Mode currentMode; private Mode interrupted; internal string LoginName; private bool firstTime = true; private const string CONVGRAMMAR = "conv"; internal Control(PluginControl pc) : base(pc) { // Initialize the index to conversations and the list of pending interruptions. interruptions = new LinkedList(); conversations = new Dictionary(); } internal override void Start() { if (firstTime) { firstTime = false; control.listener.CreateGrammar( CONVGRAMMAR, new string[] { "talk to Max", "skip", "who is online", "open the closet", "friends", "talk" }); } // Automatically handle notifications (blue dialogs) Notification.OnNotificationDisplayed += new Notification.NotificationCallback(OnNotificationDisplayed); // Notification.OnNotificationClosed += // new Notification.NotificationCallback(OnNotificationClosed); // Announce connect and disconnect. control.instance.Netcom.ClientConnected += new EventHandler(Network_ClientConnected); control.instance.Netcom.ClientDisconnected += new EventHandler(Network_Disconnected); control.instance.Netcom.ClientLoginStatus += new EventHandler(netcom_ClientLoginStatus); // Notice arrival in a new sim control.instance.Client.Network.SimChanged += new EventHandler(Network_SimChanged); control.instance.Netcom.ClientLoggingIn += new EventHandler(Netcom_ClientLoggingIn); // Watch the coming and going of main window tabs. control.instance.TabConsole.OnTabAdded += new TabsConsole.TabCallback(TabConsole_OnTabAdded); control.instance.TabConsole.OnTabRemoved += new TabsConsole.TabCallback(TabConsole_OnTabRemoved); // Notice when the active tab changes on the graphics user interface. control.instance.TabConsole.OnTabSelected += new TabsConsole.TabCallback(OnTabChange); // Handle Instant Messages too control.instance.Client.Self.IM += new EventHandler(OnInstantMessage); // Watch for global keys control.instance.MainForm.KeyUp += new KeyEventHandler(MainForm_KeyUp); // System messages in chat window control.instance.TabConsole.OnChatNotification += new TabsConsole.ChatNotificationCallback(TabConsole_OnChatNotification); control.listener.ActivateGrammar(CONVGRAMMAR); } /// /// Say various notifications that come in the chat /// /// Message sender /// Event args void TabConsole_OnChatNotification(object sender, ChatNotificationEventArgs e) { Talker.Say(e.Message); } /// /// Watch for global function keys. /// /// /// void MainForm_KeyUp(object sender, KeyEventArgs e) { // Escape clears the speak-ahead queue. if (e.KeyCode == Keys.Escape) { Talker.Flush(); Talker.SayMore("Flushed."); e.Handled = true; } } void Netcom_ClientLoggingIn(object sender, Radegast.Netcom.OverrideEventArgs e) { Talker.SayMore("Logging in. Please wait."); } private void netcom_ClientLoginStatus( object sender, LoginProgressEventArgs e) { switch (e.Status) { case LoginStatus.ConnectingToLogin: // Never seems to happen. See Netcom_ClientLoggingIn Talker.SayMore("Connecting to login server"); return; case LoginStatus.ConnectingToSim: Talker.SayMore("Connecting to region"); return; case LoginStatus.Success: LoginName = control.instance.Netcom.LoginOptions.FullName; //Talker.SayMore("Logged in as " + LoginName); //if (friends != null) // friends.Announce = true; return; case LoginStatus.Failed: Talker.Say(e.Message + ". Press Enter twice to retry", Talk.BeepType.Bad); return; default: return; } } /// /// Switch active conversation as tab focus moves. /// /// /// void OnTabChange(object sender, TabEventArgs e) { System.Windows.Forms.Control sTabControl = e.Tab.Control; if (sTabControl is InventoryConsole) SelectConversation(inventory); else if (sTabControl is ChatConsole) { if (chat == null) { chat = new Chat(control); chat.Console = sTabControl; AddConversation(chat); } SelectConversation(chat); } else if (sTabControl is FriendsConsole) SelectConversation(friends); else if (sTabControl is VoiceConsole) SelectConversation(voice); else if (sTabControl is GroupIMTabWindow) { GroupIMTabWindow tab = (GroupIMTabWindow)sTabControl; SelectConversation( control.instance.Groups[tab.SessionId].Name); } else if (sTabControl is IMTabWindow) { IMTabWindow tab = (IMTabWindow)sTabControl; SelectConversation(tab.TargetName); } else if (sTabControl is ObjectsConsole) { SelectConversation(surroundings); } } /// /// Create conversations as tabs are created. /// /// /// void TabConsole_OnTabAdded(object sender, TabEventArgs e) { System.Windows.Forms.Control sTabControl = e.Tab.Control; Mode newConv = null; // Create a conversation on first appearance of its tab. if (sTabControl is InventoryConsole) newConv = inventory = new Closet(control); else if (sTabControl is ChatConsole) { if (chat != null) return; newConv = chat = new Chat(control); } else if (sTabControl is FriendsConsole) newConv = friends = new Friends(control); else if (sTabControl is VoiceConsole) newConv = voice = new Voice(control); else if (sTabControl is GroupIMTabWindow) { GroupIMTabWindow tab = (GroupIMTabWindow)sTabControl; AddConversation(new GroupIMSession(control, tab.SessionId)); return; } else if (sTabControl is IMTabWindow) { IMTabWindow tab = (IMTabWindow)sTabControl; AddConversation(new SingleIMSession(control, tab.TargetName, tab.TargetId, tab.SessionId)); return; } else if (sTabControl is ObjectsConsole) { surroundings = new Surroundings( control ); AddConversation( surroundings ); } // If a conversation was created, switch to it. if (newConv != null) { AddConversation(newConv); // Select CHAT as soon as it is created. if (sTabControl is ChatConsole) SelectConversation(newConv); } } /// /// Quietly close conversations. /// /// /// void TabConsole_OnTabRemoved(object sender, TabEventArgs e) { System.Windows.Forms.Control sTabControl = e.Tab.Control; if (sTabControl is InventoryConsole) RemoveConversation( inventory.Title ); else if (sTabControl is ChatConsole) RemoveConversation(chat.Title); else if (sTabControl is FriendsConsole) RemoveConversation(friends.Title); else if (sTabControl is VoiceConsole) RemoveConversation(voice.Title); else if (sTabControl is GroupIMTabWindow || sTabControl is IMTabWindow) RemoveConversation(sTabControl.Name); // TODO wrong name } internal override void Shutdown() { // Automatically handle notifications (blue dialogs) Notification.OnNotificationDisplayed -= new Notification.NotificationCallback(OnNotificationDisplayed); // Announce connect and disconnect. control.instance.Netcom.ClientConnected -= new EventHandler(Network_ClientConnected); control.instance.Netcom.ClientDisconnected -= new EventHandler(Network_Disconnected); control.instance.Netcom.ClientLoginStatus -= new EventHandler(netcom_ClientLoginStatus); // Notice arrival in a new sim control.instance.Client.Network.SimChanged -= new EventHandler(Network_SimChanged); control.instance.Netcom.ClientLoggingIn -= new EventHandler(Netcom_ClientLoggingIn); // Watch the coming and going of main window tabs. control.instance.TabConsole.OnTabAdded -= new TabsConsole.TabCallback(TabConsole_OnTabAdded); control.instance.TabConsole.OnTabRemoved -= new TabsConsole.TabCallback(TabConsole_OnTabRemoved); // Notice when the active tab changes on the graphics user interface. control.instance.TabConsole.OnTabSelected -= new TabsConsole.TabCallback(OnTabChange); // Handle Instant Messages too control.instance.Client.Self.IM += new EventHandler(OnInstantMessage); // System notifications in chat control.instance.TabConsole.OnChatNotification -= new TabsConsole.ChatNotificationCallback(TabConsole_OnChatNotification); control.listener.DeactivateGrammar(CONVGRAMMAR); foreach (Mode m in conversations.Values) { m.Stop(); } foreach (Mode m in interruptions) { m.Stop(); } conversations.Clear(); interruptions.Clear(); } void WatchKeys() { } internal bool amCurrent(Mode m) { return (currentMode == m); } /// /// Start an interrupting conversation. /// void StartInterruption() { // Remember what we were talking about. if (interrupted == null) interrupted = currentMode; // Visually they stack up, so we take the last one first. currentMode = interruptions.Last(); currentMode.Start(); } /// /// Finish an interruption and resume normal conversation /// internal void FinishInterruption( Mode m ) { lock (interruptions) { // Remove the terminating interruption from the list. interruptions.Remove(m); // Let it remove any event hooks, etc m.Stop(); // If there are any other interruptions pending, start one. // Otherwise resume the interrupted conversation. if (interruptions.Count > 0) StartInterruption(); else { currentMode = interrupted; interrupted = null; currentMode.Start(); } } } private void Network_ClientConnected(object sender, EventArgs e) { Talker.Say("You are connected.", Talk.BeepType.Good); if (chat == null) { chat = new Chat(control); AddConversation(chat); SelectConversation(chat); } } void Network_SimChanged(object sender, SimChangedEventArgs e) { Talker.Say("You are now in " + control.instance.Client.Network.CurrentSim.Name, Talk.BeepType.Good); } /// /// Announce reason for disconnect. /// /// /// private void Network_Disconnected(object sender, DisconnectedEventArgs e) { switch (e.Reason) { case NetworkManager.DisconnectType.ClientInitiated: Talker.Say("You are disconnected."); break; case NetworkManager.DisconnectType.SimShutdown: Talker.Say("The region you were in has been shut down. You are disconnected.", Talk.BeepType.Bad); break; case NetworkManager.DisconnectType.NetworkTimeout: Talker.Say("You have been disconnected by a network timeout.", Talk.BeepType.Bad); break; case NetworkManager.DisconnectType.ServerInitiated: Talker.Say("The server has disconnected you. " + e.Message, Talk.BeepType.Bad); break; } } private void ListFriends() { List onlineFriends = control.instance.Client.Friends.FriendList.FindAll(delegate(FriendInfo f) { return f.IsOnline; }); string list = ""; foreach (FriendInfo f in onlineFriends) { list += f.Name + ", "; } list += "are online."; Talker.Say(list); } /// /// Check for general commands /// /// /// true if command recognized private bool Command(string message) { switch (message.ToLower()) { case "talk to max": AddInterruption(new Max(control)); break; case "who is online": ListFriends(); break; case "open the closet": control.instance.TabConsole.SelectTab("inventory"); SelectConversation(inventory); break; case "friends": control.instance.TabConsole.SelectTab("friends"); SelectConversation(friends); break; case "skip": Talker.Flush(); Talker.SayMore("Flushed."); break; case "talk": control.instance.TabConsole.SelectTab("chat"); SelectConversation(chat); break; case "voice": control.instance.TabConsole.SelectTab("voice"); SelectConversation(voice); break; default: return false; } return true; } /// /// Dispatch recognized text to appropriate conversation. /// /// internal void Hear(string message) { // General commands. if (Command(message)) return; // Let the current conversation handle it. if (currentMode != null) currentMode.Hear(message); } internal void SelectConversation(Mode c) { if (c == null) { Talker.Say("Trying to start non-existant conversation", Talk.BeepType.Bad ); return; } // Avoid multiple starts. if (currentMode == c) return; // Let the old conversation deactivate any event hooks, grammars, etc. if (currentMode != null) currentMode.Stop(); currentMode = c; currentMode.Start(); } internal void SelectConversation(string name) { if (conversations.ContainsKey(name)) { SelectConversation(conversations[name]); } else { Talker.Say("Can not find conversation " + name, Talk.BeepType.Bad); } } /// /// Find an existing conversation by name. /// /// /// /// Used for IM sessions. internal Mode GetConversation(string title) { if (conversations.ContainsKey(title)) return conversations[title]; return null; } /// /// Add a conversation context to those we are tracking. /// /// internal void AddConversation(Mode m) { if (!conversations.ContainsKey(m.Title)) conversations[m.Title] = m; } /// /// Remove the context for a conversation that is no longer visible. /// /// internal void RemoveConversation(string name) { bool change = false; lock (conversations) { if (conversations.ContainsKey(name)) { Mode doomed = conversations[name]; if (currentMode == doomed) { change = true; currentMode = chat; } if (interrupted == doomed) interrupted = chat; conversations.Remove(name); if (change) SelectConversation(currentMode); } } } /// /// Take note of a new interruption. /// /// internal void AddInterruption(Mode m) { lock (interruptions) { // Add to the end of the list. interruptions.AddLast(m); // If the list WAS empty, start this. if (interruptions.Count == 1) StartInterruption(); } } internal void ChangeFocus(Mode toThis) { currentMode = toThis; if (currentMode != null) currentMode.Start(); } /// /// Event handler for new blue dialog boxes. /// /// /// void OnNotificationDisplayed(object sender, NotificationEventArgs e) { AddInterruption(new Conversation.BlueMenu(control,e)); } /// /// Handle Instant Messages /// /// /// void OnInstantMessage(object sender, InstantMessageEventArgs e) { Conversation.IMSession sess; string groupName; // All sorts of things come in as a instant messages. For actual messages // we need to match them up with an existing Conversation. IM Conversations // are keyed by the name of the group or individual involved. switch (e.IM.Dialog) { case InstantMessageDialog.MessageFromAgent: if (control.instance.Groups.ContainsKey(e.IM.IMSessionID)) { // Message from a group member groupName = control.instance.Groups[e.IM.IMSessionID].Name; sess = (IMSession)control.converse.GetConversation(groupName); if (sess!=null) sess.OnMessage( e.IM.FromAgentID, e.IM.FromAgentName, e.IM.Message ); } else if (e.IM.BinaryBucket.Length >= 2) { // Ad-hoc friend conference // TODO this is probably the wrong name sess = (IMSession)control.converse.GetConversation(e.IM.FromAgentName); if (sess != null) sess.OnMessage(e.IM.FromAgentID, e.IM.FromAgentName, e.IM.Message); } else if (e.IM.FromAgentName == "Second Life") { Talker.Say("Second Life says "+ e.IM.Message); } else { // Message from an individual sess = (IMSession)control.converse.GetConversation(e.IM.FromAgentName); if (sess != null) sess.OnMessage(e.IM.FromAgentID, e.IM.FromAgentName, e.IM.Message); } break; case InstantMessageDialog.SessionSend: // Message from a group member groupName = control.instance.Groups[e.IM.IMSessionID].Name; sess = (IMSession)control.converse.GetConversation(groupName); if (sess != null) sess.OnMessage(e.IM.FromAgentID, e.IM.FromAgentName, e.IM.Message); break; case InstantMessageDialog.FriendshipOffered: Talker.Say(e.IM.FromAgentName + " is offering friendship."); break; default: break; } } } }