2using System.Collections.Generic;
4using System.Reflection;
5using System.Security.Cryptography;
7using System.Threading.Tasks;
9using System.Xml.Schema;
12using System.Windows.Controls;
13using System.Windows.Documents;
14using System.Windows.Input;
15using System.Windows.Media;
38 private static readonly RandomNumberGenerator rnd = RandomNumberGenerator.Create();
41 private readonly Dictionary<string, Consolidator> threads =
new Dictionary<string, Consolidator>();
43 private DateTime timer = DateTime.MinValue;
45 private bool consolidate =
true;
56 double w = this.ReceivedColumn.Width * 2;
57 this.FromColumn.Width = w;
58 this.ContentColumn.Width -= w;
60 this.timer =
MainWindow.Scheduler.Add(DateTime.Now.AddMinutes(1),
this.MucSelfPing,
null);
63 this.DataContext =
this;
66 public bool Muc => this.muc;
68 public bool Consolidate
70 get => this.consolidate;
71 set => this.consolidate = value;
74 internal static void InitEmojis()
76 if (emoji1_24x24 is
null)
78 string Folder = Assembly.GetExecutingAssembly().Location;
79 if (
string.IsNullOrEmpty(Folder))
80 Folder = AppDomain.CurrentDomain.BaseDirectory;
84 Path.Combine(Path.GetDirectoryName(Folder),
"Graphics",
"Emoji1.zip"),
85 Path.Combine(
MainWindow.AppDataFolder,
"Graphics"));
91 get {
return emoji1_24x24; }
96 if (this.timer > DateTime.MinValue)
99 this.timer = DateTime.MinValue;
107 private void UserControl_SizeChanged(
object Sender, SizeChangedEventArgs e)
109 if (this.ChatListView.View is GridView GridView)
111 GridView.Columns[2].Width = Math.Max(this.ActualWidth - GridView.Columns[0].ActualWidth -
112 GridView.Columns[1].ActualWidth - GridView.Columns[3].ActualWidth -
113 SystemParameters.VerticalScrollBarWidth - 8, 10);
116 this.Input.Width = Math.Max(this.ActualWidth - this.SendButton.ActualWidth - 16, 10);
128 XmlEntitiesOnly =
true
136 TableCellRowBackgroundColor1 =
"#20404040",
137 TableCellRowBackgroundColor2 =
"#10808080"
141 private async
void Send_Click(
object Sender, RoutedEventArgs e)
145 string Msg = this.Input.Text;
148 this.Input.Text =
string.Empty;
153 byte[] Bin =
new byte[16];
160 ThreadId = Convert.ToBase64String(Bin);
163 ThreadId =
string.Empty;
165 MarkdownDocument Markdown = await this.ChatMessageTransmitted(Msg, ThreadId);
166 await this.node.SendChatMessage(Msg, ThreadId, Markdown);
174 public async Task<MarkdownDocument> ChatMessageTransmitted(
string Message,
string ThreadId)
178 if (Message.IndexOf(
'|') >= 0)
181 int c = this.ChatListView.Items.Count;
184 this.ChatListView.Items[c - 1] is
ChatItem Item &&
185 Item.
Type == ChatItemType.Transmitted &&
186 string.IsNullOrWhiteSpace(Item.From) &&
187 (DateTime.Now - Item.LastUpdated).TotalSeconds < 10 &&
188 (s = Item.Message).IndexOf(
'|') >= 0)
192 if (!s.EndsWith(
"\n"))
193 s += Environment.NewLine;
197 await Item.Update(s, Markdown);
198 this.ChatListView.Items.Refresh();
199 this.ChatListView.ScrollIntoView(Item);
226 this.AddItem(ChatItemType.Transmitted, DateTime.Now, Message,
string.Empty, Markdown, ThreadId, Colors.Black, Colors.Honeydew);
231 private async
void AddItem(ChatItemType Type, DateTime Timestamp,
string Message,
string From,
MarkdownDocument Markdown,
string ThreadId, Color FgColor, Color BgColor)
235 if (this.muc && !
string.IsNullOrEmpty(ThreadId))
239 case ChatItemType.Transmitted:
249 if (!this.threads.ContainsKey(ThreadId))
253 Tag =
new ConsolidationTag()
259 case ChatItemType.Received:
262 if (!this.consolidate)
267 if (!this.threads.TryGetValue(ThreadId, out Consolidation))
271 ConsolidationTag Rec = (ConsolidationTag)Consolidation.
Tag;
274 await Consolidation.
Add(
ChatItem.GetShortFrom(From), Markdown);
276 Rec.UpdateTP =
MainWindow.Scheduler.Add(DateTime.Now.AddMilliseconds(100), async (_) =>
278 StringBuilder sb =
new StringBuilder();
284 foreach (
string Source
in await Consolidation.
GetSources())
298 if (Added = (Rec.Item is
null))
299 Rec.Item = await
ChatItem.CreateAsync(Type, Timestamp,
string.Empty,
string.Empty,
null, ThreadId, FgColor, BgColor);
301 Rec.Item.From = sb.ToString();
302 await Rec.Item.Update(Message, Markdown);
305 Rec.ListViewIndex =
this.AddListItem(Rec.Item);
307 this.ChatListView.Items[Rec.ListViewIndex] =
this.CreateItem(Rec.Item);
316 this.AddListItem(await
ChatItem.CreateAsync(Type, Timestamp, Message, From, Markdown, ThreadId, FgColor, BgColor));
324 private class ConsolidationTag
327 public DateTime UpdateTP = DateTime.MinValue;
328 public int ListViewIndex;
331 private int AddListItem(
ChatItem Item)
333 ListViewItem ListViewItem = this.CreateItem(Item);
334 int Index = this.ChatListView.Items.Add(ListViewItem);
335 this.ChatListView.ScrollIntoView(ListViewItem);
340 private ListViewItem CreateItem(
ChatItem Item)
342 return new ListViewItem()
347 Margin =
new Thickness(0)
351 public async Task ChatMessageReceived(
string Message,
string From,
string ThreadId,
bool IsMarkdown, DateTime Timestamp,
MainWindow MainWindow)
357 if (Message.IndexOf(
'|') >= 0)
359 int c = this.ChatListView.Items.Count;
362 this.ChatListView.Items[c - 1] is
ChatItem Item &&
363 Item.
Type == ChatItemType.Received &&
365 (DateTime.Now - Item.
LastUpdated).TotalSeconds < 10 &&
368 Item.Append(Message, this.ChatListView,
MainWindow);
387 this.AddItem(ChatItemType.Received, Timestamp, Message, From, Markdown, ThreadId, Colors.Black, Colors.AliceBlue);
388 return Task.CompletedTask;
392 private void UserControl_GotFocus(
object Sender, RoutedEventArgs e)
397 public void NewButton_Click(
object Sender, RoutedEventArgs e)
399 this.ChatListView.Items.Clear();
402 public void SaveButton_Click(
object Sender, RoutedEventArgs e)
404 this.SaveAsButton_Click(Sender, e);
407 public void SaveAsButton_Click(
object Sender, RoutedEventArgs e)
409 SaveFileDialog Dialog =
new SaveFileDialog()
412 CheckPathExists =
true,
413 CreatePrompt =
false,
415 Filter =
"XML Files (*.xml)|*.xml|HTML Files (*.html,*.htm)|*.html,*.htm|All Files (*.*)|*.*",
416 Title =
"Save chat session"
419 bool? Result = Dialog.ShowDialog(
MainWindow.FindWindow(
this));
421 if (Result.HasValue && Result.Value)
425 if (Dialog.FilterIndex == 2)
427 StringBuilder Xml =
new StringBuilder();
433 string Html =
XSL.
Transform(Xml.ToString(), chatToHtml);
435 File.WriteAllText(Dialog.FileName, Html, System.Text.Encoding.UTF8);
439 using (FileStream f = File.Create(Dialog.FileName))
450 MessageBox.Show(
MainWindow.FindWindow(
this), ex.Message,
"Unable to save file.", MessageBoxButton.OK, MessageBoxImage.Error);
455 private static readonly XslCompiledTransform chatToHtml =
XSL.
LoadTransform(
"Waher.Client.WPF.Transforms.ChatToHTML.xslt");
456 private static readonly XmlSchema schema =
XSL.
LoadSchema(
"Waher.Client.WPF.Schema.Chat.xsd");
457 private const string chatNamespace =
"http://waher.se/Schema/Chat.xsd";
458 private const string chatRoot =
"Chat";
460 private void SaveAsXml(XmlWriter w)
462 w.WriteStartElement(chatRoot, chatNamespace);
465 foreach (ListViewItem Item
in this.ChatListView.Items)
487 public async
void OpenButton_Click(
object Sender, RoutedEventArgs e)
491 OpenFileDialog Dialog =
new OpenFileDialog()
494 CheckFileExists =
true,
495 CheckPathExists =
true,
497 Filter =
"XML Files (*.xml)|*.xml|All Files (*.*)|*.*",
500 Title =
"Open chat session"
503 bool? Result = Dialog.ShowDialog(
MainWindow.FindWindow(
this));
505 if (Result.HasValue && Result.Value)
507 XmlDocument Xml =
new XmlDocument()
509 PreserveWhitespace =
true
511 Xml.Load(Dialog.FileName);
513 await this.Load(Xml, Dialog.FileName);
519 MessageBox.Show(ex.Message,
"Unable to load file.", MessageBoxButton.OK, MessageBoxImage.Error);
523 public async Task Load(XmlDocument Xml,
string FileName)
528 Color ForegroundColor;
529 Color BackgroundColor;
533 XSL.
Validate(FileName, Xml, chatRoot, chatNamespace, schema);
535 this.ChatListView.Items.Clear();
537 bool PrevMuc = this.muc;
538 this.muc =
XML.
Attribute(Xml.DocumentElement,
"muc",
this.muc);
540 if (this.muc != PrevMuc)
544 double w = this.ReceivedColumn.Width * 2;
545 this.FromColumn.Width = w;
546 this.ContentColumn.Width -= w;
550 this.ContentColumn.Width += this.FromColumn.Width;
551 this.FromColumn.Width = 0;
555 foreach (XmlNode N
in Xml.DocumentElement.ChildNodes)
561 if (!Enum.TryParse(E.LocalName, out ChatItemType Type))
564 Timestamp =
XML.
Attribute(E,
"timestamp", DateTime.MinValue);
570 case ChatItemType.Received:
571 ForegroundColor = Colors.Black;
572 BackgroundColor = Colors.AliceBlue;
575 case ChatItemType.Transmitted:
576 ForegroundColor = Colors.Black;
577 BackgroundColor = Colors.Honeydew;
580 case ChatItemType.Event:
581 ForegroundColor = EventFgColor;
582 BackgroundColor = EventBgColor;
598 this.AddItem(Type, Timestamp, E.InnerText, From, Markdown, ThreadId, ForegroundColor, BackgroundColor);
602 private void Input_PreviewKeyDown(
object Sender, KeyEventArgs e)
604 if (e.Key == Key.Enter)
606 if (!Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && !Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
608 this.Send_Click(Sender, e);
614 private void ChatListView_PreviewMouseLeftButtonUp(
object Sender, MouseButtonEventArgs e)
616 if (this.ChatListView.SelectedItem is ListViewItem ListViewItem &&
617 ListViewItem.Content is
ChatItem Item)
619 this.Input.Text = Item.Message;
622 else if (e.OriginalSource is Run Run)
624 this.Input.Text = Run.Text;
627 else if (e.OriginalSource is TextBlock TextBlock)
629 this.Input.Text = TextBlock.Text;
634 private void Hyperlink_Click(
object Sender, RoutedEventArgs e)
636 string Uri = ((Hyperlink)Sender).NavigateUri.ToString();
637 System.Diagnostics.Process.Start(Uri);
640 public void Event(
string Message,
string From,
string ThreadId)
642 this.
Event(Message, From,
null, DateTime.Now, ThreadId);
645 public void Event(
string Message,
string From,
MarkdownDocument Markdown, DateTime Timestamp,
string ThreadId)
649 this.AddItem(ChatItemType.Event, Timestamp, Message, From, Markdown, ThreadId, EventFgColor, EventBgColor);
650 return Task.CompletedTask;
654 public static readonly Color EventFgColor = Color.FromRgb(32, 32, 32);
655 public static readonly Color EventBgColor = Color.FromRgb(240, 240, 240);
657 private void MucSelfPing(
object State)
659 this.timer =
MainWindow.Scheduler.Add(DateTime.Now.AddMinutes(1),
this.MucSelfPing,
null);
667 if (e.StanzaError is Networking.XMPP.StanzaErrors.NotAcceptableException)
669 RoomNode.MucClient.EnterRoom(RoomNode.RoomId, RoomNode.Domain, RoomNode.NickName, RoomNode.Password, (sender2, e2) =>
672 RoomNode.MucClient.SetPresence(RoomNode.RoomId, RoomNode.Domain, RoomNode.NickName, Networking.XMPP.Availability.Chat, null, null);
674 return Task.CompletedTask;
679 return Task.CompletedTask;
684 public bool ContainsThread(
string ThreadId)
686 return this.threads.ContainsKey(ThreadId);
Represents one item in a chat.
ChatItemType Type
Chat item type.
string From
Who sent the message.
DateTime Timestamp
Timestamp of item.
DateTime LastUpdated
Timestamp when item was last updated.
string FromStr
Nick-name of sender.
Interaction logic for ChatView.xaml
void InitializeComponent()
InitializeComponent
Interaction logic for xaml
Color BackgroundColor
Background color
Color ForegroundColor
Foreground color
Represents a room hosted by a Multi-User Chat service.
Abstract base class for tree nodes in the connection view.
virtual void ViewClosed()
Method called when the view has been closed.
Helps with parsing of commong data types.
static string Encode(bool x)
Encodes a Boolean for use in XML and other formats.
Provides emojis from Emoji One (http://emojione.com/) stored as local files.
const string FileExtensionPng
png
Consolidates Markdown from multiple sources, sharing the same thread.
object Tag
External tag object that can be tagged to the object by its owner.
async Task< string[]> GetSources()
Consolidated sources.
async Task< string > GenerateMarkdownAsync()
Generates consolidated markdown from all sources.
Task< bool > Add(string Source, MarkdownDocument Markdown)
Adds incoming markdown information.
Contains a markdown document. This markdown document class supports original markdown,...
MarkdownSettings Settings
Markdown settings.
Type[] TransparentExceptionTypes
If an exception is thrown when processing script in markdown, and the exception is of any of these ty...
static Task< MarkdownDocument > CreateAsync(string MarkdownText, params Type[] TransparentExceptionTypes)
Contains a markdown document. This markdown document class supports original markdown,...
Contains settings that the Markdown parser uses to customize its behavior.
Contains settings that the HTML export uses to customize HTML output.
Contains settings that the XAML export uses to customize XAML output.
Helps with common XML-related tasks.
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
static string Encode(string s)
Encodes a string for use in XML.
static XmlWriterSettings WriterSettings(bool Indent, bool OmitXmlDeclaration)
Gets an XML writer settings object.
Static class managing loading of XSL resources stored as embedded resources or in content files.
static XmlSchema LoadSchema(string ResourceName)
Loads an XML schema from an embedded resource.
static XslCompiledTransform LoadTransform(string ResourceName)
Loads an XSL transformation from an embedded resource.
static string Transform(string XML, XslCompiledTransform Transform)
Transforms an XML document using an XSL transform.
static void Validate(string ObjectID, XmlDocument Xml, params XmlSchema[] Schemas)
Validates an XML document given a set of XML schemas.
Class representing an event.
Static class managing the application event log. Applications and services log events on this static ...
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Class managing a script expression.
Interface for tab view user controls in the client.
Emoji1SourceFileType
What source files to use when displaying emoji.