OSDN Git Service

Shutdown sequence cleanup.
[radegast/radegast.git] / Radegast / GUI / Dialogs / MainForm.cs
1 // \r
2 // Radegast Metaverse Client\r
3 // Copyright (c) 2009, Radegast Development Team\r
4 // All rights reserved.\r
5 // \r
6 // Redistribution and use in source and binary forms, with or without\r
7 // modification, are permitted provided that the following conditions are met:\r
8 // \r
9 //     * Redistributions of source code must retain the above copyright notice,\r
10 //       this list of conditions and the following disclaimer.\r
11 //     * Redistributions in binary form must reproduce the above copyright\r
12 //       notice, this list of conditions and the following disclaimer in the\r
13 //       documentation and/or other materials provided with the distribution.\r
14 //     * Neither the name of the application "Radegast", nor the names of its\r
15 //       contributors may be used to endorse or promote products derived from\r
16 //       this software without specific prior written permission.\r
17 // \r
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
19 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
20 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\r
21 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\r
22 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\r
23 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\r
24 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\r
25 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\r
26 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\r
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
28 //\r
29 // $Id$\r
30 //\r
31 using System;\r
32 using System.Collections.Generic;\r
33 using System.Drawing;\r
34 using System.Text;\r
35 using System.Text.RegularExpressions;\r
36 using System.Timers;\r
37 using System.Threading;\r
38 using System.Windows.Forms;\r
39 using System.Resources;\r
40 using System.IO;\r
41 using System.Web;\r
42 using Radegast.Netcom;\r
43 using OpenMetaverse;\r
44 using OpenMetaverse.StructuredData;\r
45 using OpenMetaverse.Assets;\r
46 \r
47 namespace Radegast\r
48 {\r
49     public partial class frmMain : RadegastForm\r
50     {\r
51         #region Public members\r
52         public static ImageList ResourceImages = new ImageList();\r
53         public static List<string> ImageNames = new List<string>();\r
54 \r
55         public TabsConsole TabConsole\r
56         {\r
57             get { return tabsConsole; }\r
58         }\r
59 \r
60         public MapConsole WorldMap\r
61         {\r
62             get\r
63             {\r
64                 if (MapTab != null)\r
65                 {\r
66                     return (MapConsole)MapTab.Control;\r
67                 }\r
68                 return null;\r
69             }\r
70         }\r
71 \r
72         public RadegastTab MapTab\r
73         {\r
74             get\r
75             {\r
76                 if (tabsConsole.TabExists("map"))\r
77                 {\r
78                     return tabsConsole.Tabs["map"];\r
79                 }\r
80                 else\r
81                 {\r
82                     return null;\r
83                 }\r
84             }\r
85         }\r
86 \r
87         public MediaConsole MediaConsole { get { return mediaConsole; } }\r
88 \r
89         /// <summary>\r
90         /// Drop down that contains the tools menu\r
91         /// </summary>\r
92         public ToolStripDropDownButton ToolsMenu\r
93         {\r
94             get { return tbnTools; }\r
95         }\r
96 \r
97         /// <summary>\r
98         /// Dropdown that contains the heelp menu\r
99         /// </summary>\r
100         public ToolStripDropDownButton HelpMenu\r
101         {\r
102             get { return tbtnHelp; }\r
103         }\r
104 \r
105         /// <summary>\r
106         /// Drop down that contants the plugins menu. Make sure to set it Visible if\r
107         /// you add items to this menu, it's hidden by default\r
108         /// </summary>\r
109         public ToolStripDropDownButton PluginsMenu\r
110         {\r
111             get { return tbnPlugins; }\r
112         }\r
113 \r
114         #endregion\r
115 \r
116         #region Private members\r
117         private RadegastInstance instance;\r
118         private GridClient client { get { return instance.Client; } }\r
119         private RadegastNetcom netcom { get { return instance.Netcom; } }\r
120         private TabsConsole tabsConsole;\r
121         private System.Timers.Timer statusTimer;\r
122         private AutoPilot ap;\r
123         private bool AutoPilotActive = false;\r
124         private TransparentButton btnDialogNextControl;\r
125         private MediaConsole mediaConsole;\r
126         #endregion\r
127 \r
128         #region Constructor and disposal\r
129         public frmMain(RadegastInstance instance)\r
130             : base(instance)\r
131         {\r
132             GetSLTimeZone();\r
133             InitializeComponent();\r
134             Disposed += new EventHandler(frmMain_Disposed);\r
135 \r
136             this.instance = instance;\r
137             this.instance.ClientChanged += new EventHandler<ClientChangedEventArgs>(instance_ClientChanged);\r
138             netcom.NetcomSync = this;\r
139 \r
140             pnlDialog.Visible = false;\r
141             btnDialogNextControl = new TransparentButton();\r
142             pnlDialog.Controls.Add(btnDialogNextControl);\r
143             pnlDialog.Top = 0;\r
144 \r
145             btnDialogNextControl.Size = new Size(35, 20);\r
146             btnDialogNextControl.BackColor = Color.Transparent;\r
147             btnDialogNextControl.ForeColor = Color.Gold;\r
148             btnDialogNextControl.FlatAppearance.BorderSize = 0;\r
149             btnDialogNextControl.FlatStyle = FlatStyle.Flat;\r
150             btnDialogNextControl.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;\r
151             btnDialogNextControl.Text = ">>";\r
152             btnDialogNextControl.Font = new Font(btnDialogNextControl.Font, FontStyle.Bold);\r
153             btnDialogNextControl.Margin = new Padding(0);\r
154             btnDialogNextControl.Padding = new Padding(0);\r
155             btnDialogNextControl.UseVisualStyleBackColor = false;\r
156             btnDialogNextControl.Top = btnDialogNextControl.Parent.ClientSize.Height - btnDialogNextControl.Size.Height;\r
157             btnDialogNextControl.Left = btnDialogNextControl.Parent.ClientSize.Width - btnDialogNextControl.Size.Width;\r
158             btnDialogNextControl.Click += new EventHandler(btnDialogNextControl_Click);\r
159 \r
160             if (instance.MonoRuntime)\r
161             {\r
162                 statusStrip1.LayoutStyle = ToolStripLayoutStyle.Table;\r
163             }\r
164 \r
165             // Config options\r
166             if (instance.GlobalSettings["transaction_notification_chat"].Type == OSDType.Unknown)\r
167                 instance.GlobalSettings["transaction_notification_chat"] = OSD.FromBoolean(true);\r
168 \r
169             if (instance.GlobalSettings["transaction_notification_dialog"].Type == OSDType.Unknown)\r
170                 instance.GlobalSettings["transaction_notification_dialog"] = OSD.FromBoolean(true);\r
171 \r
172             // Callbacks\r
173             netcom.ClientLoginStatus += new EventHandler<LoginProgressEventArgs>(netcom_ClientLoginStatus);\r
174             netcom.ClientLoggedOut += new EventHandler(netcom_ClientLoggedOut);\r
175             netcom.ClientDisconnected += new EventHandler<DisconnectedEventArgs>(netcom_ClientDisconnected);\r
176             RegisterClientEvents(client);\r
177 \r
178             InitializeStatusTimer();\r
179             RefreshWindowTitle();\r
180         }\r
181 \r
182         private void RegisterClientEvents(GridClient client)\r
183         {\r
184             client.Parcels.ParcelProperties += new EventHandler<ParcelPropertiesEventArgs>(Parcels_ParcelProperties);\r
185             client.Self.MoneyBalanceReply += new EventHandler<MoneyBalanceReplyEventArgs>(Self_MoneyBalanceReply);\r
186         }\r
187 \r
188         private void UnregisterClientEvents(GridClient client)\r
189         {\r
190             client.Parcels.ParcelProperties -= new EventHandler<ParcelPropertiesEventArgs>(Parcels_ParcelProperties);\r
191             client.Self.MoneyBalanceReply -= new EventHandler<MoneyBalanceReplyEventArgs>(Self_MoneyBalanceReply);\r
192         }\r
193 \r
194         void instance_ClientChanged(object sender, ClientChangedEventArgs e)\r
195         {\r
196             UnregisterClientEvents(e.OldClient);\r
197             RegisterClientEvents(client);\r
198         }\r
199 \r
200         void frmMain_Disposed(object sender, EventArgs e)\r
201         {\r
202             netcom.NetcomSync = null;\r
203             netcom.ClientLoginStatus -= new EventHandler<LoginProgressEventArgs>(netcom_ClientLoginStatus);\r
204             netcom.ClientLoggedOut -= new EventHandler(netcom_ClientLoggedOut);\r
205             netcom.ClientDisconnected -= new EventHandler<DisconnectedEventArgs>(netcom_ClientDisconnected);\r
206             UnregisterClientEvents(client);\r
207             this.instance.CleanUp();\r
208         }\r
209         #endregion\r
210 \r
211         #region Event handlers\r
212         void Self_MoneyBalanceReply(object sender, MoneyBalanceReplyEventArgs e)\r
213         {\r
214             if (!String.IsNullOrEmpty(e.Description))\r
215             {\r
216                 if (instance.GlobalSettings["transaction_notification_dialog"].AsBoolean())\r
217                     AddNotification(new ntfGeneric(instance, e.Description));\r
218                 if (instance.GlobalSettings["transaction_notification_chat"].AsBoolean())\r
219                     TabConsole.DisplayNotificationInChat(e.Description);\r
220             }\r
221         }\r
222 \r
223         public void InitializeControls()\r
224         {\r
225             InitializeTabsConsole();\r
226 \r
227             if (instance.MediaManager.SoundSystemAvailable)\r
228             {\r
229                 mediaConsole = new MediaConsole(instance);\r
230                 tbtnMedia.Visible = true;\r
231             }\r
232         }\r
233 \r
234         public bool InAutoReconnect { get; set; }\r
235 \r
236         private void DisplayAutoReconnectForm()\r
237         {\r
238             if (IsDisposed) return;\r
239 \r
240             if (InvokeRequired)\r
241             {\r
242                 BeginInvoke(new MethodInvoker(DisplayAutoReconnectForm));\r
243                 return;\r
244             }\r
245 \r
246             InAutoReconnect = true;\r
247             frmReconnect dialog = new frmReconnect(instance, 120);\r
248             dialog.ShowDialog(this);\r
249             dialog.Dispose();\r
250             dialog = null;\r
251         }\r
252 \r
253         public void BeginAutoReconnect()\r
254         {\r
255             // Sleep for 3 seconds on a separate thread while things unwind on\r
256             // disconnect, since ShowDialog() blocks GUI thread\r
257             (new Thread(new ThreadStart(() =>\r
258                 {\r
259                     System.Threading.Thread.Sleep(3000);\r
260                     DisplayAutoReconnectForm();\r
261                 }\r
262                 ))\r
263                 {\r
264                     Name = "Reconnect Delay Thread",\r
265                     IsBackground = true\r
266                 }\r
267             ).Start();\r
268         }\r
269 \r
270         private void netcom_ClientLoginStatus(object sender, LoginProgressEventArgs e)\r
271         {\r
272             if (e.Status == LoginStatus.Failed)\r
273             {\r
274                 if (InAutoReconnect)\r
275                 {\r
276                     if (instance.GlobalSettings["auto_reconnect"].AsBoolean())\r
277                         BeginAutoReconnect();\r
278                     else\r
279                         InAutoReconnect = false;\r
280                 }\r
281             }\r
282             else if (e.Status == LoginStatus.Success)\r
283             {\r
284                 InAutoReconnect = false;\r
285                 tbtnVoice.Enabled = disconnectToolStripMenuItem.Enabled =\r
286                 tbtnGroups.Enabled = tbnObjects.Enabled = tbtnWorld.Enabled = tbnTools.Enabled = tmnuImport.Enabled =\r
287                     tbtnFriends.Enabled = tbtnInventory.Enabled = tbtnSearch.Enabled = tbtnMap.Enabled = true;\r
288 \r
289                 statusTimer.Start();\r
290                 RefreshWindowTitle();\r
291             }\r
292         }\r
293 \r
294         private void netcom_ClientLoggedOut(object sender, EventArgs e)\r
295         {\r
296             tbtnVoice.Enabled = disconnectToolStripMenuItem.Enabled =\r
297             tbtnGroups.Enabled = tbnObjects.Enabled = tbtnWorld.Enabled = tbnTools.Enabled = tmnuImport.Enabled =\r
298                 tbtnFriends.Enabled = tbtnInventory.Enabled = tbtnSearch.Enabled = tbtnMap.Enabled = false;\r
299 \r
300             reconnectToolStripMenuItem.Enabled = true;\r
301             InAutoReconnect = false;\r
302 \r
303             if (statusTimer != null)\r
304                 statusTimer.Stop();\r
305 \r
306             RefreshStatusBar();\r
307             RefreshWindowTitle();\r
308         }\r
309 \r
310         private void netcom_ClientDisconnected(object sender, DisconnectedEventArgs e)\r
311         {\r
312             if (e.Reason == NetworkManager.DisconnectType.ClientInitiated) return;\r
313             netcom_ClientLoggedOut(sender, EventArgs.Empty);\r
314 \r
315             if (instance.GlobalSettings["auto_reconnect"].AsBoolean())\r
316             {\r
317                 BeginAutoReconnect();\r
318             }\r
319         }\r
320 \r
321         private void frmMain_FormClosing(object sender, FormClosingEventArgs e)\r
322         {\r
323             if (statusTimer != null)\r
324             {\r
325                 statusTimer.Stop();\r
326                 statusTimer.Dispose();\r
327                 statusTimer = null;\r
328             }\r
329 \r
330             if (mediaConsole != null)\r
331             {\r
332                 if (tabsConsole.TabExists("media"))\r
333                 {\r
334                     tabsConsole.Tabs["media"].AllowClose = true;\r
335                     tabsConsole.Tabs["media"].Close();\r
336                 }\r
337                 else\r
338                 {\r
339                     mediaConsole.Dispose();\r
340                 }\r
341                 mediaConsole = null;\r
342             }\r
343 \r
344             if (netcom.IsLoggedIn)\r
345             {\r
346                 Thread saveInvToDisk = new Thread(new ThreadStart(\r
347                     delegate()\r
348                     {\r
349                         client.Inventory.Store.SaveToDisk(instance.InventoryCacheFileName);\r
350                     }));\r
351                 saveInvToDisk.Name = "Save inventory to disk";\r
352                 saveInvToDisk.Start();\r
353 \r
354                 netcom.Logout();\r
355             }\r
356         }\r
357         #endregion\r
358 \r
359         # region Update status\r
360 \r
361         void Parcels_ParcelProperties(object sender, ParcelPropertiesEventArgs e)\r
362         {\r
363             if (e.Result != ParcelResult.Single) return;\r
364             if (InvokeRequired)\r
365             {\r
366                 BeginInvoke(new MethodInvoker(() => Parcels_ParcelProperties(sender, e)));\r
367                 return;\r
368             }\r
369 \r
370             Parcel parcel = e.Parcel;\r
371 \r
372             tlblParcel.Text = parcel.Name;\r
373             tlblParcel.ToolTipText = parcel.Desc;\r
374 \r
375             if ((parcel.Flags & ParcelFlags.AllowFly) != ParcelFlags.AllowFly)\r
376                 icoNoFly.Visible = true;\r
377             else\r
378                 icoNoFly.Visible = false;\r
379 \r
380             if ((parcel.Flags & ParcelFlags.CreateObjects) != ParcelFlags.CreateObjects)\r
381                 icoNoBuild.Visible = true;\r
382             else\r
383                 icoNoBuild.Visible = false;\r
384 \r
385             if ((parcel.Flags & ParcelFlags.AllowOtherScripts) != ParcelFlags.AllowOtherScripts)\r
386                 icoNoScript.Visible = true;\r
387             else\r
388                 icoNoScript.Visible = false;\r
389 \r
390             if ((parcel.Flags & ParcelFlags.RestrictPushObject) == ParcelFlags.RestrictPushObject)\r
391                 icoNoPush.Visible = true;\r
392             else\r
393                 icoNoPush.Visible = false;\r
394 \r
395             if ((parcel.Flags & ParcelFlags.AllowDamage) == ParcelFlags.AllowDamage)\r
396                 icoHealth.Visible = true;\r
397             else\r
398                 icoHealth.Visible = false;\r
399 \r
400             if ((parcel.Flags & ParcelFlags.AllowVoiceChat) != ParcelFlags.AllowVoiceChat)\r
401                 icoNoVoice.Visible = true;\r
402             else\r
403                 icoNoVoice.Visible = false;\r
404         }\r
405 \r
406         private void RefreshStatusBar()\r
407         {\r
408             if (netcom.IsLoggedIn)\r
409             {\r
410                 tlblLoginName.Text = netcom.LoginOptions.FullName;\r
411                 tlblMoneyBalance.Text = client.Self.Balance.ToString();\r
412                 icoHealth.Text = client.Self.Health.ToString() + "%";\r
413 \r
414                 tlblRegionInfo.Text =\r
415                     client.Network.CurrentSim.Name +\r
416                     " (" + Math.Floor(client.Self.SimPosition.X).ToString() + ", " +\r
417                     Math.Floor(client.Self.SimPosition.Y).ToString() + ", " +\r
418                     Math.Floor(client.Self.SimPosition.Z).ToString() + ")";\r
419             }\r
420             else\r
421             {\r
422                 tlblLoginName.Text = "Offline";\r
423                 tlblMoneyBalance.Text = "0";\r
424                 icoHealth.Text = "0%";\r
425                 tlblRegionInfo.Text = "No Region";\r
426                 tlblParcel.Text = "No Parcel";\r
427 \r
428                 icoHealth.Visible = false;\r
429                 icoNoBuild.Visible = false;\r
430                 icoNoFly.Visible = false;\r
431                 icoNoPush.Visible = false;\r
432                 icoNoScript.Visible = false;\r
433                 icoNoVoice.Visible = false;\r
434             }\r
435         }\r
436 \r
437         private void RefreshWindowTitle()\r
438         {\r
439             StringBuilder sb = new StringBuilder();\r
440             sb.Append("Radegast - ");\r
441 \r
442             if (netcom.IsLoggedIn)\r
443             {\r
444                 sb.Append("[" + netcom.LoginOptions.FullName + "]");\r
445 \r
446                 if (instance.State.IsAway)\r
447                 {\r
448                     sb.Append(" - Away");\r
449                     if (instance.State.IsBusy) sb.Append(", Busy");\r
450                 }\r
451                 else if (instance.State.IsBusy)\r
452                 {\r
453                     sb.Append(" - Busy");\r
454                 }\r
455 \r
456                 if (instance.State.IsFollowing)\r
457                 {\r
458                     sb.Append(" - Following ");\r
459                     sb.Append(instance.State.FollowName);\r
460                 }\r
461             }\r
462             else\r
463             {\r
464                 sb.Append("Logged Out");\r
465             }\r
466 \r
467             this.Text = sb.ToString();\r
468             sb = null;\r
469         }\r
470 \r
471         private void InitializeStatusTimer()\r
472         {\r
473             statusTimer = new System.Timers.Timer(250);\r
474             statusTimer.SynchronizingObject = this;\r
475             statusTimer.Elapsed += new ElapsedEventHandler(statusTimer_Elapsed);\r
476         }\r
477 \r
478         private void statusTimer_Elapsed(object sender, ElapsedEventArgs e)\r
479         {\r
480             // Mono sometimes fires timer after is's disposed\r
481             try\r
482             {\r
483                 RefreshWindowTitle();\r
484                 RefreshStatusBar();\r
485             }\r
486             catch { }\r
487         }\r
488         #endregion\r
489 \r
490         #region Initialization, configuration, and key shortcuts\r
491         private void InitializeTabsConsole()\r
492         {\r
493             tabsConsole = new TabsConsole(instance);\r
494             tabsConsole.Dock = DockStyle.Fill;\r
495             toolStripContainer1.ContentPanel.Controls.Add(tabsConsole);\r
496         }\r
497 \r
498         private void frmMain_KeyDown(object sender, KeyEventArgs e)\r
499         {\r
500             // Ctl-Shift-H: Teleport Home\r
501             if (e.Modifiers == (Keys.Control | Keys.Shift) && e.KeyCode == Keys.H)\r
502             {\r
503                 e.Handled = e.SuppressKeyPress = true;\r
504                 tmnuTeleportHome.PerformClick();\r
505                 return;\r
506             }\r
507 \r
508             // Alt 1-8: Toggle various tabs\r
509             if (e.Modifiers == Keys.Alt)\r
510             {\r
511                 switch (e.KeyCode)\r
512                 {\r
513                     case Keys.D1:\r
514                         e.Handled = e.SuppressKeyPress = true;\r
515                         tabsConsole.Tabs["chat"].Select();\r
516                         break;\r
517 \r
518                     case Keys.D2:\r
519                         e.Handled = e.SuppressKeyPress = true;\r
520                         tbtnFriends.PerformClick();\r
521                         break;\r
522 \r
523                     case Keys.D3:\r
524                         e.Handled = e.SuppressKeyPress = true;\r
525                         tbtnGroups.PerformClick();\r
526                         break;\r
527 \r
528                     case Keys.D4:\r
529                         e.Handled = e.SuppressKeyPress = true;\r
530                         tbtnInventory.PerformClick();\r
531                         break;\r
532 \r
533                     case Keys.D5:\r
534                         e.Handled = e.SuppressKeyPress = true;\r
535                         tbtnSearch.PerformClick();\r
536                         break;\r
537 \r
538                     case Keys.D6:\r
539                         e.Handled = e.SuppressKeyPress = true;\r
540                         tbtnMap.PerformClick();\r
541                         break;\r
542 \r
543                     case Keys.D7:\r
544                         e.Handled = e.SuppressKeyPress = true;\r
545                         tbnObjects.PerformClick();\r
546                         break;\r
547 \r
548                     case Keys.D8:\r
549                         e.Handled = e.SuppressKeyPress = true;\r
550                         tbtnMedia.PerformClick();\r
551                         break;\r
552 \r
553                     case Keys.D9:\r
554                         e.Handled = e.SuppressKeyPress = true;\r
555                         tbtnVoice.PerformClick();\r
556                         break;\r
557                 }\r
558             }\r
559 \r
560             // ctrl-g, goto slurl\r
561             if (e.Control && e.KeyCode == Keys.G)\r
562             {\r
563                 if (!ProcessLink(Clipboard.GetText(), true))\r
564                     MapToCurrentLocation();\r
565 \r
566                 e.Handled = e.SuppressKeyPress = true;\r
567                 return;\r
568             }\r
569 \r
570             // ctrl-(shift)-tab for next/previous tab\r
571             if (e.Control && e.KeyCode == Keys.Tab)\r
572             {\r
573                 if (e.Shift)\r
574                 {\r
575                     TabConsole.SelectPreviousTab();\r
576                 }\r
577                 else\r
578                 {\r
579                     TabConsole.SelectNextTab();\r
580                 }\r
581                 e.Handled = e.SuppressKeyPress = true;\r
582                 return;\r
583             }\r
584         }\r
585 \r
586         private void frmMain_KeyUp(object sender, KeyEventArgs e)\r
587         {\r
588         }\r
589 \r
590         private void frmMain_Load(object sender, EventArgs e)\r
591         {\r
592             tabsConsole.SelectTab("login");\r
593             ResourceManager rm = Properties.Resources.ResourceManager;\r
594             ResourceSet set = rm.GetResourceSet(System.Globalization.CultureInfo.CurrentCulture, true, true);\r
595             System.Collections.IDictionaryEnumerator de = set.GetEnumerator();\r
596             while (de.MoveNext() == true)\r
597             {\r
598                 if (de.Entry.Value is Image)\r
599                 {\r
600                     Bitmap bitMap = de.Entry.Value as Bitmap;\r
601                     ResourceImages.Images.Add(bitMap);\r
602                     ImageNames.Add(de.Entry.Key.ToString());\r
603                 }\r
604             }\r
605             StartUpdateCheck(false);\r
606         }\r
607         #endregion\r
608 \r
609         #region Public methods\r
610 \r
611         private Dictionary<UUID, frmProfile> shownProfiles = new Dictionary<UUID, frmProfile>();\r
612 \r
613         public void ShowAgentProfile(string name, UUID agentID)\r
614         {\r
615             lock (shownProfiles)\r
616             {\r
617                 frmProfile profile = null;\r
618                 if (shownProfiles.TryGetValue(agentID, out profile))\r
619                 {\r
620                     profile.WindowState = FormWindowState.Normal;\r
621                     profile.Focus();\r
622                 }\r
623                 else\r
624                 {\r
625                     profile = new frmProfile(instance, name, agentID);\r
626 \r
627                     profile.Disposed += (object sender, EventArgs e) =>\r
628                         {\r
629                             lock (shownProfiles)\r
630                             {\r
631                                 frmProfile agentProfile = (frmProfile)sender;\r
632                                 if (shownProfiles.ContainsKey(agentProfile.AgentID))\r
633                                     shownProfiles.Remove(agentProfile.AgentID);\r
634                             }\r
635                         };\r
636 \r
637                     profile.Show();\r
638                     profile.Focus();\r
639                     shownProfiles.Add(agentID, profile);\r
640                 }\r
641             }\r
642         }\r
643 \r
644         private Dictionary<UUID, frmGroupInfo> shownGroupProfiles = new Dictionary<UUID, frmGroupInfo>();\r
645 \r
646         public void ShowGroupProfile(OpenMetaverse.Group group)\r
647         {\r
648             lock (shownGroupProfiles)\r
649             {\r
650                 frmGroupInfo profile = null;\r
651                 if (shownGroupProfiles.TryGetValue(group.ID, out profile))\r
652                 {\r
653                     profile.WindowState = FormWindowState.Normal;\r
654                     profile.Focus();\r
655                 }\r
656                 else\r
657                 {\r
658                     profile = new frmGroupInfo(instance, group);\r
659 \r
660                     profile.Disposed += (object sender, EventArgs e) =>\r
661                         {\r
662                             lock (shownGroupProfiles)\r
663                             {\r
664                                 frmGroupInfo groupProfile = (frmGroupInfo)sender;\r
665                                 if (shownGroupProfiles.ContainsKey(groupProfile.Group.ID))\r
666                                     shownGroupProfiles.Remove(groupProfile.Group.ID);\r
667                             }\r
668                         };\r
669 \r
670                     profile.Show();\r
671                     profile.Focus();\r
672                     shownGroupProfiles.Add(group.ID, profile);\r
673                 }\r
674             }\r
675         }\r
676 \r
677         public void ProcessLink(string link)\r
678         {\r
679             ProcessLink(link, false);\r
680         }\r
681 \r
682         public bool ProcessLink(string link, bool onlyMap)\r
683         {\r
684             if (!link.Contains("://"))\r
685             {\r
686                 link = "http://" + link;\r
687             }\r
688 \r
689             Regex r = new Regex(@"^(http://slurl.com/secondlife/|secondlife://)([^/]+)/(\d+)/(\d+)(/(\d+))?");\r
690             Match m = r.Match(link);\r
691 \r
692             if (m.Success)\r
693             {\r
694                 string region = HttpUtility.UrlDecode(m.Groups[2].Value);\r
695                 int x = int.Parse(m.Groups[3].Value);\r
696                 int y = int.Parse(m.Groups[4].Value);\r
697                 int z = 0;\r
698 \r
699                 if (m.Groups.Count > 5 && m.Groups[6].Value != String.Empty)\r
700                 {\r
701                     z = int.Parse(m.Groups[6].Value);\r
702                 }\r
703 \r
704                 MapTab.Select();\r
705                 WorldMap.DisplayLocation(region, x, y, z);\r
706                 return true;\r
707             }\r
708             else if (!onlyMap)\r
709             {\r
710                 System.Diagnostics.Process.Start(link);\r
711             }\r
712             return false;\r
713         }\r
714         #endregion\r
715 \r
716         #region Notifications\r
717         CircularList<Control> notifications = new CircularList<Control>();\r
718 \r
719         public Color NotificationBackground\r
720         {\r
721             get { return pnlDialog.BackColor; }\r
722         }\r
723 \r
724         void ResizeNotificationByControl(Control active)\r
725         {\r
726             int Width = active.Size.Width + 6;\r
727             int Height = notifications.Count > 1 ? active.Size.Height + 3 + btnDialogNextControl.Size.Height : active.Size.Height + 3;\r
728             pnlDialog.Size = new Size(Width, Height);\r
729             pnlDialog.Top = 0;\r
730             pnlDialog.Left = pnlDialog.Parent.ClientSize.Width - Width;\r
731 \r
732             btnDialogNextControl.Top = btnDialogNextControl.Parent.ClientSize.Height - btnDialogNextControl.Size.Height;\r
733             btnDialogNextControl.Left = btnDialogNextControl.Parent.ClientSize.Width - btnDialogNextControl.Size.Width;\r
734 \r
735             btnDialogNextControl.BringToFront();\r
736         }\r
737 \r
738         public void AddNotification(Control control)\r
739         {\r
740             if (InvokeRequired)\r
741             {\r
742                 BeginInvoke(new MethodInvoker(delegate()\r
743                 {\r
744                     AddNotification(control);\r
745                 }\r
746                 ));\r
747                 return;\r
748             }\r
749 \r
750             FormFlash.StartFlash(this);\r
751             pnlDialog.Visible = true;\r
752             pnlDialog.BringToFront();\r
753 \r
754             foreach (Control existing in notifications)\r
755             {\r
756                 existing.Visible = false;\r
757             }\r
758 \r
759             notifications.Add(control);\r
760             control.Visible = true;\r
761             control.Anchor = AnchorStyles.Top | AnchorStyles.Left;\r
762             control.Top = 3;\r
763             control.Left = 3;\r
764             pnlDialog.Controls.Add(control);\r
765             ResizeNotificationByControl(control);\r
766 \r
767             btnDialogNextControl.Visible = notifications.Count > 1;\r
768         }\r
769 \r
770         public void RemoveNotification(Control control)\r
771         {\r
772             pnlDialog.Controls.Remove(control);\r
773             notifications.Remove(control);\r
774             control.Dispose();\r
775 \r
776             if (notifications.HasNext)\r
777             {\r
778                 pnlDialog.Visible = true;\r
779                 Control active = notifications.Next;\r
780                 active.Visible = true;\r
781                 ResizeNotificationByControl(active);\r
782             }\r
783             else\r
784             {\r
785                 pnlDialog.Visible = false;\r
786             }\r
787 \r
788             btnDialogNextControl.Visible = notifications.Count > 1;\r
789         }\r
790 \r
791         private void btnDialogNextControl_Click(object sender, EventArgs e)\r
792         {\r
793             foreach (Control existing in notifications)\r
794             {\r
795                 existing.Visible = false;\r
796             }\r
797 \r
798             if (notifications.HasNext)\r
799             {\r
800                 pnlDialog.Visible = true;\r
801                 Control active = notifications.Next;\r
802                 active.Visible = true;\r
803                 ResizeNotificationByControl(active);\r
804             }\r
805             else\r
806             {\r
807                 pnlDialog.Visible = false;\r
808             }\r
809 \r
810         }\r
811         #endregion Notifications\r
812 \r
813         #region Menu click handlers\r
814 \r
815         private void tbtnTeleport_Click(object sender, EventArgs e)\r
816         {\r
817             (new frmTeleport(instance)).ShowDialog();\r
818         }\r
819 \r
820         private void tmnuStatusAway_Click(object sender, EventArgs e)\r
821         {\r
822             instance.State.SetAway(tmnuStatusAway.Checked);\r
823         }\r
824 \r
825         private void tmnuHelpReadme_Click(object sender, EventArgs e)\r
826         {\r
827             System.Diagnostics.Process.Start(Application.StartupPath + @"\Readme.txt");\r
828         }\r
829 \r
830         private void tmnuStatusBusy_Click(object sender, EventArgs e)\r
831         {\r
832             instance.State.SetBusy(tmnuStatusBusy.Checked);\r
833         }\r
834 \r
835         private void tmnuControlFly_Click(object sender, EventArgs e)\r
836         {\r
837             instance.State.SetFlying(tmnuControlFly.Checked);\r
838         }\r
839 \r
840         private void tmnuControlAlwaysRun_Click(object sender, EventArgs e)\r
841         {\r
842             instance.State.SetAlwaysRun(tmnuControlAlwaysRun.Checked);\r
843         }\r
844 \r
845         private void tmnuPrefs_Click(object sender, EventArgs e)\r
846         {\r
847             (new frmSettings(instance)).ShowDialog();\r
848         }\r
849 \r
850         private void tbtnAppearance_Click(object sender, EventArgs e)\r
851         {\r
852             client.Appearance.RequestSetAppearance(false);\r
853         }\r
854 \r
855         private void importObjectToolStripMenuItem_Click(object sender, EventArgs e)\r
856         {\r
857             PrimDeserializer.ImportFromFile(client);\r
858         }\r
859 \r
860         private void autopilotToolStripMenuItem_Click(object sender, EventArgs e)\r
861         {\r
862             if (ap == null)\r
863             {\r
864                 ap = new AutoPilot(client);\r
865                 /*\r
866                 ap.InsertWaypoint(new Vector3(66, 163, 21));\r
867                 ap.InsertWaypoint(new Vector3(66, 98, 21));\r
868 \r
869                 ap.InsertWaypoint(new Vector3(101, 98, 21));\r
870                 ap.InsertWaypoint(new Vector3(101, 45, 21));\r
871                 ap.InsertWaypoint(new Vector3(93, 27, 21));\r
872                 ap.InsertWaypoint(new Vector3(106, 12, 21));\r
873                 ap.InsertWaypoint(new Vector3(123, 24, 21));\r
874                 ap.InsertWaypoint(new Vector3(114, 45, 21));\r
875                 ap.InsertWaypoint(new Vector3(114, 98, 21));\r
876 \r
877                 ap.InsertWaypoint(new Vector3(130, 98, 21));\r
878                 ap.InsertWaypoint(new Vector3(130, 163, 21));\r
879                  **/\r
880                 ap.InsertWaypoint(new Vector3(64, 68, 21));\r
881                 ap.InsertWaypoint(new Vector3(65, 20, 21));\r
882                 ap.InsertWaypoint(new Vector3(33, 23, 21));\r
883                 ap.InsertWaypoint(new Vector3(17, 39, 21));\r
884                 ap.InsertWaypoint(new Vector3(17, 62, 21));\r
885 \r
886 \r
887             }\r
888             if (AutoPilotActive)\r
889             {\r
890                 AutoPilotActive = false;\r
891                 ap.Stop();\r
892             }\r
893             else\r
894             {\r
895                 AutoPilotActive = true;\r
896                 ap.Start();\r
897             }\r
898 \r
899         }\r
900 \r
901         private void cleanCacheToolStripMenuItem_Click(object sender, EventArgs e)\r
902         {\r
903             client.Assets.Cache.Clear();\r
904         }\r
905 \r
906         private void rebakeTexturesToolStripMenuItem_Click(object sender, EventArgs e)\r
907         {\r
908             client.Appearance.RequestSetAppearance(true);\r
909         }\r
910 \r
911         public void MapToCurrentLocation()\r
912         {\r
913             if (MapTab != null && client.Network.Connected)\r
914             {\r
915                 MapTab.Select();\r
916                 WorldMap.DisplayLocation(client.Network.CurrentSim.Name,\r
917                     (int)client.Self.SimPosition.X,\r
918                     (int)client.Self.SimPosition.Y,\r
919                     (int)client.Self.SimPosition.Z);\r
920             }\r
921         }\r
922 \r
923         private void standToolStripMenuItem_Click(object sender, EventArgs e)\r
924         {\r
925             client.Self.Stand();\r
926         }\r
927 \r
928         private void groundSitToolStripMenuItem_Click(object sender, EventArgs e)\r
929         {\r
930             client.Self.SitOnGround();\r
931         }\r
932 \r
933         private void newWindowToolStripMenuItem_Click(object sender, EventArgs e)\r
934         {\r
935             try { System.Diagnostics.Process.Start(Application.ExecutablePath); }\r
936             catch (Exception) { }\r
937         }\r
938 \r
939         private void tmnuExit_Click(object sender, EventArgs e)\r
940         {\r
941             Close();\r
942         }\r
943 \r
944         private void tlblRegionInfo_Click(object sender, EventArgs e)\r
945         {\r
946             if (WorldMap != null && client.Network.Connected)\r
947             {\r
948                 MapTab.Select();\r
949             }\r
950         }\r
951 \r
952         private void scriptEditorToolStripMenuItem_Click(object sender, EventArgs e)\r
953         {\r
954             ScriptEditor se = new ScriptEditor(instance);\r
955             se.Dock = DockStyle.Fill;\r
956             se.ShowDetached();\r
957         }\r
958 \r
959         private void tmnuSetHome_Click(object sender, EventArgs e)\r
960         {\r
961             client.Self.SetHome();\r
962         }\r
963 \r
964         private void tmnuCreateLandmark_Click(object sender, EventArgs e)\r
965         {\r
966             string location = string.Format(", {0} ({1}, {2}, {3})",\r
967                 client.Network.CurrentSim.Name,\r
968                 (int)client.Self.SimPosition.X,\r
969                 (int)client.Self.SimPosition.Y,\r
970                 (int)client.Self.SimPosition.Z\r
971                 );\r
972 \r
973             string name = tlblParcel.Text;\r
974             int maxLen = 63 - location.Length;\r
975 \r
976             if (name.Length > maxLen)\r
977                 name = name.Substring(0, maxLen);\r
978 \r
979             name += location;\r
980 \r
981             client.Inventory.RequestCreateItem(\r
982                 client.Inventory.FindFolderForType(AssetType.Landmark),\r
983                 name,\r
984                 tlblParcel.ToolTipText,\r
985                 AssetType.Landmark,\r
986                 UUID.Random(),\r
987                 InventoryType.Landmark,\r
988                 PermissionMask.All,\r
989                 (bool success, InventoryItem item) =>\r
990                 {\r
991                     if (success)\r
992                     {\r
993                         BeginInvoke(new MethodInvoker(() =>\r
994                             {\r
995                                 Landmark ln = new Landmark(instance, (InventoryLandmark)item);\r
996                                 ln.Dock = DockStyle.Fill;\r
997                                 ln.Detached = true;\r
998                             }));\r
999                     }\r
1000                 }\r
1001             );\r
1002         }\r
1003 \r
1004         private void tmnuTeleportHome_Click(object sender, EventArgs e)\r
1005         {\r
1006             if (WorldMap != null)\r
1007             {\r
1008                 WorldMap.GoHome();\r
1009             }\r
1010         }\r
1011 \r
1012         private TimeZoneInfo SLTime;\r
1013 \r
1014         private void GetSLTimeZone()\r
1015         {\r
1016             try\r
1017             {\r
1018                 foreach (TimeZoneInfo tz in TimeZoneInfo.GetSystemTimeZones())\r
1019                 {\r
1020                     if (tz.Id == "Pacific Standard Time" || tz.Id == "America/Los_Angeles")\r
1021                     {\r
1022                         SLTime = tz;\r
1023                         break;\r
1024                     }\r
1025                 }\r
1026             }\r
1027             catch (Exception) { }\r
1028         }\r
1029 \r
1030         private void timerWorldClock_Tick(object sender, EventArgs e)\r
1031         {\r
1032             DateTime now;\r
1033             try\r
1034             {\r
1035                 if (SLTime != null)\r
1036                     now = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, SLTime);\r
1037                 else\r
1038                     now = DateTime.UtcNow.AddHours(-7);\r
1039             }\r
1040             catch (Exception)\r
1041             {\r
1042                 now = DateTime.UtcNow.AddHours(-7);\r
1043             }\r
1044             lblTime.Text = now.ToString("h:mm tt", System.Globalization.CultureInfo.InvariantCulture);\r
1045         }\r
1046 \r
1047         private void reportBugsToolStripMenuItem_Click(object sender, EventArgs e)\r
1048         {\r
1049             ProcessLink("http://jira.openmetaverse.org/browse/RAD");\r
1050         }\r
1051 \r
1052         private void aboutRadegastToolStripMenuItem_Click(object sender, EventArgs e)\r
1053         {\r
1054             (new frmAbout(instance)).ShowDialog();\r
1055         }\r
1056 \r
1057         #region Update Checking\r
1058         private UpdateChecker updateChecker = null;\r
1059         private bool ManualUpdateCheck = false;\r
1060 \r
1061         public void StartUpdateCheck(bool userInitiated)\r
1062         {\r
1063             ManualUpdateCheck = userInitiated;\r
1064 \r
1065             if (updateChecker != null)\r
1066             {\r
1067                 if (ManualUpdateCheck)\r
1068                     tabsConsole.DisplayNotificationInChat("Update check already in progress.");\r
1069                 return;\r
1070             }\r
1071 \r
1072             if (ManualUpdateCheck)\r
1073                 tabsConsole.DisplayNotificationInChat("Checking for updates...", ChatBufferTextStyle.StatusBlue);\r
1074             updateChecker = new UpdateChecker();\r
1075             updateChecker.OnUpdateInfoReceived += new UpdateChecker.UpdateInfoCallback(OnUpdateInfoReceived);\r
1076             updateChecker.StartCheck();\r
1077         }\r
1078 \r
1079         private void checkForUpdatesToolStripMenuItem_Click(object sender, EventArgs e)\r
1080         {\r
1081             tabsConsole.SelectTab("chat");\r
1082             StartUpdateCheck(true);\r
1083         }\r
1084 \r
1085         void OnUpdateInfoReceived(object sender, UpdateCheckerArgs e)\r
1086         {\r
1087             if (InvokeRequired)\r
1088             {\r
1089                 BeginInvoke(new MethodInvoker(() => OnUpdateInfoReceived(sender, e)));\r
1090                 return;\r
1091             }\r
1092 \r
1093             if (!e.Success)\r
1094             {\r
1095                 if (ManualUpdateCheck)\r
1096                     tabsConsole.DisplayNotificationInChat("Error: Failed connecting to the update site.", ChatBufferTextStyle.StatusBlue);\r
1097             }\r
1098             else\r
1099             {\r
1100                 if (!ManualUpdateCheck && e.Info.DisplayMOTD)\r
1101                 {\r
1102                     tabsConsole.DisplayNotificationInChat(e.Info.MOTD, ChatBufferTextStyle.StatusBlue);\r
1103                 }\r
1104 \r
1105                 if (e.Info.UpdateAvailable)\r
1106                 {\r
1107                     tabsConsole.DisplayNotificationInChat("New version available at " + e.Info.DownloadSite, ChatBufferTextStyle.Alert);\r
1108                 }\r
1109                 else\r
1110                 {\r
1111                     if (ManualUpdateCheck)\r
1112                         tabsConsole.DisplayNotificationInChat("Your version is up to date.", ChatBufferTextStyle.StatusBlue);\r
1113                 }\r
1114             }\r
1115 \r
1116             updateChecker.Dispose();\r
1117             updateChecker = null;\r
1118         }\r
1119         #endregion\r
1120 \r
1121         private void ToggleHidden(string tabName)\r
1122         {\r
1123             if (!tabsConsole.TabExists(tabName)) return;\r
1124 \r
1125             RadegastTab tab = tabsConsole.Tabs[tabName];\r
1126 \r
1127             if (tab.Hidden)\r
1128             {\r
1129                 tab.Show();\r
1130             }\r
1131             else\r
1132             {\r
1133                 if (!tab.Selected)\r
1134                 {\r
1135                     tab.Select();\r
1136                 }\r
1137                 else\r
1138                 {\r
1139                     tab.Hide();\r
1140                 }\r
1141             }\r
1142         }\r
1143 \r
1144         private void tbtnFriends_Click(object sender, EventArgs e)\r
1145         {\r
1146             ToggleHidden("friends");\r
1147         }\r
1148 \r
1149         private void tbtnInventory_Click(object sender, EventArgs e)\r
1150         {\r
1151             ToggleHidden("inventory");\r
1152         }\r
1153 \r
1154         private void tbtnSearch_Click(object sender, EventArgs e)\r
1155         {\r
1156             ToggleHidden("search");\r
1157         }\r
1158 \r
1159         private void tbtnGroups_Click(object sender, EventArgs e)\r
1160         {\r
1161             ToggleHidden("groups");\r
1162         }\r
1163 \r
1164         private void tbtnVoice_Click(object sender, EventArgs e)\r
1165         {\r
1166             ToggleHidden("voice");\r
1167         }\r
1168 \r
1169         private void tbtnMedia_Click(object sender, EventArgs e)\r
1170         {\r
1171             if (tabsConsole.TabExists("media"))\r
1172             {\r
1173                 ToggleHidden("media");\r
1174             }\r
1175             else\r
1176             {\r
1177                 RadegastTab tab = tabsConsole.AddTab("media", "Media", mediaConsole);\r
1178                 tab.AllowClose = false;\r
1179                 tab.AllowHide = true;\r
1180                 tab.Select();\r
1181             }\r
1182         }\r
1183 \r
1184         private void tbnObjects_Click(object sender, EventArgs e)\r
1185         {\r
1186             if (tabsConsole.TabExists("objects"))\r
1187             {\r
1188                 RadegastTab tab = tabsConsole.Tabs["objects"];\r
1189                 if (!tab.Selected)\r
1190                 {\r
1191                     tab.Select();\r
1192                     ((ObjectsConsole)tab.Control).RefreshObjectList();\r
1193                 }\r
1194                 else\r
1195                 {\r
1196                     tab.Close();\r
1197                 }\r
1198             }\r
1199             else\r
1200             {\r
1201                 RadegastTab tab = tabsConsole.AddTab("objects", "Objects", new ObjectsConsole(instance));\r
1202                 tab.AllowClose = true;\r
1203                 tab.AllowDetach = true;\r
1204                 tab.Visible = true;\r
1205                 tab.AllowHide = false;\r
1206                 tab.Select();\r
1207                 ((ObjectsConsole)tab.Control).RefreshObjectList();\r
1208             }\r
1209         }\r
1210 \r
1211         private void tbtnMap_Click(object sender, EventArgs e)\r
1212         {\r
1213             ToggleHidden("map");\r
1214             if (!MapTab.Hidden)\r
1215                 MapToCurrentLocation();\r
1216         }\r
1217 \r
1218         private void disconnectToolStripMenuItem_Click(object sender, EventArgs e)\r
1219         {\r
1220             if (client.Network.Connected)\r
1221                 client.Network.RequestLogout();\r
1222         }\r
1223 \r
1224         private void reconnectToolStripMenuItem_Click(object sender, EventArgs e)\r
1225         {\r
1226             instance.Reconnect();\r
1227         }\r
1228 \r
1229         private frmKeyboardShortcuts keyboardShortcutsForm = null;\r
1230         private void keyboardShortcutsToolStripMenuItem_Click(object sender, EventArgs e)\r
1231         {\r
1232             if (keyboardShortcutsForm != null)\r
1233             {\r
1234                 keyboardShortcutsForm.Focus();\r
1235             }\r
1236             else\r
1237             {\r
1238                 keyboardShortcutsForm = new frmKeyboardShortcuts(instance);\r
1239 \r
1240                 keyboardShortcutsForm.Disposed += (object senderx, EventArgs ex) =>\r
1241                     {\r
1242                         if (components != null)\r
1243                         {\r
1244                             components.Remove(keyboardShortcutsForm);\r
1245                         }\r
1246                         keyboardShortcutsForm = null;\r
1247                     };\r
1248 \r
1249                 keyboardShortcutsForm.Show(this);\r
1250                 keyboardShortcutsForm.Top = Top + 100;\r
1251                 keyboardShortcutsForm.Left = Left + 100;\r
1252 \r
1253                 if (components != null)\r
1254                 {\r
1255                     components.Add(keyboardShortcutsForm);\r
1256                 }\r
1257             }\r
1258         }\r
1259 \r
1260         // Menu item for testing out stuff\r
1261         private void testToolStripMenuItem_Click(object sender, EventArgs e)\r
1262         {\r
1263             foreach (KeyValuePair<UUID, string> kvp in Sounds.ToDictionary())\r
1264             {\r
1265                 client.Assets.RequestAsset(kvp.Key, AssetType.Sound, true, (AssetDownload transfer, Asset asset) =>\r
1266                     {\r
1267                         System.Console.WriteLine("Sound '{0}' download success: {1}", transfer.AssetID, transfer.Success);\r
1268                     }\r
1269                 );\r
1270             }\r
1271         }\r
1272 \r
1273         #endregion\r
1274     }\r
1275 }