Files
AX-Copilot/src/AxCopilot/Views/LauncherWindow.ShortcutHelp.cs
lacvet d6d5f518d0 [Phase L2-4/L2-5] 클립보드 OCR 이미지 검색 + Ctrl+Click 다중 선택
Phase L2-4: 클립보드 이미지 OCR 텍스트 추출 및 검색
- AxCopilot.csproj: TFM net8.0-windows → net8.0-windows10.0.17763.0 (Windows OCR API 활성화)
- ClipboardEntry: OcrText 프로퍼티 추가 (set), Preview → OCR 텍스트 우선 표시 (72자 상한)
- SavedClipEntry: OcrText 직렬화 필드 추가, BuildSnapshot/LoadHistory 연동
- ClipboardHistoryService.OnClipboardUpdate: 이미지 저장 후 백그라운드 OCR 트리거
  (EnableOcrSearch 설정 체크, capturedEntry.OcrText 비동기 갱신)
- ClipboardHistoryService.ImageCache.cs: ExtractOcrTextAsync() 추가
  (WinRT BitmapDecoder → SoftwareBitmap → OcrEngine.RecognizeAsync, 5,000자 상한)
  WinRT 별칭(WinBitmapDecoder, WinSoftwareBitmap 등) 으로 WPF 네임스페이스 충돌 방지
- AppSettings.Models.cs: ClipboardHistorySettings.EnableOcrSearch (default=true)
- ClipboardHistoryHandler.GetItemsAsync: OcrText 포함 검색, 'OCR ·' 표시 배지

Phase L2-5: Ctrl+Click 클립보드 항목 다중 선택
- LauncherWindow.Shell.cs: ResultList_PreviewMouseLeftButtonUp에 Ctrl+Click 분기 추가
  (IsClipboardMode + Ctrl 조합 시 ToggleMergeItem 호출, 기존 단일 선택 흐름 유지)
- LauncherWindow.ShortcutHelp.cs: Ctrl+Click / Shift+↑↓ / 병합 단축키 도움말 추가

빌드: 경고 0, 오류 0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 10:18:45 +09:00

152 lines
5.7 KiB
C#

using System.Windows;
using System.Windows.Input;
using AxCopilot.Services;
using AxCopilot.ViewModels;
namespace AxCopilot.Views;
public partial class LauncherWindow
{
// ─── 단축키 도움말 + 토스트 + 특수 액션 ────────────────────────────────────
/// <summary>단축키 도움말 팝업</summary>
private void ShowShortcutHelp()
{
var lines = new[]
{
"[ 전역 ]",
"Alt+Space AX Commander 열기/닫기",
"",
"[ 탐색 ]",
"↑ / ↓ 결과 이동",
"Enter 선택 실행",
"Tab 자동완성",
"→ 액션 모드",
"Escape 닫기 / 뒤로",
"",
"[ 기능 ]",
"F1 도움말",
"F2 파일 이름 바꾸기",
"F5 인덱스 새로 고침",
"Delete 항목 제거",
"Ctrl+, 설정",
"Ctrl+L 입력 초기화",
"Ctrl+C 이름 복사",
"Ctrl+H 클립보드 히스토리",
"Ctrl+R 최근 실행",
"Ctrl+B 즐겨찾기",
"Ctrl+K 이 도움말",
"Ctrl+1~9 N번째 실행",
"Ctrl+Shift+C 경로 복사",
"Ctrl+Shift+E 탐색기에서 열기",
"Ctrl+Enter 관리자 실행",
"Alt+Enter 속성 보기",
"Shift+Enter 대형 텍스트 / 이미지 미리보기 (#)",
"Ctrl+Click 클립보드 항목 다중 선택 (#)",
"Shift+↑/↓ 클립보드 항목 선택/해제 (#)",
"Shift+Enter 선택 항목 병합 (MergeCount > 0)",
};
CustomMessageBox.Show(
string.Join("\n", lines),
"AX Commander — 단축키 도움말",
MessageBoxButton.OK,
MessageBoxImage.Information);
}
// ─── 토스트 알림 ──────────────────────────────────────────────────────────
/// <summary>오버레이 토스트 표시 (페이드인 → 2초 대기 → 페이드아웃)</summary>
private void ShowToast(string message, string icon = "\uE73E")
{
ToastText.Text = message;
ToastIcon.Text = icon;
ToastOverlay.Visibility = Visibility.Visible;
ToastOverlay.Opacity = 0;
// 페이드인
var fadeIn = (System.Windows.Media.Animation.Storyboard)FindResource("ToastFadeIn");
fadeIn.Begin(this);
_indexStatusTimer?.Stop();
_indexStatusTimer = new System.Windows.Threading.DispatcherTimer
{
Interval = TimeSpan.FromSeconds(2)
};
_indexStatusTimer.Tick += (_, _) =>
{
_indexStatusTimer.Stop();
// 페이드아웃 후 Collapsed
var fadeOut = (System.Windows.Media.Animation.Storyboard)FindResource("ToastFadeOut");
EventHandler? onCompleted = null;
onCompleted = (__, ___) =>
{
fadeOut.Completed -= onCompleted;
ToastOverlay.Visibility = Visibility.Collapsed;
};
fadeOut.Completed += onCompleted;
fadeOut.Begin(this);
};
_indexStatusTimer.Start();
}
// ─── 특수 액션 처리 ───────────────────────────────────────────────────────
/// <summary>
/// 액션 모드에서 특수 처리가 필요한 동작(삭제/이름변경)을 처리합니다.
/// 처리되면 true 반환 → ExecuteSelectedAsync 호출 생략.
/// </summary>
private bool TryHandleSpecialAction()
{
if (_vm.SelectedItem?.Data is not AxCopilot.ViewModels.FileActionData actionData)
return false;
switch (actionData.Action)
{
case AxCopilot.ViewModels.FileAction.DeleteToRecycleBin:
{
var path = actionData.Path;
var name = System.IO.Path.GetFileName(path);
var r = CustomMessageBox.Show(
$"'{name}'\n\n이 항목을 휴지통으로 보내겠습니까?",
"AX Copilot — 삭제 확인",
MessageBoxButton.OKCancel,
MessageBoxImage.Warning);
if (r == MessageBoxResult.OK)
{
try
{
SendToRecycleBin(path);
_vm.ExitActionMode();
ShowToast("휴지통으로 이동됨", "\uE74D");
}
catch (Exception ex)
{
CustomMessageBox.Show($"삭제 실패: {ex.Message}", "오류",
MessageBoxButton.OK, MessageBoxImage.Error);
}
}
else
{
_vm.ExitActionMode();
}
return true;
}
case AxCopilot.ViewModels.FileAction.Rename:
{
var path = actionData.Path;
_vm.ExitActionMode();
_vm.InputText = $"rename {path}";
Dispatcher.BeginInvoke(() => { InputBox.CaretIndex = InputBox.Text.Length; },
System.Windows.Threading.DispatcherPriority.Input);
return true;
}
default:
return false;
}
}
}