변경 목적: 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개를 확인했습니다.
321 lines
12 KiB
C#
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;
|
|
}
|
|
}
|