[Phase L2-3] 클립보드 이미지 미리보기 창 구현
신규 파일: - ClipboardImagePreviewWindow.xaml (신규, 140줄): WindowStyle=None, 커스텀 타이틀바, 이미지 ScaleTransform 줌, 하단 툴바(축소/줌레이블/확대 / 1:1 / 창맞춤 / 저장 / 복사) - ClipboardImagePreviewWindow.xaml.cs (신규, 173줄): 원본 해상도 이미지 로드(OriginalImagePath → 썸네일 폴백), FitToWindow() 초기 자동 맞춤, ApplyZoom(ScaleTransform), Ctrl+휠 줌, 키보드 단축키(+/-/0/F/Esc/Ctrl+C/Ctrl+S), PNG·JPEG·BMP 저장(SaveFileDialog), NotificationCenterService 피드백 변경 파일: - LauncherWindow.Keyboard.cs: Shift+Enter 시 ClipboardEntry 이미지 항목 → 미리보기 창 열기(기존 폴더열기 분기 전에 추가) - LauncherWindow.ShortcutHelp.cs: "Shift+Enter — 대형 텍스트 / 이미지 미리보기 (#)" 안내 갱신 빌드: 경고 0, 오류 0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
225
src/AxCopilot/Views/ClipboardImagePreviewWindow.xaml
Normal file
225
src/AxCopilot/Views/ClipboardImagePreviewWindow.xaml
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
<Window x:Class="AxCopilot.Views.ClipboardImagePreviewWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Title="AX Copilot — 이미지 미리보기"
|
||||||
|
Width="820" Height="640"
|
||||||
|
WindowStyle="None" AllowsTransparency="True"
|
||||||
|
Background="Transparent"
|
||||||
|
WindowStartupLocation="CenterScreen"
|
||||||
|
ResizeMode="CanResizeWithGrip"
|
||||||
|
MinWidth="320" MinHeight="260">
|
||||||
|
|
||||||
|
<!-- Phase L2-3: 클립보드 이미지 미리보기 창 -->
|
||||||
|
|
||||||
|
<Border Background="{DynamicResource LauncherBackground}" CornerRadius="12"
|
||||||
|
BorderBrush="{DynamicResource BorderColor}" BorderThickness="1"
|
||||||
|
Margin="6">
|
||||||
|
<Border.Effect>
|
||||||
|
<DropShadowEffect BlurRadius="20" ShadowDepth="4" Opacity="0.3" Color="Black" Direction="270"/>
|
||||||
|
</Border.Effect>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="44"/> <!-- 타이틀바 -->
|
||||||
|
<RowDefinition Height="*"/> <!-- 이미지 영역 -->
|
||||||
|
<RowDefinition Height="46"/> <!-- 하단 툴바 -->
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- ─── 타이틀바 ─────────────────────────────────────────────── -->
|
||||||
|
<Border Grid.Row="0" CornerRadius="12,12,0,0"
|
||||||
|
Background="{DynamicResource ItemBackground}"
|
||||||
|
MouseLeftButtonDown="TitleBar_MouseDown">
|
||||||
|
<Grid Margin="16,0,8,0">
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
<!-- 이미지 아이콘 -->
|
||||||
|
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="15"
|
||||||
|
Foreground="{DynamicResource AccentColor}"
|
||||||
|
VerticalAlignment="Center" Margin="0,1,10,0"/>
|
||||||
|
<TextBlock Text="이미지 미리보기"
|
||||||
|
FontSize="13" FontWeight="SemiBold"
|
||||||
|
Foreground="{DynamicResource PrimaryText}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock x:Name="SizeLabel" Text=""
|
||||||
|
FontSize="11" Foreground="{DynamicResource SecondaryText}"
|
||||||
|
VerticalAlignment="Center" Margin="12,0,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- 닫기 버튼 -->
|
||||||
|
<Border x:Name="BtnClose" HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||||
|
CornerRadius="4" Padding="8,4" Cursor="Hand"
|
||||||
|
MouseLeftButtonUp="BtnClose_Click">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="#40C05050"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="13"
|
||||||
|
Foreground="{DynamicResource SecondaryText}"/>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- ─── 이미지 영역 ────────────────────────────────────────────── -->
|
||||||
|
<ScrollViewer Grid.Row="1" x:Name="ImageScroll"
|
||||||
|
HorizontalScrollBarVisibility="Auto"
|
||||||
|
VerticalScrollBarVisibility="Auto"
|
||||||
|
Background="{DynamicResource LauncherBackground}"
|
||||||
|
PreviewMouseWheel="ImageScroll_PreviewMouseWheel">
|
||||||
|
<Grid HorizontalAlignment="Center" VerticalAlignment="Center" Margin="8">
|
||||||
|
<Image x:Name="PreviewImage"
|
||||||
|
RenderOptions.BitmapScalingMode="HighQuality"
|
||||||
|
Stretch="None"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||||
|
<Image.LayoutTransform>
|
||||||
|
<ScaleTransform x:Name="ZoomTransform" ScaleX="1" ScaleY="1"/>
|
||||||
|
</Image.LayoutTransform>
|
||||||
|
</Image>
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<!-- ─── 하단 툴바 ─────────────────────────────────────────────── -->
|
||||||
|
<Border Grid.Row="2" CornerRadius="0,0,12,12"
|
||||||
|
Background="{DynamicResource ItemBackground}"
|
||||||
|
BorderBrush="{DynamicResource BorderColor}" BorderThickness="0,1,0,0">
|
||||||
|
<Grid Margin="12,0">
|
||||||
|
|
||||||
|
<!-- 좌측: 줌 컨트롤 -->
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
|
|
||||||
|
<!-- 축소 버튼 -->
|
||||||
|
<Border CornerRadius="6" Padding="8,5" Cursor="Hand" Margin="0,0,2,0"
|
||||||
|
ToolTip="축소 (−)" MouseLeftButtonUp="BtnZoomOut_Click">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource ItemHoverBackground}"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="13"
|
||||||
|
Foreground="{DynamicResource PrimaryText}"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 줌 퍼센트 라벨 -->
|
||||||
|
<Border CornerRadius="4" Padding="6,4" Margin="2,0"
|
||||||
|
Background="{DynamicResource HintBackground}">
|
||||||
|
<TextBlock x:Name="ZoomLabel" Text="100%"
|
||||||
|
FontSize="11" FontWeight="Medium"
|
||||||
|
Foreground="{DynamicResource PrimaryText}"
|
||||||
|
MinWidth="44" TextAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 확대 버튼 -->
|
||||||
|
<Border CornerRadius="6" Padding="8,5" Cursor="Hand" Margin="2,0,6,0"
|
||||||
|
ToolTip="확대 (+)" MouseLeftButtonUp="BtnZoomIn_Click">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource ItemHoverBackground}"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="13"
|
||||||
|
Foreground="{DynamicResource PrimaryText}"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 구분선 -->
|
||||||
|
<Border Width="1" Height="20" Margin="2,0,8,0"
|
||||||
|
Background="{DynamicResource BorderColor}"/>
|
||||||
|
|
||||||
|
<!-- 실제 크기 -->
|
||||||
|
<Border CornerRadius="6" Padding="8,5" Cursor="Hand" Margin="0,0,2,0"
|
||||||
|
ToolTip="실제 크기 (0)" MouseLeftButtonUp="BtnActualSize_Click">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource ItemHoverBackground}"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
<TextBlock Text="1:1" FontSize="11" FontWeight="Bold"
|
||||||
|
Foreground="{DynamicResource SecondaryText}"/>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 창에 맞추기 -->
|
||||||
|
<Border CornerRadius="6" Padding="8,5" Cursor="Hand"
|
||||||
|
ToolTip="창에 맞추기 (F)" MouseLeftButtonUp="BtnFitToWindow_Click">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource ItemHoverBackground}"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="13"
|
||||||
|
Foreground="{DynamicResource SecondaryText}"/>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- 우측: 복사 / 저장 버튼 -->
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||||
|
|
||||||
|
<!-- 파일로 저장 -->
|
||||||
|
<Border CornerRadius="8" Padding="12,6" Cursor="Hand" Margin="0,0,8,0"
|
||||||
|
ToolTip="파일로 저장 (Ctrl+S)"
|
||||||
|
MouseLeftButtonUp="BtnSave_Click">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource ItemBackground}"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource ItemHoverBackground}"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="12"
|
||||||
|
Foreground="{DynamicResource SecondaryText}" VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text=" 저장" FontSize="12"
|
||||||
|
Foreground="{DynamicResource SecondaryText}" Margin="4,0,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 클립보드 복사 -->
|
||||||
|
<Border CornerRadius="8" Padding="12,6" Cursor="Hand"
|
||||||
|
ToolTip="클립보드에 복사 (Ctrl+C)"
|
||||||
|
MouseLeftButtonUp="BtnCopy_Click">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource AccentColor}"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Opacity" Value="0.85"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="12"
|
||||||
|
Foreground="White" VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text=" 복사" FontSize="12" Foreground="White" Margin="4,0,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Window>
|
||||||
231
src/AxCopilot/Views/ClipboardImagePreviewWindow.xaml.cs
Normal file
231
src/AxCopilot/Views/ClipboardImagePreviewWindow.xaml.cs
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using AxCopilot.Services;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
|
namespace AxCopilot.Views;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Phase L2-3: 클립보드 이미지 미리보기 창.
|
||||||
|
/// 원본 해상도 이미지 표시 + 확대/축소 + 클립보드 복사 + 파일 저장.
|
||||||
|
/// Shift+Enter (이미지 항목 선택 시) 또는 런처 내 미리보기 액션으로 열립니다.
|
||||||
|
/// </summary>
|
||||||
|
public partial class ClipboardImagePreviewWindow : Window
|
||||||
|
{
|
||||||
|
private readonly ClipboardEntry _entry;
|
||||||
|
private BitmapSource? _fullImage;
|
||||||
|
private double _zoomFactor = 1.0;
|
||||||
|
|
||||||
|
private const double ZoomStep = 0.25;
|
||||||
|
private const double MinZoom = 0.05;
|
||||||
|
private const double MaxZoom = 8.0;
|
||||||
|
|
||||||
|
public ClipboardImagePreviewWindow(ClipboardEntry entry)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_entry = entry;
|
||||||
|
KeyDown += OnKeyDown;
|
||||||
|
Loaded += OnLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 초기화 ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
LoadImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadImage()
|
||||||
|
{
|
||||||
|
// 원본 해상도 이미지 우선, 없으면 썸네일 폴백
|
||||||
|
_fullImage = ClipboardHistoryService.LoadOriginalImage(_entry.OriginalImagePath)
|
||||||
|
?? _entry.Image;
|
||||||
|
|
||||||
|
if (_fullImage == null)
|
||||||
|
{
|
||||||
|
SizeLabel.Text = "(이미지 없음)";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PreviewImage.Source = _fullImage;
|
||||||
|
SizeLabel.Text = $"{_fullImage.PixelWidth} × {_fullImage.PixelHeight} px";
|
||||||
|
|
||||||
|
// 창 레이아웃이 확정된 후 Fit 적용
|
||||||
|
Dispatcher.InvokeAsync(FitToWindow, System.Windows.Threading.DispatcherPriority.Loaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 줌 제어 ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void FitToWindow()
|
||||||
|
{
|
||||||
|
if (_fullImage == null) return;
|
||||||
|
var availW = ImageScroll.ActualWidth - 24;
|
||||||
|
var availH = ImageScroll.ActualHeight - 24;
|
||||||
|
if (availW <= 0 || availH <= 0) return;
|
||||||
|
|
||||||
|
var scaleX = availW / _fullImage.PixelWidth;
|
||||||
|
var scaleY = availH / _fullImage.PixelHeight;
|
||||||
|
_zoomFactor = Math.Min(1.0, Math.Min(scaleX, scaleY));
|
||||||
|
ApplyZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyZoom()
|
||||||
|
{
|
||||||
|
ZoomTransform.ScaleX = _zoomFactor;
|
||||||
|
ZoomTransform.ScaleY = _zoomFactor;
|
||||||
|
ZoomLabel.Text = $"{_zoomFactor * 100:F0}%";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 타이틀바 ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.LeftButton == MouseButtonState.Pressed)
|
||||||
|
DragMove();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 버튼 핸들러 ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void BtnClose_Click(object sender, MouseButtonEventArgs e) => Close();
|
||||||
|
|
||||||
|
private void BtnZoomIn_Click(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
_zoomFactor = Math.Min(MaxZoom, Math.Round(_zoomFactor + ZoomStep, 2));
|
||||||
|
ApplyZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnZoomOut_Click(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
_zoomFactor = Math.Max(MinZoom, Math.Round(_zoomFactor - ZoomStep, 2));
|
||||||
|
ApplyZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnActualSize_Click(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
_zoomFactor = 1.0;
|
||||||
|
ApplyZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnFitToWindow_Click(object sender, MouseButtonEventArgs e)
|
||||||
|
=> FitToWindow();
|
||||||
|
|
||||||
|
private void BtnCopy_Click(object sender, MouseButtonEventArgs e)
|
||||||
|
=> CopyToClipboard();
|
||||||
|
|
||||||
|
private void BtnSave_Click(object sender, MouseButtonEventArgs e)
|
||||||
|
=> SaveToFile();
|
||||||
|
|
||||||
|
// ─── 스크롤 휠 줌 ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void ImageScroll_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
||||||
|
{
|
||||||
|
if ((Keyboard.Modifiers & ModifierKeys.Control) == 0) return;
|
||||||
|
e.Handled = true;
|
||||||
|
|
||||||
|
var delta = e.Delta > 0 ? ZoomStep : -ZoomStep;
|
||||||
|
_zoomFactor = Math.Clamp(Math.Round(_zoomFactor + delta, 2), MinZoom, MaxZoom);
|
||||||
|
ApplyZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 키보드 단축키 ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void OnKeyDown(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
switch (e.Key)
|
||||||
|
{
|
||||||
|
case Key.Escape:
|
||||||
|
Close();
|
||||||
|
e.Handled = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.Add:
|
||||||
|
case Key.OemPlus:
|
||||||
|
_zoomFactor = Math.Min(MaxZoom, Math.Round(_zoomFactor + ZoomStep, 2));
|
||||||
|
ApplyZoom();
|
||||||
|
e.Handled = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.Subtract:
|
||||||
|
case Key.OemMinus:
|
||||||
|
_zoomFactor = Math.Max(MinZoom, Math.Round(_zoomFactor - ZoomStep, 2));
|
||||||
|
ApplyZoom();
|
||||||
|
e.Handled = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.D0:
|
||||||
|
case Key.NumPad0:
|
||||||
|
_zoomFactor = 1.0;
|
||||||
|
ApplyZoom();
|
||||||
|
e.Handled = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.F:
|
||||||
|
FitToWindow();
|
||||||
|
e.Handled = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.C when (Keyboard.Modifiers & ModifierKeys.Control) != 0:
|
||||||
|
CopyToClipboard();
|
||||||
|
e.Handled = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Key.S when (Keyboard.Modifiers & ModifierKeys.Control) != 0:
|
||||||
|
SaveToFile();
|
||||||
|
e.Handled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── 내부 동작 ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void CopyToClipboard()
|
||||||
|
{
|
||||||
|
if (_fullImage == null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Clipboard.SetImage(_fullImage);
|
||||||
|
NotificationCenterService.Show("클립보드", "이미지를 클립보드에 복사했습니다.",
|
||||||
|
NotificationType.Success);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
NotificationCenterService.Show("복사 실패", ex.Message, NotificationType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveToFile()
|
||||||
|
{
|
||||||
|
if (_fullImage == null) return;
|
||||||
|
|
||||||
|
var dlg = new SaveFileDialog
|
||||||
|
{
|
||||||
|
Title = "이미지 저장",
|
||||||
|
Filter = "PNG 이미지 (*.png)|*.png|JPEG 이미지 (*.jpg)|*.jpg|BMP 이미지 (*.bmp)|*.bmp",
|
||||||
|
FileName = $"clipboard_{DateTime.Now:yyyyMMdd_HHmmss}",
|
||||||
|
DefaultExt = "png",
|
||||||
|
};
|
||||||
|
if (dlg.ShowDialog(this) != true) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BitmapEncoder encoder = Path.GetExtension(dlg.FileName).ToLowerInvariant() switch
|
||||||
|
{
|
||||||
|
".jpg" or ".jpeg" => new JpegBitmapEncoder { QualityLevel = 95 },
|
||||||
|
".bmp" => new BmpBitmapEncoder(),
|
||||||
|
_ => new PngBitmapEncoder(),
|
||||||
|
};
|
||||||
|
encoder.Frames.Add(BitmapFrame.Create(_fullImage));
|
||||||
|
using var fs = File.OpenWrite(dlg.FileName);
|
||||||
|
encoder.Save(fs);
|
||||||
|
|
||||||
|
NotificationCenterService.Show("저장 완료", Path.GetFileName(dlg.FileName),
|
||||||
|
NotificationType.Success);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
NotificationCenterService.Show("저장 실패", ex.Message, NotificationType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,6 +50,16 @@ public partial class LauncherWindow
|
|||||||
|
|
||||||
if (shift)
|
if (shift)
|
||||||
{
|
{
|
||||||
|
// Phase L2-3: 클립보드 이미지 항목 → 이미지 미리보기 창 열기
|
||||||
|
if (_vm.SelectedItem?.Data is AxCopilot.Services.ClipboardEntry imgEntry && !imgEntry.IsText)
|
||||||
|
{
|
||||||
|
e.Handled = true;
|
||||||
|
Hide();
|
||||||
|
var previewWin = new ClipboardImagePreviewWindow(imgEntry);
|
||||||
|
previewWin.Show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// 퍼지 파일 검색 결과: Shift+Enter → 파일이 있는 폴더 열기
|
// 퍼지 파일 검색 결과: Shift+Enter → 파일이 있는 폴더 열기
|
||||||
if (_vm.SelectedItem?.Data is AxCopilot.Services.IndexEntry shiftEntry)
|
if (_vm.SelectedItem?.Data is AxCopilot.Services.IndexEntry shiftEntry)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public partial class LauncherWindow
|
|||||||
"Ctrl+Shift+E 탐색기에서 열기",
|
"Ctrl+Shift+E 탐색기에서 열기",
|
||||||
"Ctrl+Enter 관리자 실행",
|
"Ctrl+Enter 관리자 실행",
|
||||||
"Alt+Enter 속성 보기",
|
"Alt+Enter 속성 보기",
|
||||||
"Shift+Enter 대형 텍스트",
|
"Shift+Enter 대형 텍스트 / 이미지 미리보기 (#)",
|
||||||
};
|
};
|
||||||
|
|
||||||
CustomMessageBox.Show(
|
CustomMessageBox.Show(
|
||||||
|
|||||||
Reference in New Issue
Block a user