using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; namespace JJServiceManager { public enum LogLevel { Info, Success, Warning, Error, Notice } /// 로그 한 줄 public class LogLine : ObservableObject { private bool _isChecked; private double _fillerHeight; public int No { get; init; } public DateTime Time { get; init; } public string Text { get; init; } = ""; public LogLevel Level { get; init; } public bool ShowTime { get; init; } public bool IsFiller { get; init; } public bool IsChecked { get => _isChecked; set => Set(ref _isChecked, value); } public double FillerHeight { get => _fillerHeight; set => Set(ref _fillerHeight, Math.Max(0, value)); } public string NoText => IsFiller ? "" : No.ToString(); public string Display => IsFiller ? "" : (ShowTime ? $"[{Time:HH:mm:ss}] {Text}" : Text); public Visibility FillerVisibility => IsFiller ? Visibility.Visible : Visibility.Collapsed; public Visibility LineVisibility => IsFiller ? Visibility.Collapsed : Visibility.Visible; public Brush Foreground => Level switch { LogLevel.Success => Brushes2.Running, LogLevel.Warning => Brushes2.Pending, LogLevel.Error => Brushes2.Stopped, LogLevel.Notice => LogPanel.NoticeBrush, _ => LogPanel.NormalBrush }; /// 복사할 때의 텍스트 public string CopyText => ShowTime ? $"[{Time:yyyy-MM-dd HH:mm:ss}] {Text}" : Text; } /// 앞쪽 여러 줄을 한 번에 지울 수 있는 컬렉션 (변경 알림 1회) public sealed class LogCollection : ObservableCollection { public void RemoveFirst(int count) { if (count <= 0) return; CheckReentrancy(); var list = (List)Items; list.RemoveRange(0, Math.Min(count, list.Count)); OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("Count")); OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("Item[]")); OnCollectionChanged(new System.Collections.Specialized.NotifyCollectionChangedEventArgs( System.Collections.Specialized.NotifyCollectionChangedAction.Reset)); } } /// /// 번호 + 체크박스가 있는 로그 목록. /// - 화면클리어: 새 로그를 화면 맨 위부터 출력, 위로 스크롤하면 이전 로그가 보임, 번호는 이어짐 /// - 로그 클리어: 화면의 로그를 모두 지우고 번호를 1번부터 다시 시작 /// - 파일은 절대 지우지 않음 /// public class LogPanel : ContentControl { public static readonly SolidColorBrush NormalBrush = Brushes2.Make("#212121"); public static readonly SolidColorBrush NoticeBrush = Brushes2.Make("#1565C0"); private const double LineHeight = 22; private readonly LogCollection _lines = new LogCollection(); private readonly ListBox _list; private readonly TextBlock _info; private readonly DispatcherTimer _infoTimer; private ScrollViewer _scroll; private LogLine _filler; private int _fillerNewLines; private int _nextNo = 1; private bool _scrollPending; /// 줄 앞에 시각 표시 여부 (작업 로그 = true, 파일 로그 = false) public bool ShowTimestamp { get; set; } = true; /// 화면에 유지할 최대 줄 수 (오래된 줄부터 화면에서만 빠짐) public int MaxLines { get; set; } = 20000; public int Count => _lines.Count - (_filler != null ? 1 : 0); public LogPanel() { var root = XamlLoader.LoadElement("LogPanel.xaml"); Content = root; _list = root.Find("lstLog"); _info = root.Find("txtInfo"); _list.ItemsSource = _lines; root.Find