[Phase 39] FontFamily 캐싱 + LauncherWindow 파셜 클래스 분할
- ThemeResourceHelper에 CascadiaCode/ConsolasCode/ConsolasCourierNew 정적 필드 추가 - 25개 파일, 89개 new FontFamily(...) 호출을 정적 캐시 참조로 교체 - LauncherWindow.xaml.cs (1,563줄) → 5개 파셜 파일로 분할 (63% 감소) - LauncherWindow.Theme.cs (116줄): ApplyTheme, 커스텀 딕셔너리 빌드 - LauncherWindow.Animations.cs (153줄): 무지개 글로우, 애니메이션 헬퍼 - LauncherWindow.Keyboard.cs (593줄): 단축키 20종, ShowToast, IME 검색 - LauncherWindow.Shell.cs (177줄): Shell32, SendToRecycleBin, 클릭 핸들러 - 빌드: 경고 0, 오류 0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
177
src/AxCopilot/Views/LauncherWindow.Shell.cs
Normal file
177
src/AxCopilot/Views/LauncherWindow.Shell.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using AxCopilot.Services;
|
||||
|
||||
namespace AxCopilot.Views;
|
||||
|
||||
public partial class LauncherWindow
|
||||
{
|
||||
// ─── Shell32 휴지통 삭제 ────────────────────────────────────────────────
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct SHFILEOPSTRUCT
|
||||
{
|
||||
public IntPtr hwnd;
|
||||
public uint wFunc;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string pFrom;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string? pTo;
|
||||
public ushort fFlags;
|
||||
[MarshalAs(UnmanagedType.Bool)] public bool fAnyOperationsAborted;
|
||||
public IntPtr hNameMappings;
|
||||
[MarshalAs(UnmanagedType.LPWStr)] public string? lpszProgressTitle;
|
||||
}
|
||||
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern int SHFileOperation(ref SHFILEOPSTRUCT lpFileOp);
|
||||
|
||||
private const uint FO_DELETE = 0x0003;
|
||||
private const ushort FOF_ALLOWUNDO = 0x0040;
|
||||
private const ushort FOF_NOCONFIRMATION = 0x0010;
|
||||
private const ushort FOF_SILENT = 0x0004;
|
||||
|
||||
/// <summary>파일·폴더를 Windows 휴지통으로 보냅니다.</summary>
|
||||
private void SendToRecycleBin(string path)
|
||||
{
|
||||
// pFrom은 null-terminated + 추가 null 필요
|
||||
var op = new SHFILEOPSTRUCT
|
||||
{
|
||||
hwnd = new WindowInteropHelper(this).Handle,
|
||||
wFunc = FO_DELETE,
|
||||
pFrom = path + '\0',
|
||||
fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT,
|
||||
};
|
||||
int result = SHFileOperation(ref op);
|
||||
if (result != 0)
|
||||
throw new System.ComponentModel.Win32Exception(result, $"SHFileOperation 실패 (코드 {result})");
|
||||
}
|
||||
|
||||
// ─── 대형 텍스트 / 클립보드 외부 뷰어 ──────────────────────────────────
|
||||
|
||||
private void ShowLargeType()
|
||||
{
|
||||
// 클립보드 항목 → 시스템 클립보드에 자동 복사 + 외부 앱에서 열기
|
||||
if (_vm.SelectedItem?.Data is Services.ClipboardEntry clipEntry)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 자동 클립보드 복사 억제 (히스토리 중복 방지)
|
||||
CurrentApp?.ClipboardHistoryService?.SuppressNextCapture();
|
||||
|
||||
if (!clipEntry.IsText && clipEntry.Image != null)
|
||||
{
|
||||
// 원본 이미지가 있으면 원본 사용, 없으면 썸네일 사용
|
||||
var originalImg = Services.ClipboardHistoryService.LoadOriginalImage(clipEntry.OriginalImagePath);
|
||||
var imgToUse = originalImg ?? clipEntry.Image;
|
||||
|
||||
// 시스템 클립보드에 원본 복사
|
||||
Clipboard.SetImage(imgToUse);
|
||||
|
||||
// 이미지: PNG로 저장 → 기본 이미지 뷰어
|
||||
string path;
|
||||
if (!string.IsNullOrEmpty(clipEntry.OriginalImagePath) &&
|
||||
System.IO.File.Exists(clipEntry.OriginalImagePath))
|
||||
{
|
||||
path = clipEntry.OriginalImagePath; // 원본 파일 직접 열기
|
||||
}
|
||||
else
|
||||
{
|
||||
path = Services.TempFileService.CreateTempFile("clip_image", ".png");
|
||||
var encoder = new System.Windows.Media.Imaging.PngBitmapEncoder();
|
||||
encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(imgToUse));
|
||||
using var fs = new System.IO.FileStream(path, System.IO.FileMode.Create);
|
||||
encoder.Save(fs);
|
||||
}
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path) { UseShellExecute = true });
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(clipEntry.Text))
|
||||
{
|
||||
// 시스템 클립보드에 텍스트 복사
|
||||
Clipboard.SetText(clipEntry.Text);
|
||||
|
||||
// 텍스트: txt로 저장 → 메모장
|
||||
var path = Services.TempFileService.CreateTempFile("clip_text", ".txt");
|
||||
System.IO.File.WriteAllText(path, clipEntry.Text, System.Text.Encoding.UTF8);
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo("notepad.exe", $"\"{path}\"") { UseShellExecute = true });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Services.LogService.Warn($"클립보드 외부 뷰어 실패: {ex.Message}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var text = _vm.GetLargeTypeText();
|
||||
if (string.IsNullOrWhiteSpace(text)) return;
|
||||
new LargeTypeWindow(text).Show();
|
||||
}
|
||||
|
||||
// ─── 마우스 클릭 처리 ───────────────────────────────────────────────────
|
||||
|
||||
/// <summary>이미 선택된 아이템을 클릭하면 Execute, 아직 선택되지 않은 아이템 클릭은 선택만.</summary>
|
||||
private SDK.LauncherItem? _lastClickedItem;
|
||||
private DateTime _lastClickTime;
|
||||
|
||||
private void ResultList_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// 클릭한 ListViewItem 찾기
|
||||
var dep = e.OriginalSource as DependencyObject;
|
||||
while (dep != null && dep is not System.Windows.Controls.ListViewItem)
|
||||
dep = System.Windows.Media.VisualTreeHelper.GetParent(dep);
|
||||
if (dep is not System.Windows.Controls.ListViewItem lvi) return;
|
||||
|
||||
var clickedItem = lvi.Content as SDK.LauncherItem;
|
||||
if (clickedItem == null) return;
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var timeSinceLastClick = (now - _lastClickTime).TotalMilliseconds;
|
||||
|
||||
if (_lastClickedItem == clickedItem && timeSinceLastClick < 600)
|
||||
{
|
||||
// 같은 아이템을 짧은 간격으로 재클릭 → 액션 모드 또는 실행
|
||||
if (!_vm.IsActionMode && _vm.CanEnterActionMode())
|
||||
{
|
||||
_vm.EnterActionMode(clickedItem);
|
||||
e.Handled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = _vm.ExecuteSelectedAsync();
|
||||
e.Handled = true;
|
||||
}
|
||||
_lastClickedItem = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// 첫 번째 클릭 → 선택만
|
||||
_lastClickedItem = clickedItem;
|
||||
_lastClickTime = now;
|
||||
}
|
||||
|
||||
private void ResultList_MouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_ = _vm.ExecuteSelectedAsync();
|
||||
}
|
||||
|
||||
// ─── 창 이벤트 / 스크롤 / 알림 ─────────────────────────────────────────
|
||||
|
||||
private void Window_Deactivated(object sender, EventArgs e)
|
||||
{
|
||||
// 설정 › 기능 › "포커스 잃으면 닫기"가 켜진 경우에만 자동 숨김
|
||||
if (_vm.CloseOnFocusLost) Hide();
|
||||
}
|
||||
|
||||
private void ScrollToSelected()
|
||||
{
|
||||
if (_vm.SelectedItem != null)
|
||||
ResultList.ScrollIntoView(_vm.SelectedItem);
|
||||
}
|
||||
|
||||
private void ShowNotification(string message)
|
||||
{
|
||||
// 시스템 트레이 토스트 알림 표시
|
||||
// App.xaml.cs의 TrayIcon을 통해 처리
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user