using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Interop; using System.Windows.Markup; using System.Windows.Media; using System.Windows.Threading; using AxCopilot.Services; using AxCopilot.Services.Agent; using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.Wpf; namespace AxCopilot.Views; public class PreviewWindow : Window, IComponentConnector { private static PreviewWindow? _instance; private readonly List _tabs = new List(); private string? _activeTab; private bool _webViewInitialized; private string? _selectedMood; private static readonly string WebView2DataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "WebView2_Preview"); private static readonly HashSet PreviewableExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { ".html", ".htm", ".md", ".csv", ".txt", ".json", ".xml", ".log" }; private const int WM_NCHITTEST = 132; private const int HTLEFT = 10; private const int HTRIGHT = 11; private const int HTTOP = 12; private const int HTTOPLEFT = 13; private const int HTTOPRIGHT = 14; private const int HTBOTTOM = 15; private const int HTBOTTOMLEFT = 16; private const int HTBOTTOMRIGHT = 17; internal TextBlock TitleText; internal TextBlock MaxBtnIcon; internal StackPanel TabPanel; internal WebView2 PreviewBrowser; internal ScrollViewer TextScroll; internal TextBlock TextContent; internal DataGrid DataGridContent; internal TextBlock EmptyMessage; private bool _contentLoaded; public static bool IsOpen => _instance != null && _instance.IsLoaded; public PreviewWindow() { InitializeComponent(); base.Loaded += OnLoaded; base.SourceInitialized += OnSourceInitialized; base.KeyDown += delegate(object _, KeyEventArgs e) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 if ((int)e.Key == 13) { Close(); } }; base.StateChanged += delegate { MaxBtnIcon.Text = ((base.WindowState == WindowState.Maximized) ? "\ue923" : "\ue922"); }; base.Closed += delegate { _instance = null; }; } [DllImport("user32.dll")] private static extern nint SendMessage(nint hWnd, int msg, nint wParam, nint lParam); private void OnSourceInitialized(object? sender, EventArgs e) { ((HwndSource)PresentationSource.FromVisual(this))?.AddHook(WndProc); } private nint WndProc(nint hwnd, int msg, nint wParam, nint lParam, ref bool handled) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (msg == 132) { Point val = PointFromScreen(new Point((double)(short)(((IntPtr)lParam).ToInt32() & 0xFFFF), (double)(short)((((IntPtr)lParam).ToInt32() >> 16) & 0xFFFF))); double actualWidth = base.ActualWidth; double actualHeight = base.ActualHeight; if (((Point)(ref val)).X < 8.0 && ((Point)(ref val)).Y < 8.0) { handled = true; return 13; } if (((Point)(ref val)).X > actualWidth - 8.0 && ((Point)(ref val)).Y < 8.0) { handled = true; return 14; } if (((Point)(ref val)).X < 8.0 && ((Point)(ref val)).Y > actualHeight - 8.0) { handled = true; return 16; } if (((Point)(ref val)).X > actualWidth - 8.0 && ((Point)(ref val)).Y > actualHeight - 8.0) { handled = true; return 17; } if (((Point)(ref val)).X < 8.0) { handled = true; return 10; } if (((Point)(ref val)).X > actualWidth - 8.0) { handled = true; return 11; } if (((Point)(ref val)).Y < 8.0) { handled = true; return 12; } if (((Point)(ref val)).Y > actualHeight - 8.0) { handled = true; return 15; } } return IntPtr.Zero; } public static void ShowPreview(string filePath, string? mood = null) { if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath)) { return; } string item = Path.GetExtension(filePath).ToLowerInvariant(); if (!PreviewableExtensions.Contains(item)) { return; } ((DispatcherObject)Application.Current).Dispatcher.Invoke((Action)delegate { if (_instance == null || !_instance.IsLoaded) { _instance = new PreviewWindow(); Window mainWindow = Application.Current.MainWindow; if (mainWindow != null) { foreach (ResourceDictionary mergedDictionary in mainWindow.Resources.MergedDictionaries) { _instance.Resources.MergedDictionaries.Add(mergedDictionary); } } _instance._selectedMood = mood; _instance.Show(); } _instance.AddTab(filePath); _instance.Activate(); }); } public static void RefreshIfOpen(string filePath) { if (_instance == null || !_instance.IsLoaded) { return; } ((DispatcherObject)Application.Current).Dispatcher.Invoke((Action)delegate { if (_instance._activeTab != null && string.Equals(_instance._activeTab, filePath, StringComparison.OrdinalIgnoreCase)) { _instance.LoadContent(filePath); } else { string text = _instance._tabs.FirstOrDefault((string t) => string.Equals(t, filePath, StringComparison.OrdinalIgnoreCase)); if (text != null) { _instance._activeTab = text; _instance.LoadContent(text); _instance.RebuildTabs(); } } }); } private async void OnLoaded(object sender, RoutedEventArgs e) { try { CoreWebView2Environment env = await CoreWebView2Environment.CreateAsync(null, WebView2DataFolder); await PreviewBrowser.EnsureCoreWebView2Async(env); _webViewInitialized = true; if (_activeTab != null) { LoadContent(_activeTab); } } catch (Exception ex) { Exception ex2 = ex; LogService.Warn("PreviewWindow WebView2 초기화 실패: " + ex2.Message); } } private void AddTab(string filePath) { if (!_tabs.Contains(filePath, StringComparer.OrdinalIgnoreCase)) { _tabs.Add(filePath); } _activeTab = filePath; RebuildTabs(); LoadContent(filePath); } private void CloseTab(string filePath) { _tabs.RemoveAll((string t) => string.Equals(t, filePath, StringComparison.OrdinalIgnoreCase)); if (_tabs.Count == 0) { Close(); return; } if (string.Equals(_activeTab, filePath, StringComparison.OrdinalIgnoreCase)) { List tabs = _tabs; _activeTab = tabs[tabs.Count - 1]; LoadContent(_activeTab); } RebuildTabs(); } private void RebuildTabs() { TabPanel.Children.Clear(); Brush brush = (TryFindResource("AccentColor") as Brush) ?? Brushes.CornflowerBlue; Brush brush2 = (TryFindResource("SecondaryText") as Brush) ?? Brushes.Gray; Brush brush3 = (TryFindResource("PrimaryText") as Brush) ?? Brushes.White; Brush background = (TryFindResource("BorderColor") as Brush) ?? Brushes.Gray; foreach (string tab in _tabs) { string fileName = Path.GetFileName(tab); bool flag = string.Equals(tab, _activeTab, StringComparison.OrdinalIgnoreCase); Border border = new Border { Background = Brushes.Transparent, BorderBrush = (flag ? brush : Brushes.Transparent), BorderThickness = new Thickness(0.0, 0.0, 0.0, flag ? 2 : 0), Padding = new Thickness(10.0, 6.0, 6.0, 6.0), Cursor = Cursors.Hand, MaxWidth = ((_tabs.Count <= 3) ? 220 : ((_tabs.Count <= 5) ? 160 : 110)) }; StackPanel stackPanel = new StackPanel { Orientation = Orientation.Horizontal }; stackPanel.Children.Add(new TextBlock { Text = fileName, FontSize = 11.5, Foreground = (flag ? brush3 : brush2), FontWeight = (flag ? FontWeights.SemiBold : FontWeights.Normal), VerticalAlignment = VerticalAlignment.Center, TextTrimming = TextTrimming.CharacterEllipsis, MaxWidth = border.MaxWidth - 36.0, ToolTip = tab }); string closePath = tab; Border border2 = new Border { Background = Brushes.Transparent, CornerRadius = new CornerRadius(3.0), Padding = new Thickness(4.0, 2.0, 4.0, 2.0), Margin = new Thickness(6.0, 0.0, 0.0, 0.0), Cursor = Cursors.Hand, VerticalAlignment = VerticalAlignment.Center, Child = new TextBlock { Text = "\ue711", FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 8.0, Foreground = brush2, VerticalAlignment = VerticalAlignment.Center } }; border2.MouseEnter += delegate(object s, MouseEventArgs _) { if (s is Border border3) { border3.Background = new SolidColorBrush(Color.FromArgb(48, byte.MaxValue, 80, 80)); } }; border2.MouseLeave += delegate(object s, MouseEventArgs _) { if (s is Border border3) { border3.Background = Brushes.Transparent; } }; border2.MouseLeftButtonUp += delegate(object _, MouseButtonEventArgs me) { me.Handled = true; CloseTab(closePath); }; stackPanel.Children.Add(border2); border.Child = stackPanel; string clickPath = tab; border.MouseLeftButtonUp += delegate(object _, MouseButtonEventArgs me) { if (!me.Handled) { me.Handled = true; _activeTab = clickPath; RebuildTabs(); LoadContent(clickPath); } }; border.MouseEnter += delegate(object s, MouseEventArgs _) { if (s is Border border3 && !string.Equals(clickPath, _activeTab, StringComparison.OrdinalIgnoreCase)) { border3.Background = new SolidColorBrush(Color.FromArgb(24, byte.MaxValue, byte.MaxValue, byte.MaxValue)); } }; border.MouseLeave += delegate(object s, MouseEventArgs _) { if (s is Border border3) { border3.Background = Brushes.Transparent; } }; TabPanel.Children.Add(border); List tabs = _tabs; if (tab != tabs[tabs.Count - 1]) { TabPanel.Children.Add(new Border { Width = 1.0, Height = 14.0, Background = background, Margin = new Thickness(2.0, 0.0, 2.0, 0.0), VerticalAlignment = VerticalAlignment.Center }); } } if (_activeTab != null) { TitleText.Text = "미리보기 — " + Path.GetFileName(_activeTab); } } private async void LoadContent(string filePath) { string ext = Path.GetExtension(filePath).ToLowerInvariant(); PreviewBrowser.Visibility = Visibility.Collapsed; TextScroll.Visibility = Visibility.Collapsed; DataGridContent.Visibility = Visibility.Collapsed; EmptyMessage.Visibility = Visibility.Collapsed; if (!File.Exists(filePath)) { EmptyMessage.Text = "파일을 찾을 수 없습니다"; EmptyMessage.Visibility = Visibility.Visible; return; } try { switch (ext) { case ".html": case ".htm": if (!_webViewInitialized) { return; } PreviewBrowser.Source = new Uri(filePath); PreviewBrowser.Visibility = Visibility.Visible; break; case ".csv": LoadCsvContent(filePath); DataGridContent.Visibility = Visibility.Visible; break; case ".md": { if (!_webViewInitialized) { return; } string mdText = File.ReadAllText(filePath); if (mdText.Length > 50000) { mdText = mdText.Substring(0, 50000); } string mdHtml = TemplateService.RenderMarkdownToHtml(mdText, _selectedMood ?? "modern"); PreviewBrowser.NavigateToString(mdHtml); PreviewBrowser.Visibility = Visibility.Visible; break; } case ".txt": case ".json": case ".xml": case ".log": { string text = File.ReadAllText(filePath); if (text.Length > 50000) { text = text.Substring(0, 50000) + "\n\n... (이후 생략)"; } TextContent.Text = text; TextScroll.Visibility = Visibility.Visible; break; } default: EmptyMessage.Text = "미리보기할 수 없는 파일 형식입니다"; EmptyMessage.Visibility = Visibility.Visible; break; } } catch (Exception ex) { Exception ex2 = ex; TextContent.Text = "미리보기 오류: " + ex2.Message; TextScroll.Visibility = Visibility.Visible; } await Task.CompletedTask; } private void LoadCsvContent(string filePath) { string[] array = File.ReadAllLines(filePath); if (array.Length == 0) { return; } DataTable dataTable = new DataTable(); string[] array2 = ParseCsvLine(array[0]); string[] array3 = array2; foreach (string columnName in array3) { dataTable.Columns.Add(columnName); } int num = Math.Min(array.Length, 501); for (int j = 1; j < num; j++) { string[] array4 = ParseCsvLine(array[j]); DataRow dataRow = dataTable.NewRow(); for (int k = 0; k < Math.Min(array4.Length, array2.Length); k++) { dataRow[k] = array4[k]; } dataTable.Rows.Add(dataRow); } DataGridContent.ItemsSource = dataTable.DefaultView; } private static string[] ParseCsvLine(string line) { List list = new List(); StringBuilder stringBuilder = new StringBuilder(); bool flag = false; for (int i = 0; i < line.Length; i++) { char c = line[i]; if (flag) { if (c == '"' && i + 1 < line.Length && line[i + 1] == '"') { stringBuilder.Append('"'); i++; } else if (c == '"') { flag = false; } else { stringBuilder.Append(c); } continue; } switch (c) { case '"': flag = true; break; case ',': list.Add(stringBuilder.ToString()); stringBuilder.Clear(); break; default: stringBuilder.Append(c); break; } } list.Add(stringBuilder.ToString()); return list.ToArray(); } private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (e.ClickCount == 2) { ToggleMaximize(); } else { DragMove(); } } private void OpenExternalBtn_Click(object sender, MouseButtonEventArgs e) { e.Handled = true; if (_activeTab == null || !File.Exists(_activeTab)) { return; } try { Process.Start(new ProcessStartInfo { FileName = _activeTab, UseShellExecute = true }); } catch (Exception ex) { LogService.Warn("외부 프로그램 열기 실패: " + ex.Message); } } private void MinBtn_Click(object sender, MouseButtonEventArgs e) { e.Handled = true; base.WindowState = WindowState.Minimized; } private void MaxBtn_Click(object sender, MouseButtonEventArgs e) { e.Handled = true; ToggleMaximize(); } private void CloseBtn_Click(object sender, MouseButtonEventArgs e) { e.Handled = true; Close(); } private void ToggleMaximize() { base.WindowState = ((base.WindowState != WindowState.Maximized) ? WindowState.Maximized : WindowState.Normal); } private void TitleBtn_Enter(object sender, MouseEventArgs e) { if (sender is Border border) { border.Background = (TryFindResource("ItemHoverBackground") as Brush) ?? new SolidColorBrush(Color.FromArgb(24, byte.MaxValue, byte.MaxValue, byte.MaxValue)); } } private void CloseBtnEnter(object sender, MouseEventArgs e) { if (sender is Border border) { border.Background = new SolidColorBrush(Color.FromArgb(68, byte.MaxValue, 64, 64)); } } private void TitleBtn_Leave(object sender, MouseEventArgs e) { if (sender is Border border) { border.Background = Brushes.Transparent; } } [DebuggerNonUserCode] [GeneratedCode("PresentationBuildTasks", "10.0.5.0")] public void InitializeComponent() { if (!_contentLoaded) { _contentLoaded = true; Uri resourceLocator = new Uri("/AxCopilot;component/views/previewwindow.xaml", UriKind.Relative); Application.LoadComponent(this, resourceLocator); } } [DebuggerNonUserCode] [GeneratedCode("PresentationBuildTasks", "10.0.5.0")] [EditorBrowsable(EditorBrowsableState.Never)] void IComponentConnector.Connect(int connectionId, object target) { switch (connectionId) { case 1: ((Border)target).MouseLeftButtonDown += TitleBar_MouseLeftButtonDown; break; case 2: TitleText = (TextBlock)target; break; case 3: ((Border)target).MouseLeftButtonDown += OpenExternalBtn_Click; ((Border)target).MouseEnter += TitleBtn_Enter; ((Border)target).MouseLeave += TitleBtn_Leave; break; case 4: ((Border)target).MouseLeftButtonDown += MinBtn_Click; ((Border)target).MouseEnter += TitleBtn_Enter; ((Border)target).MouseLeave += TitleBtn_Leave; break; case 5: ((Border)target).MouseLeftButtonDown += MaxBtn_Click; ((Border)target).MouseEnter += TitleBtn_Enter; ((Border)target).MouseLeave += TitleBtn_Leave; break; case 6: MaxBtnIcon = (TextBlock)target; break; case 7: ((Border)target).MouseLeftButtonDown += CloseBtn_Click; ((Border)target).MouseEnter += CloseBtnEnter; ((Border)target).MouseLeave += TitleBtn_Leave; break; case 8: TabPanel = (StackPanel)target; break; case 9: PreviewBrowser = (WebView2)target; break; case 10: TextScroll = (ScrollViewer)target; break; case 11: TextContent = (TextBlock)target; break; case 12: DataGridContent = (DataGrid)target; break; case 13: EmptyMessage = (TextBlock)target; break; default: _contentLoaded = true; break; } } }