Files
AX-Copilot-Codex/src/AxCopilot/Views/MacroEditorWindow.xaml.cs
lacvet 0336904258 AX Commander 비교본 런처 기능 대량 이식
변경 목적: Agent Compare 아래 비교본의 개발 문서와 런처 소스를 기준으로 현재 AX Commander에 빠져 있던 신규 런처 기능을 동일한 흐름으로 옮겨, 비교본 수준의 기능 폭을 현재 제품에 반영했습니다.

핵심 수정사항: 비교본의 신규 런처 핸들러 다수를 src/AxCopilot/Handlers로 이식하고 App.xaml.cs 등록 흐름에 연결했습니다. 빠른 링크, 파일 태그, 알림 센터, 포모도로, 파일 브라우저, 핫키 관리, OCR, 세션/스케줄/매크로, Git/정규식/네트워크/압축/해시/UUID/JWT/QR 등 AX Commander 기능을 추가했습니다.

핵심 수정사항: 신규 기능이 실제 동작하도록 AppSettings 확장, SchedulerService/FileTagService/NotificationCenterService/IconCacheService/UrlTemplateEngine/PomodoroService 추가, 배치 이름변경/세션/스케줄/매크로 편집 창 추가, NotificationService와 Symbols 보강, QR/OCR용 csproj 의존성과 Windows 타겟 프레임워크를 반영했습니다.

문서 반영: README.md와 docs/DEVELOPMENT.md에 비교본 기반 런처 기능 이식 이력과 검증 결과를 업데이트했습니다.

검증 결과: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ 실행 기준 경고 0개, 오류 0개를 확인했습니다.
2026-04-05 00:59:45 +09:00

