395 lines
14 KiB
C#
395 lines
14 KiB
C#
using System;
|
|
using System.Collections.Immutable;
|
|
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Documents;
|
|
using System.Windows.Threading;
|
|
using Microsoft.Win32;
|
|
using XIVChatCommon;
|
|
|
|
namespace XIVChat_Desktop {
|
|
public partial class Export : INotifyPropertyChanged {
|
|
public App App => (App)Application.Current;
|
|
|
|
public Tab ExportTab { get; }
|
|
|
|
public ExportFilter Filter => (ExportFilter)this.ExportTab.Filter;
|
|
|
|
public ObservableCollection<ServerMessage.SenderPlayer> Senders { get; } = new ObservableCollection<ServerMessage.SenderPlayer>();
|
|
|
|
private bool showTimestamps = true;
|
|
|
|
public bool ShowTimestamps {
|
|
get => this.showTimestamps;
|
|
set {
|
|
this.showTimestamps = value;
|
|
this.OnPropertyChanged(nameof(this.ShowTimestamps));
|
|
}
|
|
}
|
|
|
|
public Export(Window owner) {
|
|
this.Owner = owner;
|
|
|
|
this.ExportTab = new Tab("Export") {
|
|
Filter = new ExportFilter {
|
|
Types = Tab.GeneralFilter().Types,
|
|
},
|
|
};
|
|
|
|
this.Repopulate();
|
|
|
|
this.InitializeComponent();
|
|
this.DataContext = this;
|
|
|
|
this.SetUpFilters();
|
|
}
|
|
|
|
private void SetUpFilters() {
|
|
foreach (var category in (FilterCategory[])Enum.GetValues(typeof(FilterCategory))) {
|
|
var tabContent = new WrapPanel {
|
|
Margin = new Thickness(8),
|
|
Orientation = Orientation.Vertical,
|
|
HorizontalAlignment = HorizontalAlignment.Stretch,
|
|
};
|
|
|
|
var buttonsPanel = new WrapPanel {
|
|
Margin = new Thickness(0, 0, 0, 4),
|
|
};
|
|
|
|
var selectButton = new Button {
|
|
Content = "Select all",
|
|
};
|
|
selectButton.Click += (sender, e) => SetAllChecked(true);
|
|
|
|
var deselectButton = new Button {
|
|
Content = "Deselect all",
|
|
Margin = new Thickness(4, 0, 0, 0),
|
|
};
|
|
deselectButton.Click += (sender, e) => SetAllChecked(false);
|
|
|
|
var doingMultiple = false;
|
|
|
|
void SetAllChecked(bool isChecked) {
|
|
doingMultiple = true;
|
|
|
|
foreach (var child in tabContent.Children) {
|
|
if (!(child is CheckBox)) {
|
|
continue;
|
|
}
|
|
|
|
var check = (CheckBox)child;
|
|
check.IsChecked = isChecked;
|
|
}
|
|
|
|
this.Repopulate();
|
|
|
|
doingMultiple = false;
|
|
}
|
|
|
|
buttonsPanel.Children.Add(selectButton);
|
|
buttonsPanel.Children.Add(deselectButton);
|
|
|
|
tabContent.Children.Add(buttonsPanel);
|
|
tabContent.Children.Add(new Separator());
|
|
|
|
foreach (var type in category.Types()) {
|
|
var check = new CheckBox {
|
|
Content = type.Name(),
|
|
IsChecked = this.ExportTab.Filter.Types.Contains(type),
|
|
};
|
|
|
|
check.Checked += (sender, e) => {
|
|
this.ExportTab.Filter.Types.Add(type);
|
|
|
|
if (!doingMultiple) {
|
|
this.Repopulate();
|
|
}
|
|
};
|
|
check.Unchecked += (sender, e) => {
|
|
this.ExportTab.Filter.Types.Remove(type);
|
|
|
|
if (!doingMultiple) {
|
|
this.Repopulate();
|
|
}
|
|
};
|
|
|
|
tabContent.Children.Add(check);
|
|
}
|
|
|
|
var tabItem = new TabItem {
|
|
Header = new TextBlock(new Run(category.Name())),
|
|
Content = tabContent,
|
|
};
|
|
|
|
this.Tabs.Items.Add(tabItem);
|
|
}
|
|
}
|
|
|
|
private void Repopulate() {
|
|
this.ExportTab.RepopulateMessages(this.App.Window.Messages);
|
|
this.SetUpSenders();
|
|
}
|
|
|
|
private void SetUpSenders() {
|
|
// var senders = this.ExportTab.Messages
|
|
var senders = this.App.Window.Messages
|
|
.Where(msg => ((ExportFilter)this.ExportTab.Filter).AllowedMinusSenders(msg))
|
|
.Select(msg => msg.GetSenderPlayer())
|
|
.Where(sender => sender != null)
|
|
.ToImmutableSortedSet();
|
|
|
|
this.Senders.Clear();
|
|
|
|
foreach (var sender in senders) {
|
|
this.Senders.Add(sender!);
|
|
}
|
|
}
|
|
|
|
public class ExportFilter : Filter {
|
|
public ObservableCollection<ServerMessage.SenderPlayer> Senders { get; } = new ObservableCollection<ServerMessage.SenderPlayer>();
|
|
|
|
public DateTime? Before { get; set; }
|
|
public DateTime? After { get; set; }
|
|
|
|
public void AddSender(ServerMessage.SenderPlayer sender) {
|
|
if (this.Senders.Contains(sender)) {
|
|
return;
|
|
}
|
|
|
|
this.Senders.Add(sender);
|
|
}
|
|
|
|
public bool AllowedMinusSenders(ServerMessage message) {
|
|
if (!base.Allowed(message)) {
|
|
return false;
|
|
}
|
|
|
|
if (this.Before != null && message.Timestamp > this.Before) {
|
|
return false;
|
|
}
|
|
|
|
if (this.After != null && message.Timestamp < this.After) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public override bool Allowed(ServerMessage message) {
|
|
if (!this.AllowedMinusSenders(message)) {
|
|
return false;
|
|
}
|
|
|
|
// check sender if any senders are selected
|
|
var sender = message.GetSenderPlayer();
|
|
if (this.Senders.Count != 0 && sender != null && !this.Senders.Contains(sender)) {
|
|
return false;
|
|
}
|
|
|
|
// our stuff
|
|
return true;
|
|
}
|
|
|
|
public event PropertyChangedEventHandler? PropertyChanged;
|
|
|
|
protected void OnPropertyChanged1([CallerMemberName] string? propertyName = null) {
|
|
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
}
|
|
}
|
|
|
|
private void Markdown_Checked(object sender, RoutedEventArgs e) => this.SetMarkdownProcessing(true);
|
|
|
|
private void Markdown_Unchecked(object sender, RoutedEventArgs e) => this.SetMarkdownProcessing(false);
|
|
|
|
private void SetMarkdownProcessing(bool on) {
|
|
this.ExportTab.ProcessMarkdown = on;
|
|
}
|
|
|
|
private void RightArrow_Click(object sender, RoutedEventArgs e) {
|
|
var idx = this.SendersFilterSource.SelectedIndex;
|
|
if (idx == -1) {
|
|
return;
|
|
}
|
|
|
|
var player = this.Senders[idx];
|
|
|
|
var filter = (ExportFilter)this.ExportTab.Filter;
|
|
filter.AddSender(player);
|
|
|
|
this.Repopulate();
|
|
}
|
|
|
|
private void LeftArrow_Click(object sender, RoutedEventArgs e) {
|
|
var idx = this.SenderFiltersDest.SelectedIndex;
|
|
if (idx == -1) {
|
|
return;
|
|
}
|
|
|
|
var filter = (ExportFilter)this.ExportTab.Filter;
|
|
|
|
var player = filter.Senders.ElementAt(idx);
|
|
|
|
filter.Senders.Remove(player);
|
|
|
|
this.Repopulate();
|
|
}
|
|
|
|
public event PropertyChangedEventHandler? PropertyChanged;
|
|
|
|
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) {
|
|
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
|
}
|
|
|
|
private async void Save_Click(object sender, RoutedEventArgs e) {
|
|
// create a new flowdocument for saving
|
|
var flow = new FlowDocument();
|
|
|
|
// turn every message in the tab into a paragraph in the flowdocument
|
|
foreach (var message in this.ExportTab.Messages) {
|
|
this.Dispatch(DispatcherPriority.Background, () => {
|
|
var paragraph = new Paragraph();
|
|
// this has to be done on the main thread
|
|
var inlines = MessageFormatter.ChunksToTextBlock(message, this.App.Config.FontSize, this.ExportTab.ProcessMarkdown, this.ShowTimestamps);
|
|
paragraph.Inlines.AddRange(inlines);
|
|
flow.Blocks.Add(paragraph);
|
|
});
|
|
}
|
|
|
|
// ask the user where to save
|
|
var saveDialog = new SaveFileDialog {
|
|
Filter = $"{DataFormats.Text} (*.txt)|*.txt|{DataFormats.Rtf} (*.rtf)|*.rtf",
|
|
};
|
|
|
|
if (saveDialog.ShowDialog(this) != true) {
|
|
return;
|
|
}
|
|
|
|
var ext = saveDialog.FileName.Split('.').Last().ToLowerInvariant();
|
|
string dataFormat = ext switch {
|
|
"rtf" => DataFormats.Rtf,
|
|
_ => DataFormats.Text,
|
|
};
|
|
// save the data into memory (this apparently has to happen on the main thread or we'd save directly into a
|
|
// file)
|
|
await using var memoryStream = new MemoryStream();
|
|
new TextRange(flow.ContentStart, flow.ContentEnd).Save(memoryStream, dataFormat);
|
|
|
|
// write the saved data to a file on another thread
|
|
await Task.Run(async () => {
|
|
await using var stream = new FileStream(saveDialog.FileName, FileMode.Create);
|
|
memoryStream.Position = 0;
|
|
await memoryStream.CopyToAsync(stream);
|
|
});
|
|
|
|
// show completion box
|
|
MessageBox.Show("Exported successfully.");
|
|
}
|
|
|
|
private bool ignoreDateChanges;
|
|
|
|
private void AfterDatePicker_OnSelectedDateChanged(object? sender, SelectionChangedEventArgs e) {
|
|
if (this.ignoreDateChanges) {
|
|
return;
|
|
}
|
|
|
|
var datePicker = (DatePicker)sender!;
|
|
this.Filter.After = UpdateDate(this.Filter.After?.ToLocalTime(), datePicker.SelectedDate)?.ToUniversalTime();
|
|
|
|
this.ignoreDateChanges = true;
|
|
this.AfterTimePicker.SelectedDateTime = this.Filter.After?.ToLocalTime();
|
|
this.ignoreDateChanges = false;
|
|
|
|
this.Repopulate();
|
|
}
|
|
|
|
private void AfterTimePicker_OnSelectedDateTimeChanged(object sender, RoutedPropertyChangedEventArgs<DateTime?> e) {
|
|
if (this.ignoreDateChanges) {
|
|
return;
|
|
}
|
|
|
|
this.Filter.After = UpdateTime(this.Filter.After?.ToLocalTime(), e.NewValue)?.ToUniversalTime();
|
|
|
|
this.AfterDatePicker.SelectedDate = this.Filter.After?.ToLocalTime();
|
|
}
|
|
|
|
private void BeforeDatePicker_OnSelectedDateChanged(object? sender, SelectionChangedEventArgs e) {
|
|
if (this.ignoreDateChanges) {
|
|
return;
|
|
}
|
|
|
|
var datePicker = (DatePicker)sender!;
|
|
this.Filter.Before = UpdateDate(this.Filter.Before?.ToLocalTime(), datePicker.SelectedDate)?.ToUniversalTime();
|
|
|
|
this.ignoreDateChanges = true;
|
|
this.BeforeTimePicker.SelectedDateTime = this.Filter.Before?.ToLocalTime();
|
|
this.ignoreDateChanges = false;
|
|
|
|
this.Repopulate();
|
|
}
|
|
|
|
private void BeforeTimePicker_OnSelectedDateTimeChanged(object sender, RoutedPropertyChangedEventArgs<DateTime?> e) {
|
|
if (this.ignoreDateChanges) {
|
|
return;
|
|
}
|
|
|
|
this.Filter.Before = UpdateTime(this.Filter.Before?.ToLocalTime(), e.NewValue)?.ToUniversalTime();
|
|
|
|
this.BeforeDatePicker.SelectedDate = this.Filter.Before?.ToLocalTime();
|
|
}
|
|
|
|
private static DateTime? UpdateTime(DateTime? dest, DateTime? source) {
|
|
switch (dest) {
|
|
case null when source == null:
|
|
return null;
|
|
case null:
|
|
return source;
|
|
}
|
|
|
|
if (source == null) {
|
|
return dest;
|
|
}
|
|
|
|
var newValue = source.Value;
|
|
|
|
return new DateTime(dest.Value.Year, dest.Value.Month, dest.Value.Day, newValue.Hour, newValue.Minute, newValue.Second);
|
|
}
|
|
|
|
private static DateTime? UpdateDate(DateTime? dest, DateTime? source) {
|
|
switch (dest) {
|
|
case null when source == null:
|
|
return null;
|
|
case null:
|
|
return source;
|
|
}
|
|
|
|
if (source == null) {
|
|
return dest;
|
|
}
|
|
|
|
var newValue = source.Value;
|
|
|
|
return new DateTime(newValue.Year, newValue.Month, newValue.Day, dest.Value.Hour, dest.Value.Minute, dest.Value.Second);
|
|
}
|
|
|
|
private void BeforeClear_Click(object sender, RoutedEventArgs e) {
|
|
this.ignoreDateChanges = true;
|
|
this.BeforeDatePicker.SelectedDate = null;
|
|
this.ignoreDateChanges = false;
|
|
this.BeforeTimePicker.SelectedDateTime = null;
|
|
}
|
|
|
|
private void AfterClear_Click(object sender, RoutedEventArgs e) {
|
|
this.ignoreDateChanges = true;
|
|
this.AfterDatePicker.SelectedDate = null;
|
|
this.ignoreDateChanges = false;
|
|
this.AfterTimePicker.SelectedDateTime = null;
|
|
}
|
|
}
|
|
}
|