using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using XIVChatCommon; namespace XIVChat_Desktop { /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow : INotifyPropertyChanged { #region commands private void AlwaysTrue_CanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = true; } public static readonly RoutedUICommand EditTab = new RoutedUICommand( "EditTab", "EditTab", typeof(MainWindow) ); private void EditTab_OnExecuted(object sender, ExecutedRoutedEventArgs e) { if (!(e.Parameter is Tab tab)) { return; } new ManageTab(this, tab).Show(); } public static readonly RoutedUICommand DeleteTab = new RoutedUICommand( "DeleteTab", "DeleteTab", typeof(MainWindow) ); private void DeleteTab_OnExecuted(object sender, ExecutedRoutedEventArgs e) { if (!(e.Parameter is Tab tab)) { return; } this.App.Config.Tabs.Remove(tab); this.App.Config.Save(); } public static readonly RoutedUICommand AddTab = new RoutedUICommand( "AddTab", "AddTab", typeof(MainWindow) ); private void AddTab_OnExecuted(object sender, ExecutedRoutedEventArgs e) { new ManageTab(this, null).Show(); } public static readonly RoutedUICommand ManageTabs = new RoutedUICommand( "ManageTabs", "ManageTabs", typeof(MainWindow) ); private void ManageTabs_OnExecuted(object sender, ExecutedRoutedEventArgs e) { new ManageTabs(this).Show(); } #endregion public App App => (App)Application.Current; public List Messages { get; } = new List(); public ObservableCollection FriendList { get; } = new ObservableCollection(); private int historyIndex = -1; private int HistoryIndex { get => this.historyIndex; set { var idx = Math.Min(this.History.Count - 1, Math.Max(-1, value)); this.historyIndex = idx; } } private int ReverseHistoryIndex => this.HistoryIndex == -1 ? -1 : Math.Max(-1, this.History.Count - this.HistoryIndex - 1); private string? HistoryBuffer { get; set; } private List History { get; } = new List(); public string InputPlaceholder => this.App.Connection?.Available == true ? "Send a message..." : "Chat is currently unavailable"; public MainWindow() { this.InitializeComponent(); this.DataContext = this; } private T? FindElementByName(DependencyObject element, string sChildName) where T : FrameworkElement { T? childElement = null; var nChildCount = VisualTreeHelper.GetChildrenCount(element); for (int i = 0; i < nChildCount; i++) { if (!(VisualTreeHelper.GetChild(element, i) is FrameworkElement child)) { continue; } if (child is T t && child.Name.Equals(sChildName)) { childElement = t; break; } childElement = this.FindElementByName(child, sChildName); if (childElement != null) { break; } } return childElement; } public void ClearAllMessages() { this.Messages.Clear(); foreach (var tab in this.App.Config.Tabs) { tab.ClearMessages(); } } public void AddSystemMessage(string content) { var message = new ServerMessage( DateTime.UtcNow, 0, new byte[0], Encoding.UTF8.GetBytes(content), new List { new TextChunk(content) { Foreground = 0xb38cffff, }, } ); this.AddMessage(message); } private int lastSequence = -1; private int insertAt; public void AddReversedChunk(ServerMessage[] messages, int sequence) { if (sequence != this.lastSequence) { this.lastSequence = sequence; this.insertAt = this.Messages.Count; } // detect if scroller is at the bottom var scroller = this.FindElementByName(this.Tabs, "scroller"); var wasAtBottom = Math.Abs(scroller!.VerticalOffset - scroller.ScrollableHeight) < .0001; // add messages to main list this.Messages.InsertRange(this.insertAt, messages); // add message to each tab if the filter allows for it foreach (var tab in this.App.Config.Tabs) { tab.AddReversedChunk(messages, sequence, this.App.Config); } var diff = this.Messages.Count - this.App.Config.LocalBacklogMessages; if (diff > 0) { this.Messages.RemoveRange(0, (int)diff); } // scroll to the bottom if previously at the bottom if (wasAtBottom) { scroller.ScrollToBottom(); } } public void AddMessage(ServerMessage message) { // detect if scroller is at the bottom var scroller = this.FindElementByName(this.Tabs, "scroller"); var wasAtBottom = Math.Abs(scroller!.VerticalOffset - scroller.ScrollableHeight) < .0001; // add message to main list this.Messages.Add(message); // add message to each tab if the filter allows for it foreach (var tab in this.App.Config.Tabs) { tab.AddMessage(message, this.App.Config); } var diff = this.Messages.Count - this.App.Config.LocalBacklogMessages; if (diff > 0) { this.Messages.RemoveRange(0, (int)diff); } // scroll to the bottom if previously at the bottom if (wasAtBottom) { scroller.ScrollToBottom(); } } private void Connect_Click(object sender, RoutedEventArgs e) { new ConnectDialog(this).ShowDialog(); } private void Disconnect_Click(object sender, RoutedEventArgs e) { this.App.Disconnect(); } private void Input_Submit(object sender, KeyEventArgs e) { if (!(sender is TextBox textBox)) { return; } switch (e.Key) { case Key.Return: this.Submit(textBox); break; case Key.Up: this.ArrowNavigate(textBox, true); break; case Key.Down: this.ArrowNavigate(textBox, false); break; } } private void Submit(TextBox textBox) { var conn = this.App.Connection; if (conn == null) { return; } conn.SendMessage(textBox.Text); this.History.Add(textBox.Text); while (this.History.Count > 100) { this.History.RemoveAt(0); } textBox.Text = ""; } private void ArrowNavigate(TextBox textBox, bool up) { var caretLine = textBox.GetLineIndexFromCharacterIndex(textBox.CaretIndex); var inFirstLine = caretLine == 0; var inLastLine = caretLine == textBox.LineCount - 1; if (this.HistoryIndex == -1) { this.HistoryBuffer = textBox.Text; } if (up && inFirstLine) { // go up in history this.HistoryIndex += 1; textBox.Text = this.History[this.ReverseHistoryIndex]; } else if (!up && inLastLine) { // go down in history this.HistoryIndex -= 1; if (this.HistoryIndex == -1) { textBox.Text = this.HistoryBuffer; this.HistoryBuffer = null; } else { textBox.Text = this.History[this.ReverseHistoryIndex]; } } } private void Configuration_Click(object sender, RoutedEventArgs e) { new ConfigWindow(this, this.App.Config).Show(); } private void Tabs_Loaded(object sender, RoutedEventArgs e) { this.Tabs.SelectedIndex = 0; } public TextBox? GetCurrentInputBox() { return this.FindElementByName(this.Tabs, "InputBox"); } private void Tabs_SelectionChanged(object sender, SelectionChangedEventArgs e) { var scroller = this.FindElementByName(this.Tabs, "scroller"); scroller?.ScrollToBottom(); } private void Scan_Click(object sender, RoutedEventArgs e) { new ServerScan(this).Show(); } public event PropertyChangedEventHandler? PropertyChanged; internal void OnPropertyChanged([CallerMemberName] string? propertyName = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } private void Export_Click(object sender, RoutedEventArgs e) { new Export(this).Show(); } private void Licence_Click(object sender, RoutedEventArgs e) { new LicenceWindow(this, false).Show(); } private void Exit_Click(object sender, RoutedEventArgs e) { this.Close(); this.App.Shutdown(); } private void FriendList_Click(object sender, RoutedEventArgs e) { new FriendList(this).Show(); } } }