321 lines
12 KiB
C#

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using AxCopilot.Models;
using AxCopilot.Services;
namespace AxCopilot.Views;
public partial class MacroEditorWindow : Window
{
private readonly SettingsService _settings;
private readonly MacroEntry? _editing;
// 유형 목록
private static readonly string[] StepTypes = { "app", "url", "folder", "notification", "cmd" };
private static readonly string[] StepTypeLabels = { "앱", "URL", "폴더", "알림", "PowerShell" };
// 각 행의 컨트롤 참조
private readonly List<StepRowUi> _rows = new();
// 공유 타입 팝업
private readonly Popup _typePopup = new()
{
StaysOpen = false,
AllowsTransparency = true,
Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom
};
private StepRowUi? _typeTargetRow;
public MacroEditorWindow(MacroEntry? entry, SettingsService settings)
{
InitializeComponent();
_settings = settings;
_editing = entry;
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
BuildTypePopup();
if (_editing != null)
{
NameBox.Text = _editing.Name;
DescBox.Text = _editing.Description;
foreach (var step in _editing.Steps)
AddRow(step);
}
if (_rows.Count == 0)
AddRow(null); // 기본 빈 행
}
// ─── 팝업 빌드 ──────────────────────────────────────────────────────────
private void BuildTypePopup()
{
var bg = TryFindResource("ItemBackground") as Brush ?? Brushes.DimGray;
var border = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
var fg = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
var hover = TryFindResource("ItemHoverBackground") as Brush ?? Brushes.Gray;
var panel = new StackPanel { Background = bg, MinWidth = 100 };
for (int i = 0; i < StepTypes.Length; i++)
{
var idx = i;
var label = StepTypeLabels[i];
var item = new Border
{
Padding = new Thickness(12, 6, 12, 6),
Background = Brushes.Transparent,
Cursor = Cursors.Hand,
Tag = idx
};
item.MouseEnter += (_, _) => item.Background = hover;
item.MouseLeave += (_, _) => item.Background = Brushes.Transparent;
item.MouseLeftButtonUp += (_, _) =>
{
if (_typeTargetRow != null)
SetRowType(_typeTargetRow, idx);
_typePopup.IsOpen = false;
};
item.Child = new TextBlock
{
Text = label,
FontSize = 12,
Foreground = fg
};
panel.Children.Add(item);
}
var outerBorder = new Border
{
Background = bg,
BorderBrush = border,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(6),
Child = panel,
Effect = new System.Windows.Media.Effects.DropShadowEffect
{
BlurRadius = 12,
ShadowDepth = 3,
Opacity = 0.3,
Color = Colors.Black,
Direction = 270
}
};
_typePopup.Child = outerBorder;
}
// ─── 행 추가 ────────────────────────────────────────────────────────────
private void AddRow(MacroStep? step)
{
var bg = TryFindResource("ItemBackground") as Brush ?? Brushes.DimGray;
var border = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
var accent = TryFindResource("AccentColor") as Brush ?? Brushes.Blue;
var secFg = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
var primFg = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
var row = new StepRowUi();
var grid = new Grid { Margin = new Thickness(0, 0, 0, 4) };
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(90) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(80) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(70) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(28) });
// Col 0: 유형 버튼
var typeLbl = new TextBlock
{
FontSize = 11,
VerticalAlignment = VerticalAlignment.Center,
Foreground = primFg,
Text = StepTypeLabels[0]
};
var typeBtn = new Border
{
Background = bg,
BorderBrush = border,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(4),
Padding = new Thickness(6, 4, 6, 4),
Cursor = Cursors.Hand,
Child = typeLbl,
Margin = new Thickness(0, 0, 4, 0)
};
typeBtn.MouseLeftButtonUp += (_, _) =>
{
_typeTargetRow = row;
_typePopup.PlacementTarget = typeBtn;
_typePopup.IsOpen = true;
};
row.TypeButton = typeBtn;
row.TypeLabel = typeLbl;
Grid.SetColumn(typeBtn, 0);
// Col 1: 대상
var targetBox = new TextBox
{
FontSize = 11,
Foreground = primFg,
Background = (Brush)TryFindResource("LauncherBackground")! ?? Brushes.Black,
BorderBrush = border,
BorderThickness = new Thickness(1),
Padding = new Thickness(6, 4, 6, 4),
Margin = new Thickness(0, 0, 4, 0),
Text = step?.Target ?? ""
};
row.TargetBox = targetBox;
Grid.SetColumn(targetBox, 1);
// Col 2: 표시 이름
var labelBox = new TextBox
{
FontSize = 11,
Foreground = secFg,
Background = (Brush)TryFindResource("LauncherBackground")! ?? Brushes.Black,
BorderBrush = border,
BorderThickness = new Thickness(1),
Padding = new Thickness(6, 4, 6, 4),
Margin = new Thickness(0, 0, 4, 0),
Text = step?.Label ?? ""
};
row.LabelBox = labelBox;
Grid.SetColumn(labelBox, 2);
// Col 3: 딜레이
var delayBox = new TextBox
{
FontSize = 11,
Foreground = primFg,
Background = (Brush)TryFindResource("LauncherBackground")! ?? Brushes.Black,
BorderBrush = border,
BorderThickness = new Thickness(1),
Padding = new Thickness(6, 4, 6, 4),
Margin = new Thickness(0, 0, 4, 0),
Text = (step?.DelayMs ?? 500).ToString()
};
row.DelayBox = delayBox;
Grid.SetColumn(delayBox, 3);
// Col 4: 삭제
var delBtn = new Border
{
Width = 24,
Height = 24,
CornerRadius = new CornerRadius(4),
Background = Brushes.Transparent,
Cursor = Cursors.Hand,
VerticalAlignment = VerticalAlignment.Center,
Child = new TextBlock
{
Text = "\uE711",
FontFamily = new FontFamily("Segoe MDL2 Assets"),
FontSize = 11,
Foreground = secFg,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
}
};
delBtn.MouseEnter += (_, _) => delBtn.Background = new SolidColorBrush(Color.FromArgb(0x30, 0xC0, 0x50, 0x50));
delBtn.MouseLeave += (_, _) => delBtn.Background = Brushes.Transparent;
delBtn.MouseLeftButtonUp += (_, _) => RemoveRow(row);
Grid.SetColumn(delBtn, 4);
grid.Children.Add(typeBtn);
grid.Children.Add(targetBox);
grid.Children.Add(labelBox);
grid.Children.Add(delayBox);
grid.Children.Add(delBtn);
row.Grid = grid;
// 유형 초기화
int typeIdx = step != null ? Array.IndexOf(StepTypes, step.Type.ToLowerInvariant()) : 0;
if (typeIdx < 0) typeIdx = 0;
SetRowType(row, typeIdx);
_rows.Add(row);
StepsPanel.Children.Add(grid);
}
private void SetRowType(StepRowUi row, int typeIdx)
{
row.TypeIndex = typeIdx;
row.TypeLabel.Text = StepTypeLabels[typeIdx];
var accent = TryFindResource("AccentColor") as Brush ?? Brushes.Blue;
var secFg = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
row.TypeLabel.Foreground = accent;
}
private void RemoveRow(StepRowUi row)
{
StepsPanel.Children.Remove(row.Grid);
_rows.Remove(row);
}
// ─── 버튼 이벤트 ─────────────────────────────────────────────────────────
private void BtnAddStep_Click(object sender, MouseButtonEventArgs e) => AddRow(null);
private void BtnSave_Click(object sender, MouseButtonEventArgs e)
{
var name = NameBox.Text.Trim();
if (string.IsNullOrWhiteSpace(name))
{
MessageBox.Show("매크로 이름을 입력하세요.", "저장 오류",
MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
var steps = _rows
.Where(r => !string.IsNullOrWhiteSpace(r.TargetBox.Text))
.Select(r => new MacroStep
{
Type = StepTypes[r.TypeIndex],
Target = r.TargetBox.Text.Trim(),
Label = r.LabelBox.Text.Trim(),
DelayMs = int.TryParse(r.DelayBox.Text, out var d) ? Math.Max(0, d) : 500
})
.ToList();
var entry = _editing ?? new MacroEntry();
entry.Name = name;
entry.Description = DescBox.Text.Trim();
entry.Steps = steps;
if (_editing == null)
_settings.Settings.Macros.Add(entry);
_settings.Save();
NotificationService.Notify("AX Copilot", $"매크로 '{entry.Name}' 저장됨");
Close();
}
private void BtnCancel_Click(object sender, MouseButtonEventArgs e) => Close();
private void BtnClose_Click(object sender, MouseButtonEventArgs e) => Close();
private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed) DragMove();
}
// ─── 내부 행 참조 클래스 ──────────────────────────────────────────────────
private class StepRowUi
{
public Grid Grid = null!;
public Border TypeButton = null!;
public TextBlock TypeLabel = null!;
public TextBox TargetBox = null!;
public TextBox LabelBox = null!;
public TextBox DelayBox = null!;
public int TypeIndex;
}
}