OSDN Git Service

RAD-141: Allow speech plugin to be activated after login.
[radegast/radegast.git] / plugins / Radegast.Plugin.Speech / RadSpeech / Conversation / Control.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using OpenMetaverse;
6 using Radegast;
7 using System.Text.RegularExpressions;
8 using System.Net;
9 using System.Windows.Forms;
10 using System.Threading;
11
12 namespace RadegastSpeech.Conversation
13 {
14     /// <summary>
15     /// Manages all conversations
16     /// </summary>
17     internal class Control : AreaControl
18     {
19         /// <summary>
20         /// Conversations correspond to tabbed panels on the main window.
21         /// </summary>
22         private Dictionary<string, Mode> conversations;
23         /// <summary>
24         /// Interruptions are short-lived conversations about dialog boxes, etc
25         /// </summary>
26         private LinkedList<Mode> interruptions;
27
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";
39
40         internal Control(PluginControl pc)
41             : base(pc)
42         {
43             // Initialize the index to conversations and the list of pending interruptions.
44             interruptions = new LinkedList<Mode>();
45             conversations = new Dictionary<string,Mode>();
46         }
47
48         internal override void Start()
49         {
50             if (firstTime)
51             {
52                 firstTime = false;
53
54                 control.listener.CreateGrammar(
55                     CONVGRAMMAR,
56                     new string[] { "talk to Max",
57                     "skip",
58                     "who is online",
59                     "open the closet",
60                     "friends",
61                     "talk" });
62             }
63
64             // Automatically handle notifications (blue dialogs)
65             Notification.OnNotificationDisplayed +=
66                 new Notification.NotificationCallback(OnNotificationDisplayed);
67 //            Notification.OnNotificationClosed +=
68 //                new Notification.NotificationCallback(OnNotificationClosed);
69
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);
75
76             control.instance.Netcom.ClientLoginStatus +=
77                 new EventHandler<LoginProgressEventArgs>(netcom_ClientLoginStatus);
78
79             // Notice arrival in a new sim
80             control.instance.Client.Network.SimChanged +=
81                 new EventHandler<SimChangedEventArgs>(Network_SimChanged);
82
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);
90
91             // Notice when the active tab changes on the graphics user interface.
92             control.instance.TabConsole.OnTabSelected +=
93                 new TabsConsole.TabCallback(OnTabChange);
94
95             // Handle Instant Messages too
96             control.instance.Client.Self.IM +=
97                 new EventHandler<InstantMessageEventArgs>(OnInstantMessage);
98
99             // Watch for global keys
100             control.instance.MainForm.KeyUp += new KeyEventHandler(MainForm_KeyUp);
101
102             // System messages in chat window
103             control.instance.TabConsole.OnChatNotification += new TabsConsole.ChatNotificationCallback(TabConsole_OnChatNotification);
104
105             control.listener.ActivateGrammar(CONVGRAMMAR);
106
107         }
108
109         /// <summary>
110         /// Say various notifications that come in the chat
111         /// </summary>
112         /// <param name="sender">Message sender</param>
113         /// <param name="e">Event args</param>
114         void TabConsole_OnChatNotification(object sender, ChatNotificationEventArgs e)
115         {
116             Talker.Say(e.Message);
117         }
118
119         /// <summary>
120         /// Watch for global function keys.
121         /// </summary>
122         /// <param name="sender"></param>
123         /// <param name="e"></param>
124         void MainForm_KeyUp(object sender, KeyEventArgs e)
125         {
126             // Escape clears the speak-ahead queue.
127             if (e.KeyCode == Keys.Escape)
128             {
129                 Talker.Flush();
130                 Talker.SayMore("Flushed.");
131                 e.Handled = true;
132             }
133         }
134
135
136         void Netcom_ClientLoggingIn(object sender, Radegast.Netcom.OverrideEventArgs e)
137         {
138             Talker.SayMore("Logging in.  Please wait.");
139         }
140
141         private void netcom_ClientLoginStatus(
142             object sender,
143             LoginProgressEventArgs e)
144         {
145             switch (e.Status)
146             {
147                 case LoginStatus.ConnectingToLogin:
148                     // Never seems to happen.  See Netcom_ClientLoggingIn
149                     Talker.SayMore("Connecting to login server");
150                     return;
151
152                 case LoginStatus.ConnectingToSim:
153                     Talker.SayMore("Connecting to region");
154                     return;
155
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;
161                     return;
162
163                 case LoginStatus.Failed:
164                     Talker.Say(e.Message +
165                         ". Press Enter twice to retry", Talk.BeepType.Bad);
166                     return;
167
168                 default:
169                     return;
170             }
171         }
172
173         /// <summary>
174         /// Switch active conversation as tab focus moves.
175         /// </summary>
176         /// <param name="sender"></param>
177         /// <param name="e"></param>
178         void OnTabChange(object sender, TabEventArgs e)
179         {
180             ActivateConversationFromTab(e.Tab);
181         }
182
183         public void ActivateConversationFromTab(RadegastTab Tab)
184         {
185             System.Windows.Forms.Control sTabControl = Tab.Control;
186
187             if (sTabControl is InventoryConsole)
188                 SelectConversation(inventory);
189             else if (sTabControl is ChatConsole)
190             {
191                 if (chat == null)
192                 {
193                     chat = new Chat(control);
194                     chat.Console = sTabControl;
195                     AddConversation(chat);
196                 }
197                 SelectConversation(chat);
198             }
199             else if (sTabControl is FriendsConsole)
200                 SelectConversation(friends);
201             else if (sTabControl is VoiceConsole)
202                 SelectConversation(voice);
203             else if (sTabControl is GroupIMTabWindow)
204             {
205                 GroupIMTabWindow tab = (GroupIMTabWindow)sTabControl;
206                 SelectConversation(
207                     control.instance.Groups[tab.SessionId].Name);
208             }
209             else if (sTabControl is ConferenceIMTabWindow)
210             {
211                 ConferenceIMTabWindow tab = (ConferenceIMTabWindow)sTabControl;
212                 SelectConversation(tab.SessionName);
213             }
214             else if (sTabControl is IMTabWindow)
215             {
216                 IMTabWindow tab = (IMTabWindow)sTabControl;
217                 SelectConversation(tab.TargetName);
218             }
219             else if (sTabControl is ObjectsConsole)
220             {
221                 SelectConversation(surroundings);
222             }
223
224         }
225
226         /// <summary>
227         /// Create conversations as tabs are created.
228         /// </summary>
229         /// <param name="sender"></param>
230         /// <param name="e"></param>
231         void TabConsole_OnTabAdded(object sender, TabEventArgs e)
232         {
233             CreateConversationFromTab(e.Tab, true);
234         }
235
236         public void CreateConversationFromTab(RadegastTab Tab, bool selectConversation)
237         {
238             System.Windows.Forms.Control sTabControl = Tab.Control;
239
240             Mode newConv = null;
241
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)
246             {
247                 if (chat != null) return;
248                 newConv = chat = new Chat(control);
249             }
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)
255             {
256                 GroupIMTabWindow tab = (GroupIMTabWindow)sTabControl;
257                 AddConversation(new GroupIMSession(control, tab.SessionId));
258                 return;
259             }
260             else if (sTabControl is ConferenceIMTabWindow)
261             {
262                 ConferenceIMTabWindow tab = (ConferenceIMTabWindow)sTabControl;
263                 AddConversation(new ConferenceIMSession(control, tab.SessionId, tab.SessionName));
264                 return;
265             }
266             else if (sTabControl is IMTabWindow)
267             {
268                 IMTabWindow tab = (IMTabWindow)sTabControl;
269                 AddConversation(new SingleIMSession(control, tab.TargetName, tab.TargetId, tab.SessionId));
270                 return;
271             }
272             else if (sTabControl is ObjectsConsole)
273             {
274                 surroundings = new Surroundings(control);
275                 AddConversation(surroundings);
276             }
277
278             // If a conversation was created, switch to it.
279             if (newConv != null)
280             {
281                 AddConversation(newConv);
282                 // Select CHAT as soon as it is created.
283                 if (sTabControl is ChatConsole)
284                     SelectConversation(newConv);
285             }
286         }
287
288         /// <summary>
289         /// Quietly close conversations.
290         /// </summary>
291         /// <param name="sender"></param>
292         /// <param name="e"></param>
293         void TabConsole_OnTabRemoved(object sender, TabEventArgs e)
294         {
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
309         }
310
311
312         internal override void Shutdown()
313         {
314             // Automatically handle notifications (blue dialogs)
315             Notification.OnNotificationDisplayed -=
316                 new Notification.NotificationCallback(OnNotificationDisplayed);
317
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);
323
324             control.instance.Netcom.ClientLoginStatus -=
325                 new EventHandler<LoginProgressEventArgs>(netcom_ClientLoginStatus);
326
327             // Notice arrival in a new sim
328             control.instance.Client.Network.SimChanged -=
329                 new EventHandler<SimChangedEventArgs>(Network_SimChanged);
330
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);
338
339             // Notice when the active tab changes on the graphics user interface.
340             control.instance.TabConsole.OnTabSelected -=
341                 new TabsConsole.TabCallback(OnTabChange);
342
343             // Handle Instant Messages too
344             control.instance.Client.Self.IM +=
345                 new EventHandler<InstantMessageEventArgs>(OnInstantMessage);
346
347             // System notifications in chat
348             control.instance.TabConsole.OnChatNotification -= new TabsConsole.ChatNotificationCallback(TabConsole_OnChatNotification);
349
350             control.listener.DeactivateGrammar(CONVGRAMMAR);
351
352             foreach (Mode m in conversations.Values)
353             {
354                 m.Stop();
355             }
356             foreach (Mode m in interruptions)
357             {
358                 m.Stop();
359             }
360             conversations.Clear();
361             interruptions.Clear();
362         }
363
364         void WatchKeys()
365         {
366         }
367
368         internal bool amCurrent(Mode m)
369         {
370             return (currentMode == m);
371         }
372
373         /// <summary>
374         /// Start an interrupting conversation.
375         /// </summary>
376         void StartInterruption()
377         {
378             // Remember what we were talking about.
379             if (interrupted == null)
380                 interrupted = currentMode;
381
382             // Visually they stack up, so we take the last one first.
383             currentMode = interruptions.Last();
384             currentMode.Start();
385         }
386
387         /// <summary>
388         /// Finish an interruption and resume normal conversation
389         /// </summary>
390         internal void FinishInterruption( Mode m )
391         {
392             lock (interruptions)
393             {
394                 // Remove the terminating interruption from the list.
395                 interruptions.Remove(m);
396
397                 // Let it remove any event hooks, etc
398                 m.Stop();
399
400                 // If there are any other interruptions pending, start one.
401                 // Otherwise resume the interrupted conversation.
402                 if (interruptions.Count > 0)
403                     StartInterruption();
404                 else
405                 {
406                     currentMode = interrupted;
407                     interrupted = null;
408                     currentMode.Start();
409                 }
410             }
411         }
412  
413         private void Network_ClientConnected(object sender, EventArgs e)
414         {
415             Talker.Say("You are connected.", Talk.BeepType.Good);
416             if (chat == null)
417             {
418                 chat = new Chat(control);
419
420                 AddConversation(chat);
421                 SelectConversation(chat);
422             }
423         }
424
425         void Network_SimChanged(object sender, SimChangedEventArgs e)
426         {
427             Talker.Say("You are now in " +
428                 control.instance.Client.Network.CurrentSim.Name,
429                 Talk.BeepType.Good);
430         }
431
432
433         /// <summary>
434         /// Announce reason for disconnect.
435         /// </summary>
436         /// <param name="reason"></param>
437         /// <param name="message"></param>
438         private void Network_Disconnected(object sender, DisconnectedEventArgs e)
439         {
440             switch (e.Reason)
441             {
442                 case NetworkManager.DisconnectType.ClientInitiated:
443                     Talker.Say("You are disconnected.");
444                     break;
445                 case NetworkManager.DisconnectType.SimShutdown:
446                     Talker.Say("The region you were in has been shut down.  You are disconnected.",
447                         Talk.BeepType.Bad);
448                     break;
449                 case NetworkManager.DisconnectType.NetworkTimeout:
450                     Talker.Say("You have been disconnected by a network timeout.",
451                         Talk.BeepType.Bad);
452                     break;
453                 case NetworkManager.DisconnectType.ServerInitiated:
454                     Talker.Say("The server has disconnected you.  " + e.Message,
455                         Talk.BeepType.Bad);
456                     break;
457             }
458         }
459         private void ListFriends()
460         {
461             List<FriendInfo> onlineFriends =
462                 control.instance.Client.Friends.FriendList.FindAll(delegate(FriendInfo f) { return f.IsOnline; });
463             string list = "";
464             foreach (FriendInfo f in onlineFriends)
465             {
466                 list += f.Name + ", ";
467             }
468             list += "are online.";
469             Talker.Say(list);
470         }
471
472
473         /// <summary>
474         /// Check for general commands
475         /// </summary>
476         /// <param name="message"></param>
477         /// <returns>true if command recognized</returns>
478         private bool Command(string message)
479         {
480             switch (message.ToLower())
481             {
482                 case "talk to max":
483                     AddInterruption(new Max(control));
484                     break;
485                 case "who is online":
486                     ListFriends();
487                     break;
488                 case "open the closet":
489                     control.instance.TabConsole.SelectTab("inventory");
490                     SelectConversation(inventory);
491                     break;
492                 case "friends":
493                     control.instance.TabConsole.SelectTab("friends");
494                     SelectConversation(friends);
495                     break;
496                 case "skip":
497                     Talker.Flush();
498                     Talker.SayMore("Flushed.");
499                     break;
500                 case "talk":
501                     control.instance.TabConsole.SelectTab("chat");
502                     SelectConversation(chat);
503                     break;
504                 case "voice":
505                     control.instance.TabConsole.SelectTab("voice");
506                     SelectConversation(voice);
507                     break;
508                 default:
509                     return false;
510             }
511             return true;
512         }
513         
514         /// <summary>
515         /// Dispatch recognized text to appropriate conversation.
516         /// </summary>
517         /// <param name="message"></param>
518         internal void Hear(string message)
519         {
520             // General commands.
521             if (Command(message)) return;
522
523             // Let the current conversation handle it.
524             if (currentMode != null)
525                 currentMode.Hear(message);
526         }
527
528         internal void SelectConversation(Mode c)
529         {
530             if (c == null)
531             {
532                 Talker.Say("Trying to start non-existant conversation", Talk.BeepType.Bad );
533                 return;
534             }
535             // Avoid multiple starts.
536             if (currentMode == c) return;
537
538             // Let the old conversation deactivate any event hooks, grammars, etc.
539             if (currentMode != null)
540                 currentMode.Stop();
541
542             currentMode = c;
543             currentMode.Start();
544         }
545
546         internal void SelectConversation(string name)
547         {
548             if (conversations.ContainsKey(name))
549             {
550                 SelectConversation(conversations[name]);
551             }
552             else
553             {
554                 Talker.Say("Can not find conversation " + name, Talk.BeepType.Bad);
555             }
556         }
557
558         /// <summary>
559         /// Find an existing conversation by name.
560         /// </summary>
561         /// <param name="title"></param>
562         /// <returns></returns>
563         /// <remarks>Used for IM sessions.</remarks>
564         internal Mode GetConversation(string title)
565         {
566             if (conversations.ContainsKey(title))
567                 return conversations[title];
568
569             return null;
570         }
571
572         /// <summary>
573         /// Add a conversation context to those we are tracking.
574         /// </summary>
575         /// <param name="m"></param>
576         internal void AddConversation(Mode m)
577         {
578             if (!conversations.ContainsKey(m.Title))
579                 conversations[m.Title] = m;
580         }
581
582         /// <summary>
583         /// Remove the context for a conversation that is no longer visible.
584         /// </summary>
585         /// <param name="name"></param>
586         internal void RemoveConversation(string name)
587         {
588             bool change = false;
589
590             lock (conversations)
591             {
592                 if (conversations.ContainsKey(name))
593                 {
594                     Mode doomed = conversations[name];
595                     if (currentMode == doomed)
596                     {
597                         change = true;
598                         currentMode = chat;
599                     }
600                     if (interrupted == doomed)
601                         interrupted = chat;
602
603                     conversations.Remove(name);
604                     if (change)
605                         SelectConversation(currentMode);
606                 }
607             }
608         }
609
610         /// <summary>
611         /// Take note of a new interruption.
612         /// </summary>
613         /// <param name="m"></param>
614         internal void AddInterruption(Mode m)
615         {
616             lock (interruptions)
617             {
618                 // Add to the end of the list.
619                 interruptions.AddLast(m);
620
621                 // If the list WAS empty, start this.
622                 if (interruptions.Count == 1)
623                     StartInterruption();
624             }
625         }
626
627         internal void ChangeFocus(Mode toThis)
628         {
629             currentMode = toThis;
630              if (currentMode != null)
631                 currentMode.Start();
632         }
633
634  
635         /// <summary>
636         /// Event handler for new blue dialog boxes.
637         /// </summary>
638         /// <param name="sender"></param>
639         /// <param name="e"></param>
640         void OnNotificationDisplayed(object sender, NotificationEventArgs e)
641         {
642             AddInterruption(new Conversation.BlueMenu(control,e));
643         }
644
645         /// <summary>
646         /// Handle Instant Messages
647         /// </summary>
648         /// <param name="im"></param>
649         /// <param name="simulator"></param>
650         void OnInstantMessage(object sender, InstantMessageEventArgs e)
651         {
652             ThreadPool.QueueUserWorkItem(sync =>
653                 {
654                     Thread.Sleep(100); // Give tab a chance to show up
655                     Conversation.IMSession sess = null;
656                     string groupName;
657
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.
661                     switch (e.IM.Dialog)
662                     {
663                         case InstantMessageDialog.MessageFromAgent:
664                             if (control.instance.Groups.ContainsKey(e.IM.IMSessionID))
665                             {
666                                 // Message from a group member
667                                 groupName = control.instance.Groups[e.IM.IMSessionID].Name;
668                                 sess = (IMSession)control.converse.GetConversation(groupName);
669                                 if (sess != null)
670                                     sess.OnMessage(e.IM.FromAgentID, e.IM.FromAgentName, e.IM.Message);
671                                 else
672                                     Talker.Say(e.IM.FromAgentName + ", " + e.IM.Message);
673                             }
674                             else if (e.IM.BinaryBucket.Length >= 2)
675                             {
676                                 // Ad-hoc friend conference
677                                 sess = (IMSession)control.converse.GetConversation(Utils.BytesToString(e.IM.BinaryBucket));
678                                 if (sess != null)
679                                     sess.OnMessage(e.IM.FromAgentID, e.IM.FromAgentName, e.IM.Message);
680                                 else
681                                     Talker.Say(e.IM.FromAgentName + ", " + e.IM.Message);
682                             }
683                             else if (e.IM.FromAgentName == "Second Life")
684                             {
685                                 Talker.Say("Second Life says " + e.IM.Message);
686                             }
687                             else
688                             {
689                                 // Message from an individual
690                                 sess = (IMSession)control.converse.GetConversation(e.IM.FromAgentName);
691                                 if (sess != null)
692                                     sess.OnMessage(e.IM.FromAgentID, e.IM.FromAgentName, e.IM.Message);
693                                 else
694                                     Talker.Say(e.IM.FromAgentName + ", " + e.IM.Message);
695                             }
696                             break;
697
698                         case InstantMessageDialog.SessionSend:
699                             if (control.instance.Groups.ContainsKey(e.IM.IMSessionID))
700                             {
701                                 // Message from a group member
702                                 groupName = control.instance.Groups[e.IM.IMSessionID].Name;
703                                 sess = (IMSession)control.converse.GetConversation(groupName);
704                             }
705                             else if (e.IM.BinaryBucket.Length >= 2) // ad hoc friends conference
706                             {
707                                 sess = (IMSession)control.converse.GetConversation(Utils.BytesToString(e.IM.BinaryBucket));
708                             }
709
710                             if (sess != null)
711                                 sess.OnMessage(e.IM.FromAgentID, e.IM.FromAgentName, e.IM.Message);
712                             break;
713
714                         case InstantMessageDialog.FriendshipOffered:
715                             Talker.Say(e.IM.FromAgentName + " is offering friendship.");
716                             break;
717
718                         default:
719                             break;
720                     }
721                 }
722             );
723
724         }
725     }
726 }