WIP X-plore 비교 5-1 ⑥ 파일 탐색기 단축키 바꾸기 (관리자 지시 10/21): 환경 설정 > 파일 탐색기 [파일 탐색기 단축키] (키보드가 있는 PC 에서만) - 동작 19개마다 지금 키 · [바꾸기] (다음에 누른 키, Ctrl · Shift · Alt 함께) · [처음 값으로] · [단축키 없음] · [모두 처음 값으로]. 처음 키는 83 의 스타일별 키 그대로 (바꾼 것만 설정 explorerKeyOverrides 에), 바꾼 키는 모든 스타일에 쓰이고 다른 동작과 겹치면 그 동작은 그 키를 잃음 (창에서 먼저 알림), Esc 는 바꿀 수 없음, Rsync 화면은 늘 같은 키 · 시험 (처음 키 = 예전 키 · 바꾸기 · 겹침 · 저장 꼴 · 탐색기에서 바꾼 키 동작 · 설정 화면) (검토 대기)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>xplore_p3b
parent
7dfbc400d1
commit
b5434683f4
|
|
@ -65,6 +65,7 @@
|
|||
"(받는 쪽이 더 새것 - 건너뜀)": "(receiving side is newer - skipped)",
|
||||
"(비어 있음)": "(empty)",
|
||||
"(설명 없음)": "(no description)",
|
||||
"(없음)": "(none)",
|
||||
"(지워짐)": "(deleted)",
|
||||
"(폴더 · 안의 {0}개를 지우고 같은 이름의 파일로)": "(folder and the {0} items inside are deleted and replaced by the file of the same name)",
|
||||
"(현재 방식은 옵션 없음)": "(no options for the built-in method)",
|
||||
|
|
@ -201,6 +202,7 @@
|
|||
"Edge 에서 가져옴": "Imported from Edge",
|
||||
"Edge 엔진으로 바꾸기": "Switch to the Edge engine",
|
||||
"Edge: Windows 에 들어 있어 따로 설치하지 않습니다. 여기서 YouTube 에 로그인하면 다운로드에도 그 로그인이 쓰입니다.\nChrome: 고르면 약 {0}MB 를 내려받고 다시 시작한 뒤 쓸 수 있습니다 (Google 로그인은 막힐 수 있음).{1}{2}": "Edge: part of Windows, nothing to install. If you sign in to YouTube here, downloads use that sign-in too.\nChrome: if chosen, about {0}MB is downloaded and it can be used after a restart (Google sign-in may be blocked).{1}{2}",
|
||||
"Esc 는 선택 · 빠른 필터를 닫는 키라 쓸 수 없습니다": "Esc closes selection and the quick filter, so it cannot be used",
|
||||
"FFmpeg · yt-dlp · aria2 · Deno 가 없으면 내려받아 설치합니다": "Downloads and installs FFmpeg · yt-dlp · aria2 · Deno if missing",
|
||||
"FFmpeg 준비됨: {0}{1}": "FFmpeg ready: {0}{1}",
|
||||
"FTP 서버 \"{0}\" 을(를) 지울까요?": "Delete FTP server \"{0}\"?",
|
||||
|
|
@ -498,6 +500,7 @@
|
|||
"{0} (기본): {1}": "{0} (default): {1}",
|
||||
"{0} (동영상 {1})": "{0} (videos: {1})",
|
||||
"{0} - {1} (약 {2}MB)": "{0} - {1} (about {2}MB)",
|
||||
"{0} - 단축키": "{0} - shortcut",
|
||||
"{0} / {1} 읽음": "{0} / {1} read",
|
||||
"{0} / {1} 확인": "{0} / {1} checked",
|
||||
"{0} / {1}장": "{0} / {1} images",
|
||||
|
|
@ -993,6 +996,7 @@
|
|||
"다른 창에서 열기": "Open in other pane",
|
||||
"다른 창으로 끌어 놓았을 때. 어느 쪽이든 복사 · 이동 확인 창 (같은 이름 등) 은 그대로 뜹니다": "When you drop onto the other pane. Either way, the copy/move confirmation (same names etc.) still appears",
|
||||
"다른 창으로 복사": "Copy to other pane",
|
||||
"다른 창으로 옮겨 가기": "Switch to the other pane",
|
||||
"다른 창으로 이동": "Move to other pane",
|
||||
"다른 폴더": "Other folder",
|
||||
"다른 폴더로 이동": "Move to another folder",
|
||||
|
|
@ -1050,6 +1054,7 @@
|
|||
"단계": "Steps",
|
||||
"단어 첫 글자만 크게 (Abc)": "Capitalize words (Abc)",
|
||||
"단축키 - Total Commander: F5 복사 · F6 이동 · F7 새 폴더 · F8 지우기 · Insert/Space 표시 · Ctrl+R 새로 고침. 그 밖: Ctrl+C · X · V (복사 · 잘라내기 · 붙여넣기) · Ctrl+A 모두 표시 · F5 새로 고침": "Shortcuts - Total Commander: F5 copy · F6 move · F7 new folder · F8 delete · Insert/Space mark · Ctrl+R refresh. Others: Ctrl+C · X · V (copy · cut · paste) · Ctrl+A mark all · F5 refresh",
|
||||
"단축키 없음": "No shortcut",
|
||||
"단축키 해제": "Unregister hotkey",
|
||||
"닫기": "Close",
|
||||
"닫힌 문서입니다": "The document is closed",
|
||||
|
|
@ -1262,10 +1267,12 @@
|
|||
"모두 작게 (abc)": "lowercase (abc)",
|
||||
"모두 종료": "Quit all",
|
||||
"모두 지우기": "Clear all",
|
||||
"모두 처음 값으로": "Reset all",
|
||||
"모두 초기화": "Reset all",
|
||||
"모두 취소 ({0}개)": "Cancel all ({0})",
|
||||
"모두 취소 (대기 {0}개 포함)": "Cancel all (including {0} waiting)",
|
||||
"모두 크게 (ABC)": "UPPERCASE (ABC)",
|
||||
"모두 표시": "Mark all",
|
||||
"모두 풀기": "Extract all",
|
||||
"모든 작업 취소": "Cancel all jobs",
|
||||
"목": "Thu",
|
||||
|
|
@ -1329,6 +1336,7 @@
|
|||
"바꾸면 바로 저장됩니다": "Saved automatically",
|
||||
"바꾸지 못했습니다: {0}": "Could not change it: {0}",
|
||||
"바꾼 PDF 를 저장하지 못했습니다 (저장 공간을 확인해 주세요)": "Couldn't save the converted PDF (check storage space)",
|
||||
"바꾼 것 {0}개": "{0} changed",
|
||||
"바꾼 것이 없습니다 (원본 그대로)": "Nothing changed (original as is)",
|
||||
"바꿀 글": "Replace with",
|
||||
"바꿀 글 (비우면 지움)": "Replace with (empty = remove)",
|
||||
|
|
@ -1543,6 +1551,7 @@
|
|||
"빈 폴더입니다": "This folder is empty",
|
||||
"빠르게": "Fast",
|
||||
"빠른 디코더 (TAESD)": "Fast decoder (TAESD)",
|
||||
"빠른 보기": "Quick look",
|
||||
"빠른 필터": "Quick filter",
|
||||
"빼고 싶은 것 (선택)": "What to avoid (optional)",
|
||||
"사라지는 것": "What is lost",
|
||||
|
|
@ -1703,6 +1712,7 @@
|
|||
"쉬는 중": "Idle",
|
||||
"스웨덴어": "Swedish",
|
||||
"스타일": "Style",
|
||||
"스타일대로 (바꾼 것 없음)": "As the style (nothing changed)",
|
||||
"스페인어": "Spanish",
|
||||
"시간 지정 (cron)": "Scheduled (cron)",
|
||||
"시간 형식 오류 {0}곳": "{0} time format errors",
|
||||
|
|
@ -1765,6 +1775,7 @@
|
|||
"쓸 수 없는 글자 (:)": "Invalid character (:)",
|
||||
"쓸 수 없는 이름": "Invalid name",
|
||||
"쓸 수 없는 이름입니다": "This name cannot be used",
|
||||
"쓸 키를 누르세요 (Ctrl · Shift · Alt 와 함께 눌러도 됩니다)": "Press the key to use (you can combine it with Ctrl, Shift or Alt)",
|
||||
"아랍어": "Arabic",
|
||||
"아래 갤러리 줄 보이기": "Show the gallery strip",
|
||||
"아래 갤러리 줄 숨기기": "Hide the gallery strip",
|
||||
|
|
@ -1897,6 +1908,7 @@
|
|||
"열어 본 때: {0}": "Accessed: {0}",
|
||||
"영구 삭제": "Delete permanently",
|
||||
"영구 삭제 ({0}개, 되돌릴 수 없음)": "Delete permanently ({0}, cannot be undone)",
|
||||
"영구 삭제 (휴지통을 거치지 않음)": "Delete permanently (skip the trash)",
|
||||
"영구 삭제하면 되살릴 수 없습니다.": "Permanently deleted items can't be restored.",
|
||||
"영상": "Video",
|
||||
"영상 {0}": "Video {0}",
|
||||
|
|
@ -2198,6 +2210,7 @@
|
|||
"이름을 바꿔 풂 {0}개": "{0} extracted with new names",
|
||||
"이름을 찾을 수 없습니다: {0} (DNS {1})": "Name not found: {0} (DNS {1})",
|
||||
"이름이 너무 김 (255자 넘음)": "Name too long (over 255 characters)",
|
||||
"이미 \"{0}\" 에 쓰는 키입니다 - 저장하면 그 동작의 이 키는 없어집니다": "This key is already used for \"{0}\" - if you save, that action loses this key",
|
||||
"이미 그리는 중입니다": "Already drawing",
|
||||
"이미 동영상 목록에 있습니다 ({0}개).": "Already in the video list ({0}).",
|
||||
"이미 만든 MKV 가 있습니다 ({0}개)": "Some MKVs already exist ({0})",
|
||||
|
|
@ -2498,6 +2511,7 @@
|
|||
"지금 보고 있는 동영상의 {0} (여러 개는 목록에서 체크)": "{0} for the video you are viewing (check several in the list)",
|
||||
"지금 설정 그대로": "Keep current settings",
|
||||
"지금 설치": "Install now",
|
||||
"지금 스타일 ({0}) 의 키입니다. 바꾼 키는 모든 스타일에 쓰이고, 다른 동작과 겹치면 그 동작의 키는 없어집니다. Esc 는 바꿀 수 없습니다": "Keys of the current style ({0}). Changed keys apply to all styles; if one clashes with another action, that action loses the key. Esc cannot be changed",
|
||||
"지금 실행 중입니다. 멈추고 lsync (실시간 동기화) 로 옮길까요? 남은 것은 lsync 가 이어서 맞춥니다.": "It is running now. Stop it and move to lsync (live sync)? lsync will sync the rest.",
|
||||
"지금 쓰는 버전: v{0}": "Current version: v{0}",
|
||||
"지금 쓰는 버전: v{0} → 새 버전: v{1}": "Current version: v{0} → new version: v{1}",
|
||||
|
|
@ -2592,6 +2606,7 @@
|
|||
"채우기 (가운데 잘라냄)": "Fill (crop the center)",
|
||||
"처리 장치": "Processor",
|
||||
"처음 · 🏠 주소 (기본: Tor 연결 확인 페이지)": "Start / 🏠 address (default: the Tor connection check page)",
|
||||
"처음 값으로": "Reset",
|
||||
"처음 보는 서버 인증서입니다": "This server certificate is new",
|
||||
"처음 보는 서버 인증서입니다 (자체 서명). 지문을 확인한 뒤 믿을지 정하세요": "This server certificate is new (self-signed). Check the fingerprint, then decide whether to trust it",
|
||||
"처음 사용 시 내려받기 {0}": "Downloads {0} on first use",
|
||||
|
|
@ -2724,6 +2739,7 @@
|
|||
"파일 탐색기 (지금 여기)": "File explorer (you are here)",
|
||||
"파일 탐색기 가운데에 [모니터링] 버튼: 복사 · 이동을 기억해 진행 · 대상 디스크 용량 · 옵션을 보여 주고, 옵션을 고쳐 다시 실행 · 재개하거나 lsync 로 옮깁니다 (복사 · rsync 와 lsync 를 따로).": "[Monitor] button in the middle of the file explorer: remembers copies · moves and shows progress · target disk space · options; change options to run again · resume, or move to lsync (copy · rsync and lsync shown separately).",
|
||||
"파일 탐색기 누르기: 한 번 누르면 선택 → 한 번 누르면 바로 실행. 되돌리기: 환경 설정 > 파일 탐색기 > 누르기": "File explorer tap: single tap selects → single tap opens. To revert: Settings > File explorer > Tap",
|
||||
"파일 탐색기 단축키": "File explorer shortcuts",
|
||||
"파일 탐색기 두 창 배치: 화면 모양 따라 → 좌우. 되돌리기: 파일 탐색기의 [좌우 ⇆ / 위아래 ⇅] 버튼 또는 창 배치": "File explorer two-pane layout: follow screen shape → side by side. To revert: the [Columns ⇆ / Rows ⇅] button in the file explorer, or Pane layout",
|
||||
"파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다": "Only the name and size text in the file explorer list. Multiplied by the app screen size and the device text size",
|
||||
"파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다": "Row height in the file explorer list. Compact rows may be hard to tap with a finger",
|
||||
|
|
@ -2808,10 +2824,12 @@
|
|||
"폴더만 lsync 로 옮길 수 있습니다.": "Only folders can be moved to lsync.",
|
||||
"폴더와 안의 것 전체 (기본)": "The folder and everything inside (default)",
|
||||
"폴란드어": "Polish",
|
||||
"표시": "Mark",
|
||||
"표시 (여러 개 고르기)": "Mark (select several)",
|
||||
"표시 이름": "Display name",
|
||||
"표시 이름은 바로 고칠 수 있습니다. 동영상 줄을 세 번 누르면 첫 번째 버튼의 폴더로 옮깁니다": "You can edit display names right here. Pressing a video row three times moves it to the first button's folder",
|
||||
"표시 지우기": "Clear marks",
|
||||
"표시하고 다음 줄": "Mark and move down",
|
||||
"표준": "Standard",
|
||||
"표준 입력 (-) 은 쓸 수 없습니다": "Standard input (-) cannot be used",
|
||||
"푸는 중": "Extracting",
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@
|
|||
"(받는 쪽이 더 새것 - 건너뜀)": "(受け取る側が新しい - スキップ)",
|
||||
"(비어 있음)": "(空)",
|
||||
"(설명 없음)": "(説明なし)",
|
||||
"(없음)": "(なし)",
|
||||
"(지워짐)": "(削除)",
|
||||
"(폴더 · 안의 {0}개를 지우고 같은 이름의 파일로)": "(フォルダーと中の {0} 件を削除して同名のファイルに)",
|
||||
"(현재 방식은 옵션 없음)": "(現在の方式にはオプションなし)",
|
||||
|
|
@ -201,6 +202,7 @@
|
|||
"Edge 에서 가져옴": "Edge から取り込み",
|
||||
"Edge 엔진으로 바꾸기": "Edge エンジンに切り替える",
|
||||
"Edge: Windows 에 들어 있어 따로 설치하지 않습니다. 여기서 YouTube 에 로그인하면 다운로드에도 그 로그인이 쓰입니다.\nChrome: 고르면 약 {0}MB 를 내려받고 다시 시작한 뒤 쓸 수 있습니다 (Google 로그인은 막힐 수 있음).{1}{2}": "Edge: Windows に含まれているので別途インストール不要です。ここで YouTube にログインすると、ダウンロードにもそのログインが使われます。\nChrome: 選ぶと約 {0}MB をダウンロードし、再起動後に使えます(Google ログインはブロックされることがあります)。{1}{2}",
|
||||
"Esc 는 선택 · 빠른 필터를 닫는 키라 쓸 수 없습니다": "Esc は選択・クイックフィルターを閉じるキーのため使えません",
|
||||
"FFmpeg · yt-dlp · aria2 · Deno 가 없으면 내려받아 설치합니다": "FFmpeg・yt-dlp・aria2・Deno がなければダウンロードしてインストールします",
|
||||
"FFmpeg 준비됨: {0}{1}": "FFmpeg 準備完了: {0}{1}",
|
||||
"FTP 서버 \"{0}\" 을(를) 지울까요?": "FTP サーバー「{0}」を削除しますか?",
|
||||
|
|
@ -498,6 +500,7 @@
|
|||
"{0} (기본): {1}": "{0}(既定): {1}",
|
||||
"{0} (동영상 {1})": "{0} (動画 {1})",
|
||||
"{0} - {1} (약 {2}MB)": "{0} - {1}(約 {2}MB)",
|
||||
"{0} - 단축키": "{0} - ショートカット",
|
||||
"{0} / {1} 읽음": "{0} / {1} 読み込み済み",
|
||||
"{0} / {1} 확인": "{0} / {1} 確認",
|
||||
"{0} / {1}장": "{0} / {1} 枚",
|
||||
|
|
@ -993,6 +996,7 @@
|
|||
"다른 창에서 열기": "もう一方のペインで開く",
|
||||
"다른 창으로 끌어 놓았을 때. 어느 쪽이든 복사 · 이동 확인 창 (같은 이름 등) 은 그대로 뜹니다": "もう一方のウィンドウにドロップしたとき。どちらでもコピー・移動の確認 (同じ名前など) は表示されます",
|
||||
"다른 창으로 복사": "もう一方のペインへコピー",
|
||||
"다른 창으로 옮겨 가기": "もう一方のペインへ移動",
|
||||
"다른 창으로 이동": "もう一方のペインへ移動",
|
||||
"다른 폴더": "別のフォルダー",
|
||||
"다른 폴더로 이동": "他のフォルダーに移動",
|
||||
|
|
@ -1050,6 +1054,7 @@
|
|||
"단계": "ステップ",
|
||||
"단어 첫 글자만 크게 (Abc)": "単語の先頭だけ大文字 (Abc)",
|
||||
"단축키 - Total Commander: F5 복사 · F6 이동 · F7 새 폴더 · F8 지우기 · Insert/Space 표시 · Ctrl+R 새로 고침. 그 밖: Ctrl+C · X · V (복사 · 잘라내기 · 붙여넣기) · Ctrl+A 모두 표시 · F5 새로 고침": "ショートカット - Total Commander: F5 コピー · F6 移動 · F7 新しいフォルダー · F8 削除 · Insert/Space マーク · Ctrl+R 更新。その他: Ctrl+C · X · V (コピー · 切り取り · 貼り付け) · Ctrl+A すべてマーク · F5 更新",
|
||||
"단축키 없음": "ショートカットなし",
|
||||
"단축키 해제": "ショートカット解除",
|
||||
"닫기": "閉じる",
|
||||
"닫힌 문서입니다": "文書は閉じられています",
|
||||
|
|
@ -1262,10 +1267,12 @@
|
|||
"모두 작게 (abc)": "すべて小文字 (abc)",
|
||||
"모두 종료": "すべて終了",
|
||||
"모두 지우기": "すべて削除",
|
||||
"모두 처음 값으로": "すべて既定に戻す",
|
||||
"모두 초기화": "すべてリセット",
|
||||
"모두 취소 ({0}개)": "すべてキャンセル ({0} 件)",
|
||||
"모두 취소 (대기 {0}개 포함)": "すべてキャンセル (待機 {0} 件を含む)",
|
||||
"모두 크게 (ABC)": "すべて大文字 (ABC)",
|
||||
"모두 표시": "すべて選択",
|
||||
"모두 풀기": "すべて展開",
|
||||
"모든 작업 취소": "すべての作業をキャンセル",
|
||||
"목": "木",
|
||||
|
|
@ -1329,6 +1336,7 @@
|
|||
"바꾸면 바로 저장됩니다": "変更するとすぐ保存されます",
|
||||
"바꾸지 못했습니다: {0}": "変更できませんでした: {0}",
|
||||
"바꾼 PDF 를 저장하지 못했습니다 (저장 공간을 확인해 주세요)": "変換した PDF を保存できませんでした (空き容量を確認してください)",
|
||||
"바꾼 것 {0}개": "{0} 件変更",
|
||||
"바꾼 것이 없습니다 (원본 그대로)": "変更なし(元のまま)",
|
||||
"바꿀 글": "置き換える文字",
|
||||
"바꿀 글 (비우면 지움)": "置き換える文字 (空なら削除)",
|
||||
|
|
@ -1543,6 +1551,7 @@
|
|||
"빈 폴더입니다": "空のフォルダーです",
|
||||
"빠르게": "速く",
|
||||
"빠른 디코더 (TAESD)": "高速デコーダー (TAESD)",
|
||||
"빠른 보기": "クイックルック",
|
||||
"빠른 필터": "クイックフィルター",
|
||||
"빼고 싶은 것 (선택)": "避けたいもの (任意)",
|
||||
"사라지는 것": "消えるもの",
|
||||
|
|
@ -1703,6 +1712,7 @@
|
|||
"쉬는 중": "休止中",
|
||||
"스웨덴어": "スウェーデン語",
|
||||
"스타일": "スタイル",
|
||||
"스타일대로 (바꾼 것 없음)": "スタイルどおり (変更なし)",
|
||||
"스페인어": "スペイン語",
|
||||
"시간 지정 (cron)": "時間指定 (cron)",
|
||||
"시간 형식 오류 {0}곳": "時間形式エラー {0}か所",
|
||||
|
|
@ -1765,6 +1775,7 @@
|
|||
"쓸 수 없는 글자 (:)": "使えない文字 (:)",
|
||||
"쓸 수 없는 이름": "使えない名前",
|
||||
"쓸 수 없는 이름입니다": "使用できない名前です",
|
||||
"쓸 키를 누르세요 (Ctrl · Shift · Alt 와 함께 눌러도 됩니다)": "使うキーを押してください (Ctrl・Shift・Alt と組み合わせても構いません)",
|
||||
"아랍어": "アラビア語",
|
||||
"아래 갤러리 줄 보이기": "下のギャラリー列を表示",
|
||||
"아래 갤러리 줄 숨기기": "下のギャラリー列を隠す",
|
||||
|
|
@ -1897,6 +1908,7 @@
|
|||
"열어 본 때: {0}": "アクセス日時: {0}",
|
||||
"영구 삭제": "完全に削除",
|
||||
"영구 삭제 ({0}개, 되돌릴 수 없음)": "完全に削除 ({0} 件、元に戻せません)",
|
||||
"영구 삭제 (휴지통을 거치지 않음)": "完全に削除 (ごみ箱を経由しない)",
|
||||
"영구 삭제하면 되살릴 수 없습니다.": "完全に削除すると元に戻せません。",
|
||||
"영상": "映像",
|
||||
"영상 {0}": "映像 {0}",
|
||||
|
|
@ -2198,6 +2210,7 @@
|
|||
"이름을 바꿔 풂 {0}개": "名前を変えて展開 {0} 件",
|
||||
"이름을 찾을 수 없습니다: {0} (DNS {1})": "名前が見つかりません: {0} (DNS {1})",
|
||||
"이름이 너무 김 (255자 넘음)": "名前が長すぎます (255 文字超)",
|
||||
"이미 \"{0}\" 에 쓰는 키입니다 - 저장하면 그 동작의 이 키는 없어집니다": "このキーはすでに「{0}」に使われています - 保存するとその操作のこのキーはなくなります",
|
||||
"이미 그리는 중입니다": "すでに描画中です",
|
||||
"이미 동영상 목록에 있습니다 ({0}개).": "既に動画リストにあります({0}個)。",
|
||||
"이미 만든 MKV 가 있습니다 ({0}개)": "作成済みの MKV があります ({0}件)",
|
||||
|
|
@ -2498,6 +2511,7 @@
|
|||
"지금 보고 있는 동영상의 {0} (여러 개는 목록에서 체크)": "今見ている動画の{0}(複数はリストでチェック)",
|
||||
"지금 설정 그대로": "今の設定のまま",
|
||||
"지금 설치": "今すぐインストール",
|
||||
"지금 스타일 ({0}) 의 키입니다. 바꾼 키는 모든 스타일에 쓰이고, 다른 동작과 겹치면 그 동작의 키는 없어집니다. Esc 는 바꿀 수 없습니다": "現在のスタイル ({0}) のキーです。変更したキーはすべてのスタイルに使われ、他の操作と重なるとその操作のキーはなくなります。Esc は変更できません",
|
||||
"지금 실행 중입니다. 멈추고 lsync (실시간 동기화) 로 옮길까요? 남은 것은 lsync 가 이어서 맞춥니다.": "実行中です。止めて lsync (リアルタイム同期) へ移しますか? 残りは lsync が続けて同期します。",
|
||||
"지금 쓰는 버전: v{0}": "使用中のバージョン: v{0}",
|
||||
"지금 쓰는 버전: v{0} → 새 버전: v{1}": "現在のバージョン: v{0} → 新しいバージョン: v{1}",
|
||||
|
|
@ -2592,6 +2606,7 @@
|
|||
"채우기 (가운데 잘라냄)": "フィル(中央を切り抜き)",
|
||||
"처리 장치": "処理装置",
|
||||
"처음 · 🏠 주소 (기본: Tor 연결 확인 페이지)": "最初・🏠 のアドレス (既定: Tor 接続確認ページ)",
|
||||
"처음 값으로": "既定に戻す",
|
||||
"처음 보는 서버 인증서입니다": "初めて見るサーバー証明書です",
|
||||
"처음 보는 서버 인증서입니다 (자체 서명). 지문을 확인한 뒤 믿을지 정하세요": "初めて見るサーバー証明書です (自己署名)。指紋を確認してから信頼するか決めてください",
|
||||
"처음 사용 시 내려받기 {0}": "初回使用時にダウンロード {0}",
|
||||
|
|
@ -2724,6 +2739,7 @@
|
|||
"파일 탐색기 (지금 여기)": "ファイルエクスプローラー (現在地)",
|
||||
"파일 탐색기 가운데에 [모니터링] 버튼: 복사 · 이동을 기억해 진행 · 대상 디스크 용량 · 옵션을 보여 주고, 옵션을 고쳐 다시 실행 · 재개하거나 lsync 로 옮깁니다 (복사 · rsync 와 lsync 를 따로).": "ファイルエクスプローラー中央に [モニタリング] ボタン: コピー · 移動を記憶して進捗 · 対象ディスク容量 · オプションを表示し、オプションを変えて再実行 · 再開、または lsync へ移動します (コピー · rsync と lsync は別々)。",
|
||||
"파일 탐색기 누르기: 한 번 누르면 선택 → 한 번 누르면 바로 실행. 되돌리기: 환경 설정 > 파일 탐색기 > 누르기": "ファイルエクスプローラーのタップ: 1 回で選択 → 1 回ですぐ開く。元に戻す: 環境設定 > ファイルエクスプローラー > タップ",
|
||||
"파일 탐색기 단축키": "ファイルエクスプローラーのショートカット",
|
||||
"파일 탐색기 두 창 배치: 화면 모양 따라 → 좌우. 되돌리기: 파일 탐색기의 [좌우 ⇆ / 위아래 ⇅] 버튼 또는 창 배치": "ファイルエクスプローラーの 2 画面配置: 画面の形に合わせる → 左右。元に戻す: ファイルエクスプローラーの [左右 ⇆ / 上下 ⇅] ボタンまたは画面配置",
|
||||
"파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다": "ファイルエクスプローラー一覧の名前・サイズの文字のみ。アプリの画面サイズ・端末の文字サイズと掛け合わされます",
|
||||
"파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다": "ファイルエクスプローラー一覧の行の高さ。狭くすると指でタップしにくい場合があります",
|
||||
|
|
@ -2808,10 +2824,12 @@
|
|||
"폴더만 lsync 로 옮길 수 있습니다.": "lsync へ移せるのはフォルダーだけです。",
|
||||
"폴더와 안의 것 전체 (기본)": "フォルダーと中身すべて (既定)",
|
||||
"폴란드어": "ポーランド語",
|
||||
"표시": "選択",
|
||||
"표시 (여러 개 고르기)": "マーク (複数選択)",
|
||||
"표시 이름": "表示名",
|
||||
"표시 이름은 바로 고칠 수 있습니다. 동영상 줄을 세 번 누르면 첫 번째 버튼의 폴더로 옮깁니다": "表示名はその場で変更できます。動画の行を 3回押すと最初のボタンのフォルダーに移動します",
|
||||
"표시 지우기": "マークを解除",
|
||||
"표시하고 다음 줄": "選択して次の行へ",
|
||||
"표준": "標準",
|
||||
"표준 입력 (-) 은 쓸 수 없습니다": "標準入力 (-) は使えません",
|
||||
"푸는 중": "展開中",
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@
|
|||
"(받는 쪽이 더 새것 - 건너뜀)": "(接收端较新 - 已跳过)",
|
||||
"(비어 있음)": "(空)",
|
||||
"(설명 없음)": "(无说明)",
|
||||
"(없음)": "(无)",
|
||||
"(지워짐)": "(将删除)",
|
||||
"(폴더 · 안의 {0}개를 지우고 같은 이름의 파일로)": "(删除文件夹及其中 {0} 项,换成同名文件)",
|
||||
"(현재 방식은 옵션 없음)": "(当前方式没有选项)",
|
||||
|
|
@ -201,6 +202,7 @@
|
|||
"Edge 에서 가져옴": "从 Edge 导入",
|
||||
"Edge 엔진으로 바꾸기": "切换到 Edge 引擎",
|
||||
"Edge: Windows 에 들어 있어 따로 설치하지 않습니다. 여기서 YouTube 에 로그인하면 다운로드에도 그 로그인이 쓰입니다.\nChrome: 고르면 약 {0}MB 를 내려받고 다시 시작한 뒤 쓸 수 있습니다 (Google 로그인은 막힐 수 있음).{1}{2}": "Edge: Windows 自带,无需另外安装。在此登录 YouTube 后,下载也会使用该登录。\nChrome: 选择后会下载约 {0}MB,重新启动后可用(Google 登录可能被阻止)。{1}{2}",
|
||||
"Esc 는 선택 · 빠른 필터를 닫는 키라 쓸 수 없습니다": "Esc 用于关闭选择和快速筛选,不能使用",
|
||||
"FFmpeg · yt-dlp · aria2 · Deno 가 없으면 내려받아 설치합니다": "如缺少 FFmpeg · yt-dlp · aria2 · Deno,将下载并安装",
|
||||
"FFmpeg 준비됨: {0}{1}": "FFmpeg 已就绪: {0}{1}",
|
||||
"FTP 서버 \"{0}\" 을(를) 지울까요?": "删除 FTP 服务器\"{0}\"?",
|
||||
|
|
@ -498,6 +500,7 @@
|
|||
"{0} (기본): {1}": "{0}(默认): {1}",
|
||||
"{0} (동영상 {1})": "{0}(视频 {1})",
|
||||
"{0} - {1} (약 {2}MB)": "{0} - {1}(约 {2}MB)",
|
||||
"{0} - 단축키": "{0} - 快捷键",
|
||||
"{0} / {1} 읽음": "已读取 {0} / {1}",
|
||||
"{0} / {1} 확인": "已检查 {0} / {1}",
|
||||
"{0} / {1}장": "{0} / {1} 张",
|
||||
|
|
@ -993,6 +996,7 @@
|
|||
"다른 창에서 열기": "在另一窗格中打开",
|
||||
"다른 창으로 끌어 놓았을 때. 어느 쪽이든 복사 · 이동 확인 창 (같은 이름 등) 은 그대로 뜹니다": "拖放到另一个窗格时。无论哪种,都会显示复制/移动确认 (同名等)",
|
||||
"다른 창으로 복사": "复制到另一窗格",
|
||||
"다른 창으로 옮겨 가기": "切换到另一窗格",
|
||||
"다른 창으로 이동": "移动到另一窗格",
|
||||
"다른 폴더": "其他文件夹",
|
||||
"다른 폴더로 이동": "移动到其他文件夹",
|
||||
|
|
@ -1050,6 +1054,7 @@
|
|||
"단계": "步数",
|
||||
"단어 첫 글자만 크게 (Abc)": "单词首字母大写 (Abc)",
|
||||
"단축키 - Total Commander: F5 복사 · F6 이동 · F7 새 폴더 · F8 지우기 · Insert/Space 표시 · Ctrl+R 새로 고침. 그 밖: Ctrl+C · X · V (복사 · 잘라내기 · 붙여넣기) · Ctrl+A 모두 표시 · F5 새로 고침": "快捷键 - Total Commander: F5 复制 · F6 移动 · F7 新建文件夹 · F8 删除 · Insert/Space 标记 · Ctrl+R 刷新。其他: Ctrl+C · X · V (复制 · 剪切 · 粘贴) · Ctrl+A 全部标记 · F5 刷新",
|
||||
"단축키 없음": "无快捷键",
|
||||
"단축키 해제": "注销快捷键",
|
||||
"닫기": "关闭",
|
||||
"닫힌 문서입니다": "文档已关闭",
|
||||
|
|
@ -1262,10 +1267,12 @@
|
|||
"모두 작게 (abc)": "全部小写 (abc)",
|
||||
"모두 종료": "全部退出",
|
||||
"모두 지우기": "全部清除",
|
||||
"모두 처음 값으로": "全部恢复默认",
|
||||
"모두 초기화": "全部重置",
|
||||
"모두 취소 ({0}개)": "全部取消({0} 个)",
|
||||
"모두 취소 (대기 {0}개 포함)": "全部取消 (包括等待中的 {0} 个)",
|
||||
"모두 크게 (ABC)": "全部大写 (ABC)",
|
||||
"모두 표시": "全部标记",
|
||||
"모두 풀기": "全部解压",
|
||||
"모든 작업 취소": "取消所有任务",
|
||||
"목": "四",
|
||||
|
|
@ -1329,6 +1336,7 @@
|
|||
"바꾸면 바로 저장됩니다": "更改后立即保存",
|
||||
"바꾸지 못했습니다: {0}": "无法更改: {0}",
|
||||
"바꾼 PDF 를 저장하지 못했습니다 (저장 공간을 확인해 주세요)": "无法保存转换后的 PDF (请检查存储空间)",
|
||||
"바꾼 것 {0}개": "已更改 {0} 个",
|
||||
"바꾼 것이 없습니다 (원본 그대로)": "未做更改(保持原样)",
|
||||
"바꿀 글": "替换为",
|
||||
"바꿀 글 (비우면 지움)": "替换为 (留空则删除)",
|
||||
|
|
@ -1543,6 +1551,7 @@
|
|||
"빈 폴더입니다": "文件夹为空",
|
||||
"빠르게": "快速",
|
||||
"빠른 디코더 (TAESD)": "快速解码器 (TAESD)",
|
||||
"빠른 보기": "快速查看",
|
||||
"빠른 필터": "快速筛选",
|
||||
"빼고 싶은 것 (선택)": "要排除的内容 (可选)",
|
||||
"사라지는 것": "丢失的内容",
|
||||
|
|
@ -1703,6 +1712,7 @@
|
|||
"쉬는 중": "空闲",
|
||||
"스웨덴어": "瑞典语",
|
||||
"스타일": "样式",
|
||||
"스타일대로 (바꾼 것 없음)": "按样式 (未更改)",
|
||||
"스페인어": "西班牙语",
|
||||
"시간 지정 (cron)": "指定时间 (cron)",
|
||||
"시간 형식 오류 {0}곳": "{0} 处时间格式错误",
|
||||
|
|
@ -1765,6 +1775,7 @@
|
|||
"쓸 수 없는 글자 (:)": "无效字符 (:)",
|
||||
"쓸 수 없는 이름": "无效名称",
|
||||
"쓸 수 없는 이름입니다": "不能使用此名称",
|
||||
"쓸 키를 누르세요 (Ctrl · Shift · Alt 와 함께 눌러도 됩니다)": "请按下要使用的键 (可与 Ctrl、Shift、Alt 组合)",
|
||||
"아랍어": "阿拉伯语",
|
||||
"아래 갤러리 줄 보이기": "显示底部图库条",
|
||||
"아래 갤러리 줄 숨기기": "隐藏底部图库条",
|
||||
|
|
@ -1897,6 +1908,7 @@
|
|||
"열어 본 때: {0}": "访问时间: {0}",
|
||||
"영구 삭제": "永久删除",
|
||||
"영구 삭제 ({0}개, 되돌릴 수 없음)": "永久删除 ({0} 个,无法撤销)",
|
||||
"영구 삭제 (휴지통을 거치지 않음)": "永久删除 (不经过回收站)",
|
||||
"영구 삭제하면 되살릴 수 없습니다.": "永久删除后无法还原。",
|
||||
"영상": "视频",
|
||||
"영상 {0}": "视频 {0}",
|
||||
|
|
@ -2198,6 +2210,7 @@
|
|||
"이름을 바꿔 풂 {0}개": "{0} 个以新名称解压",
|
||||
"이름을 찾을 수 없습니다: {0} (DNS {1})": "找不到名称: {0} (DNS {1})",
|
||||
"이름이 너무 김 (255자 넘음)": "名称过长 (超过 255 个字符)",
|
||||
"이미 \"{0}\" 에 쓰는 키입니다 - 저장하면 그 동작의 이 키는 없어집니다": "此键已用于“{0}” - 保存后该操作将失去此键",
|
||||
"이미 그리는 중입니다": "已在绘制中",
|
||||
"이미 동영상 목록에 있습니다 ({0}개).": "已在视频列表中({0} 个)。",
|
||||
"이미 만든 MKV 가 있습니다 ({0}개)": "已有制作好的 MKV({0} 个)",
|
||||
|
|
@ -2498,6 +2511,7 @@
|
|||
"지금 보고 있는 동영상의 {0} (여러 개는 목록에서 체크)": "当前查看视频的{0}(多个请在列表中勾选)",
|
||||
"지금 설정 그대로": "保持当前设置",
|
||||
"지금 설치": "立即安装",
|
||||
"지금 스타일 ({0}) 의 키입니다. 바꾼 키는 모든 스타일에 쓰이고, 다른 동작과 겹치면 그 동작의 키는 없어집니다. Esc 는 바꿀 수 없습니다": "当前样式 ({0}) 的按键。更改的键适用于所有样式,与其他操作冲突时该操作将失去此键。Esc 不能更改",
|
||||
"지금 실행 중입니다. 멈추고 lsync (실시간 동기화) 로 옮길까요? 남은 것은 lsync 가 이어서 맞춥니다.": "正在运行。停止并移到 lsync (实时同步) 吗? 剩余部分由 lsync 继续同步。",
|
||||
"지금 쓰는 버전: v{0}": "当前版本: v{0}",
|
||||
"지금 쓰는 버전: v{0} → 새 버전: v{1}": "当前版本: v{0} → 新版本: v{1}",
|
||||
|
|
@ -2592,6 +2606,7 @@
|
|||
"채우기 (가운데 잘라냄)": "填充(裁切中间)",
|
||||
"처리 장치": "处理器",
|
||||
"처음 · 🏠 주소 (기본: Tor 연결 확인 페이지)": "起始 · 🏠 地址 (默认: Tor 连接检查页面)",
|
||||
"처음 값으로": "恢复默认",
|
||||
"처음 보는 서버 인증서입니다": "这是首次见到的服务器证书",
|
||||
"처음 보는 서버 인증서입니다 (자체 서명). 지문을 확인한 뒤 믿을지 정하세요": "这是首次见到的服务器证书 (自签名)。请确认指纹后决定是否信任",
|
||||
"처음 사용 시 내려받기 {0}": "首次使用时下载 {0}",
|
||||
|
|
@ -2724,6 +2739,7 @@
|
|||
"파일 탐색기 (지금 여기)": "文件浏览器 (当前位置)",
|
||||
"파일 탐색기 가운데에 [모니터링] 버튼: 복사 · 이동을 기억해 진행 · 대상 디스크 용량 · 옵션을 보여 주고, 옵션을 고쳐 다시 실행 · 재개하거나 lsync 로 옮깁니다 (복사 · rsync 와 lsync 를 따로).": "文件浏览器中间的 [监控] 按钮: 记住复制 · 移动并显示进度 · 目标磁盘容量 · 选项,可修改选项后重新运行 · 继续,或移到 lsync (复制 · rsync 与 lsync 分开显示)。",
|
||||
"파일 탐색기 누르기: 한 번 누르면 선택 → 한 번 누르면 바로 실행. 되돌리기: 환경 설정 > 파일 탐색기 > 누르기": "文件浏览器点击: 单击选择 → 单击直接打开。恢复: 设置 > 文件浏览器 > 点击",
|
||||
"파일 탐색기 단축키": "文件管理器快捷键",
|
||||
"파일 탐색기 두 창 배치: 화면 모양 따라 → 좌우. 되돌리기: 파일 탐색기의 [좌우 ⇆ / 위아래 ⇅] 버튼 또는 창 배치": "文件浏览器双窗格布局: 跟随屏幕形状 → 左右。恢复: 文件浏览器中的 [左右 ⇆ / 上下 ⇅] 按钮或窗格布局",
|
||||
"파일 탐색기 목록의 이름 · 크기 글자만. 앱 화면 크기 · 기기 글자 크기와 곱해집니다": "仅文件管理器列表中的名称和大小文字。会与应用界面大小及设备文字大小相乘",
|
||||
"파일 탐색기 목록의 줄 높이. 좁게는 손가락으로 누르기 어려울 수 있습니다": "文件管理器列表的行高。紧凑时可能难以用手指点按",
|
||||
|
|
@ -2808,10 +2824,12 @@
|
|||
"폴더만 lsync 로 옮길 수 있습니다.": "只有文件夹可以移到 lsync。",
|
||||
"폴더와 안의 것 전체 (기본)": "文件夹及其所有内容 (默认)",
|
||||
"폴란드어": "波兰语",
|
||||
"표시": "标记",
|
||||
"표시 (여러 개 고르기)": "标记 (多选)",
|
||||
"표시 이름": "显示名称",
|
||||
"표시 이름은 바로 고칠 수 있습니다. 동영상 줄을 세 번 누르면 첫 번째 버튼의 폴더로 옮깁니다": "显示名称可直接修改。连续点击视频行三次会移到第一个按钮的文件夹",
|
||||
"표시 지우기": "清除标记",
|
||||
"표시하고 다음 줄": "标记并移到下一行",
|
||||
"표준": "标准",
|
||||
"표준 입력 (-) 은 쓸 수 없습니다": "不能使用标准输入 (-)",
|
||||
"푸는 중": "正在解压",
|
||||
|
|
|
|||
|
|
@ -481,6 +481,10 @@ class AppSettings {
|
|||
int explorerRemoteThumbCacheMb = 200;
|
||||
static const remoteThumbCacheChoices = [50, 100, 200, 500, 1000];
|
||||
|
||||
/// X-plore 비교 5-1 ⑥ (관리자 지시 10/21): 파일 탐색기 단축키 중 사용자가 바꾼 것만 (동작 → 키, '' = 그 동작은 단축키 없음).
|
||||
/// 비어 있으면 (처음) 스타일대로 (83) - ui/explorer_keys.dart
|
||||
Map<String, String> explorerKeyOverrides = {};
|
||||
|
||||
/// 84: 폴더 → "기준|desc" (예: "size|1"). 오래된 것부터 지워 300개까지
|
||||
Map<String, String> explorerFolderSorts = {};
|
||||
|
||||
|
|
@ -881,6 +885,7 @@ class AppSettings {
|
|||
'explorerSortByKind': explorerSortByKind,
|
||||
'explorerRemoteThumbs': explorerRemoteThumbs,
|
||||
'explorerRemoteThumbCacheMb': explorerRemoteThumbCacheMb,
|
||||
'explorerKeyOverrides': explorerKeyOverrides,
|
||||
'explorerFolderSorts': explorerFolderSorts,
|
||||
'explorerShowHidden': explorerShowHidden,
|
||||
'explorerHiddenItems': explorerHiddenItems,
|
||||
|
|
@ -1125,6 +1130,9 @@ class AppSettings {
|
|||
..explorerSortPerFolder = j['explorerSortPerFolder'] == true
|
||||
..explorerSortByKind = j['explorerSortByKind'] == true
|
||||
..explorerRemoteThumbs = const {'off', 'wifi', 'always'}.contains(j['explorerRemoteThumbs']) ? j['explorerRemoteThumbs'] as String : 'wifi'
|
||||
..explorerKeyOverrides = j['explorerKeyOverrides'] is Map
|
||||
? {for (final e in (j['explorerKeyOverrides'] as Map).entries) if (e.key is String && e.value is String) e.key as String: e.value as String}
|
||||
: {}
|
||||
..explorerRemoteThumbCacheMb = j['explorerRemoteThumbCacheMb'] is num
|
||||
? (j['explorerRemoteThumbCacheMb'] as num).round().clamp(10, 10000)
|
||||
: 200
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../l10n/tr.dart';
|
||||
|
||||
/// X-plore 비교 5-1 ⑥ (관리자 지시 10/21): 파일 탐색기 단축키 바꾸기.
|
||||
/// 동작마다 처음 키는 스타일대로 (83 - Total Commander / 그 밖) 이고, 사용자가 바꾼 것만 설정 (explorerKeyOverrides: 동작 → 키) 에 둔다.
|
||||
/// 바꾼 키가 다른 동작의 키와 같으면 바꾼 쪽이 이기고 그 동작은 그 키를 잃는다 (설정 화면이 먼저 알린다). Esc 는 바꾸지 않는다.
|
||||
const explorerKeyActions = <String>[
|
||||
'copy', 'move', 'newFolder', 'delete', 'deletePermanent', 'rename', 'refresh', 'quickLook', //
|
||||
'clipCopy', 'clipCut', 'paste', 'selectAll', 'mark', 'markNext', 'filter', 'up', 'back', 'forward', 'switchPane',
|
||||
];
|
||||
|
||||
/// 동작 이름 (화면 글)
|
||||
String explorerKeyLabel(String id) => switch (id) {
|
||||
'copy' => tr('다른 창으로 복사'),
|
||||
'move' => tr('다른 창으로 이동'),
|
||||
'newFolder' => tr('새 폴더'),
|
||||
'delete' => tr('삭제'),
|
||||
'deletePermanent' => tr('영구 삭제 (휴지통을 거치지 않음)'),
|
||||
'rename' => tr('이름 변경'),
|
||||
'refresh' => tr('새로 고침'),
|
||||
'quickLook' => tr('빠른 보기'),
|
||||
'clipCopy' => tr('복사 (붙여넣기로)'),
|
||||
'clipCut' => tr('이동 (붙여넣기로)'),
|
||||
'paste' => tr('붙여넣기'),
|
||||
'selectAll' => tr('모두 표시'),
|
||||
'mark' => tr('표시'),
|
||||
'markNext' => tr('표시하고 다음 줄'),
|
||||
'filter' => tr('빠른 필터'),
|
||||
'up' => tr('상위 폴더'),
|
||||
'back' => tr('뒤로'),
|
||||
'forward' => tr('앞으로'),
|
||||
'switchPane' => tr('다른 창으로 옮겨 가기'),
|
||||
_ => id,
|
||||
};
|
||||
|
||||
/// 스타일의 처음 키 (83 과 같음). [tc] Total Commander 스타일
|
||||
Map<String, List<SingleActivator>> defaultExplorerKeys({required bool tc}) => {
|
||||
'delete': [const SingleActivator(LogicalKeyboardKey.delete), if (tc) const SingleActivator(LogicalKeyboardKey.f8)],
|
||||
'deletePermanent': [const SingleActivator(LogicalKeyboardKey.delete, shift: true)],
|
||||
'rename': [const SingleActivator(LogicalKeyboardKey.f2)],
|
||||
'filter': [const SingleActivator(LogicalKeyboardKey.keyF, control: true)],
|
||||
'up': [const SingleActivator(LogicalKeyboardKey.backspace)],
|
||||
'back': [const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true)],
|
||||
'forward': [const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true)],
|
||||
'switchPane': [const SingleActivator(LogicalKeyboardKey.tab)],
|
||||
if (tc) ...{
|
||||
'copy': [const SingleActivator(LogicalKeyboardKey.f5)],
|
||||
'move': [const SingleActivator(LogicalKeyboardKey.f6)],
|
||||
'newFolder': [const SingleActivator(LogicalKeyboardKey.f7)],
|
||||
'refresh': [const SingleActivator(LogicalKeyboardKey.keyR, control: true)],
|
||||
'markNext': [const SingleActivator(LogicalKeyboardKey.insert)],
|
||||
'mark': [const SingleActivator(LogicalKeyboardKey.space)],
|
||||
'quickLook': [const SingleActivator(LogicalKeyboardKey.f3)],
|
||||
} else ...{
|
||||
'quickLook': [const SingleActivator(LogicalKeyboardKey.space)],
|
||||
'refresh': [const SingleActivator(LogicalKeyboardKey.f5)],
|
||||
'clipCopy': [const SingleActivator(LogicalKeyboardKey.keyC, control: true)],
|
||||
'clipCut': [const SingleActivator(LogicalKeyboardKey.keyX, control: true)],
|
||||
'paste': [const SingleActivator(LogicalKeyboardKey.keyV, control: true)],
|
||||
'selectAll': [const SingleActivator(LogicalKeyboardKey.keyA, control: true)],
|
||||
},
|
||||
};
|
||||
|
||||
/// 같은 키 (같은 글쇠 · 같은 Ctrl · Shift · Alt · Meta)
|
||||
bool sameKey(SingleActivator a, SingleActivator b) =>
|
||||
a.trigger == b.trigger && a.control == b.control && a.shift == b.shift && a.alt == b.alt && a.meta == b.meta;
|
||||
|
||||
/// 설정에 두는 꼴: "c,s,a,m|글쇠 번호" (없으면 '' = 그 동작은 단축키 없음). 틀리면 null
|
||||
String keySpec(SingleActivator a) =>
|
||||
'${[if (a.control) 'c', if (a.shift) 's', if (a.alt) 'a', if (a.meta) 'm'].join(',')}|${a.trigger.keyId}';
|
||||
|
||||
SingleActivator? parseKeySpec(String spec) {
|
||||
final bar = spec.lastIndexOf('|');
|
||||
if (bar < 0) return null;
|
||||
final id = int.tryParse(spec.substring(bar + 1));
|
||||
final key = id == null ? null : LogicalKeyboardKey.findKeyByKeyId(id);
|
||||
if (key == null) return null;
|
||||
final mods = spec.substring(0, bar).split(',');
|
||||
return SingleActivator(key,
|
||||
control: mods.contains('c'), shift: mods.contains('s'), alt: mods.contains('a'), meta: mods.contains('m'));
|
||||
}
|
||||
|
||||
/// 화면에 보일 글: "Ctrl+Shift+K"
|
||||
String keyText(SingleActivator a) {
|
||||
final name = a.trigger.keyLabel.isNotEmpty ? a.trigger.keyLabel : (a.trigger.debugName ?? '?');
|
||||
return [if (a.control) 'Ctrl', if (a.shift) 'Shift', if (a.alt) 'Alt', if (a.meta) 'Meta', name == ' ' ? 'Space' : name].join('+');
|
||||
}
|
||||
|
||||
/// 바꾼 것을 넣은 동작마다의 키. 바꾼 키는 다른 동작에서 뺀다
|
||||
Map<String, List<SingleActivator>> effectiveExplorerKeys({required bool tc, required Map<String, String> overrides}) {
|
||||
final out = {for (final e in defaultExplorerKeys(tc: tc).entries) e.key: [...e.value]};
|
||||
final mine = <String, SingleActivator?>{};
|
||||
for (final e in overrides.entries) {
|
||||
if (!explorerKeyActions.contains(e.key)) continue;
|
||||
mine[e.key] = e.value.isEmpty ? null : parseKeySpec(e.value);
|
||||
if (e.value.isNotEmpty && mine[e.key] == null) mine.remove(e.key); // 틀린 값은 처음 키 그대로
|
||||
}
|
||||
for (final e in mine.entries) {
|
||||
out[e.key] = [?e.value];
|
||||
}
|
||||
for (final e in mine.entries) {
|
||||
final k = e.value;
|
||||
if (k == null) continue;
|
||||
for (final other in out.keys) {
|
||||
if (other != e.key) out[other]!.removeWhere((x) => sameKey(x, k));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/// [action] 에 [key] 를 주면 키를 잃는 다른 동작 (없으면 null). Esc 는 쓸 수 없음 ('escape')
|
||||
String? keyConflict(String action, SingleActivator key, {required bool tc, required Map<String, String> overrides}) {
|
||||
if (key.trigger == LogicalKeyboardKey.escape) return 'escape';
|
||||
final now = effectiveExplorerKeys(tc: tc, overrides: overrides);
|
||||
for (final e in now.entries) {
|
||||
if (e.key != action && e.value.any((x) => sameKey(x, key))) return e.key;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 키를 받는 창에서 모자 키 (Ctrl · Shift · Alt · Meta) 만 누른 것은 아직 키가 아니다
|
||||
bool isModifierOnly(LogicalKeyboardKey k) => {
|
||||
LogicalKeyboardKey.control, LogicalKeyboardKey.controlLeft, LogicalKeyboardKey.controlRight, //
|
||||
LogicalKeyboardKey.shift, LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftRight,
|
||||
LogicalKeyboardKey.alt, LogicalKeyboardKey.altLeft, LogicalKeyboardKey.altRight,
|
||||
LogicalKeyboardKey.meta, LogicalKeyboardKey.metaLeft, LogicalKeyboardKey.metaRight,
|
||||
}.contains(k);
|
||||
|
||||
/// 키 이벤트를 받는 쪽 (설정의 키 받기 창) 에서 쓰는 모자 상태
|
||||
SingleActivator activatorFromEvent(KeyEvent e) {
|
||||
final hk = HardwareKeyboard.instance;
|
||||
return SingleActivator(e.logicalKey,
|
||||
control: hk.isControlPressed, shift: hk.isShiftPressed, alt: hk.isAltPressed, meta: hk.isMetaPressed);
|
||||
}
|
||||
|
||||
/// 표시용 위젯이 키 글을 고정 폭 글로
|
||||
TextStyle get keyTextStyle => const TextStyle(fontFamily: 'monospace', fontSize: 13);
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../app/app_controller.dart';
|
||||
import '../l10n/tr.dart';
|
||||
import 'explorer_keys.dart';
|
||||
import 'explorer_look.dart' show ExplorerStyle;
|
||||
import 'theme.dart';
|
||||
|
||||
/// X-plore 비교 5-1 ⑥ (관리자 지시 10/21): 파일 탐색기 단축키 바꾸기 - 동작마다 지금 키 · [바꾸기] (다음에 누른 키) · [처음 값으로].
|
||||
/// 지금 고른 스타일의 처음 키를 보이고, 바꾼 키는 모든 스타일에 쓰인다
|
||||
class ExplorerKeysPage extends StatelessWidget {
|
||||
final AppController c;
|
||||
const ExplorerKeysPage({super.key, required this.c});
|
||||
|
||||
static Future<void> open(BuildContext context, AppController c) =>
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => ExplorerKeysPage(c: c)));
|
||||
|
||||
bool get _tc => ExplorerStyle.of(c.settings.explorerStyle) == ExplorerStyle.totalcmd;
|
||||
|
||||
Future<void> _change(BuildContext context, String id) async {
|
||||
final picked = await showDialog<SingleActivator?>(
|
||||
context: context,
|
||||
builder: (_) => _KeyCaptureDialog(c: c, action: id, tc: _tc),
|
||||
);
|
||||
if (picked == null) return;
|
||||
await c.updateSettings((x) => x.explorerKeyOverrides = {...x.explorerKeyOverrides, id: _none(picked) ? '' : keySpec(picked)});
|
||||
}
|
||||
|
||||
static bool _none(SingleActivator a) => a.trigger == LogicalKeyboardKey.abort;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ListenableBuilder(
|
||||
listenable: c,
|
||||
builder: (context, _) {
|
||||
final ov = c.settings.explorerKeyOverrides;
|
||||
final keys = effectiveExplorerKeys(tc: _tc, overrides: ov);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(tr('파일 탐색기 단축키')),
|
||||
actions: [
|
||||
if (ov.isNotEmpty)
|
||||
TextButton(
|
||||
onPressed: () => c.updateSettings((x) => x.explorerKeyOverrides = {}),
|
||||
child: Text(tr('모두 처음 값으로')),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ListView(children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
|
||||
child: Text(
|
||||
trf('지금 스타일 ({0}) 의 키입니다. 바꾼 키는 모든 스타일에 쓰이고, 다른 동작과 겹치면 그 동작의 키는 없어집니다. Esc 는 바꿀 수 없습니다',
|
||||
[ExplorerStyle.of(c.settings.explorerStyle).label]),
|
||||
style: const TextStyle(fontSize: 12, color: JjColors.textDim)),
|
||||
),
|
||||
for (final id in explorerKeyActions)
|
||||
ListTile(
|
||||
dense: true,
|
||||
title: Text(explorerKeyLabel(id)),
|
||||
subtitle: Text(
|
||||
keys[id]?.isNotEmpty == true ? keys[id]!.map(keyText).join(' · ') : tr('(없음)'),
|
||||
style: keyTextStyle.copyWith(color: ov.containsKey(id) ? JjColors.accent : null),
|
||||
),
|
||||
trailing: Wrap(spacing: 4, children: [
|
||||
if (ov.containsKey(id))
|
||||
TextButton(
|
||||
onPressed: () => c.updateSettings((x) => x.explorerKeyOverrides = {...x.explorerKeyOverrides}..remove(id)),
|
||||
child: Text(tr('처음 값으로')),
|
||||
),
|
||||
OutlinedButton(onPressed: () => _change(context, id), child: Text(tr('바꾸기'))),
|
||||
]),
|
||||
),
|
||||
]),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 다음에 누른 키를 받는 창 - 겹치면 알리고 [저장] 은 그래도 됨. [단축키 없음] 은 abort 글쇠로 돌려준다 (부르는 쪽이 '' 로 저장)
|
||||
class _KeyCaptureDialog extends StatefulWidget {
|
||||
final AppController c;
|
||||
final String action;
|
||||
final bool tc;
|
||||
const _KeyCaptureDialog({required this.c, required this.action, required this.tc});
|
||||
|
||||
@override
|
||||
State<_KeyCaptureDialog> createState() => _KeyCaptureDialogState();
|
||||
}
|
||||
|
||||
class _KeyCaptureDialogState extends State<_KeyCaptureDialog> {
|
||||
SingleActivator? _key;
|
||||
final _focus = FocusNode();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_focus.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final k = _key;
|
||||
final clash = k == null ? null : keyConflict(widget.action, k, tc: widget.tc, overrides: widget.c.settings.explorerKeyOverrides);
|
||||
return AlertDialog(
|
||||
title: Text(trf('{0} - 단축키', [explorerKeyLabel(widget.action)])),
|
||||
content: Focus(
|
||||
focusNode: _focus,
|
||||
autofocus: true,
|
||||
onKeyEvent: (_, e) {
|
||||
if (e is! KeyDownEvent || isModifierOnly(e.logicalKey)) return KeyEventResult.handled;
|
||||
setState(() => _key = activatorFromEvent(e));
|
||||
return KeyEventResult.handled;
|
||||
},
|
||||
child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Text(tr('쓸 키를 누르세요 (Ctrl · Shift · Alt 와 함께 눌러도 됩니다)')),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(border: Border.all(color: JjColors.border), borderRadius: BorderRadius.circular(6)),
|
||||
child: Text(k == null ? '…' : keyText(k), style: keyTextStyle.copyWith(fontSize: 16)),
|
||||
),
|
||||
if (clash == 'escape')
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Text(tr('Esc 는 선택 · 빠른 필터를 닫는 키라 쓸 수 없습니다'), style: const TextStyle(color: JjColors.danger, fontSize: 12)),
|
||||
)
|
||||
else if (clash != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Text(trf('이미 "{0}" 에 쓰는 키입니다 - 저장하면 그 동작의 이 키는 없어집니다', [explorerKeyLabel(clash)]),
|
||||
style: const TextStyle(color: Colors.orangeAccent, fontSize: 12)),
|
||||
),
|
||||
]),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, const SingleActivator(LogicalKeyboardKey.abort)),
|
||||
child: Text(tr('단축키 없음')),
|
||||
),
|
||||
TextButton(onPressed: () => Navigator.pop(context), child: Text(tr('취소'))),
|
||||
FilledButton(onPressed: k == null || clash == 'escape' ? null : () => Navigator.pop(context, k), child: Text(tr('저장'))),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +58,7 @@ import '../l10n/tr.dart';
|
|||
import 'explorer_bookmarks.dart';
|
||||
import 'explorer_search.dart';
|
||||
import 'remote_thumb_view.dart';
|
||||
import 'explorer_keys.dart' show defaultExplorerKeys, effectiveExplorerKeys;
|
||||
import 'sqlite_viewer.dart' show openSqliteViewer, sqliteExtensions;
|
||||
import 'quick_look.dart';
|
||||
import 'file_tools.dart' show showFileHash, showFolderProperties;
|
||||
|
|
@ -4460,17 +4461,9 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
// P0 (15 지적 10/17): 글자 칸 (빠른 필터 · 경로 등) 에 치는 중이면 파일 단축키를 쓰지 않는다 - 예전에는 CallbackShortcuts 가
|
||||
// 글자 칸보다 먼저 키를 받아, 칸에서 Delete 를 누르면 파일 지우기 창이 뜨고 Ctrl+A · C · V · Space 도 파일에 쓰였다
|
||||
final bindings = <ShortcutActivator, VoidCallback>{
|
||||
// 83: 스타일에 맞는 단축키 (Total Commander: F5 복사 · F6 이동 · F7 새 폴더 · F8 지우기 · Insert/Space 표시 ·
|
||||
// Ctrl+R 새로 고침 / Windows 탐색기 · X-plore: Ctrl+C · X · V · A · F5 새로 고침)
|
||||
..._styleKeys(),
|
||||
const SingleActivator(LogicalKeyboardKey.delete): () => _button(ExplorerButton.delete),
|
||||
// 65: Shift+Delete = 휴지통을 거치지 않고 (창에서 한 번 더 확인)
|
||||
const SingleActivator(LogicalKeyboardKey.delete, shift: true): () {
|
||||
_shiftDelete = true;
|
||||
_button(ExplorerButton.delete);
|
||||
},
|
||||
const SingleActivator(LogicalKeyboardKey.f2): () => _button(ExplorerButton.rename),
|
||||
// 180: 선택 모드 · 표시 풀기 (복사 · 이동이 끝나도 선택 모드는 그대로이므로 바로 나올 수 있게)
|
||||
// 83 · X-plore 비교 5-1 ⑥: 스타일에 맞는 처음 키 + 사용자가 바꾼 키 ([explorer_keys.dart], 환경 설정 > 파일 탐색기 > 단축키)
|
||||
..._keyBindings(),
|
||||
// 180: 선택 모드 · 표시 풀기 (복사 · 이동이 끝나도 선택 모드는 그대로이므로 바로 나올 수 있게) - Esc 는 바꾸지 않는다
|
||||
const SingleActivator(LogicalKeyboardKey.escape): () {
|
||||
// 76: 열린 빠른 필터부터 닫는다
|
||||
if (_panes[_active].filterOpen) {
|
||||
|
|
@ -4479,13 +4472,6 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
_setSelecting(false);
|
||||
}
|
||||
},
|
||||
const SingleActivator(LogicalKeyboardKey.keyF, control: true): () => _openFilter(_panes[_active]),
|
||||
const SingleActivator(LogicalKeyboardKey.backspace): () => _button(ExplorerButton.up),
|
||||
// 75: 지금 창의 뒤로 · 앞으로
|
||||
const SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true): () => _travel(_panes[_active], forward: false),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowRight, alt: true): () => _travel(_panes[_active], forward: true),
|
||||
const SingleActivator(LogicalKeyboardKey.tab): () =>
|
||||
setState(() => _active = _dual ? 1 - _active : 0),
|
||||
};
|
||||
return Focus(
|
||||
canRequestFocus: false,
|
||||
|
|
@ -5123,34 +5109,52 @@ class _ExplorerPageState extends State<ExplorerPage> {
|
|||
/// 83: 지금 스타일이 Total Commander 인지 (Rsync 화면은 스타일 단축키를 쓰지 않는다 - 늘 같은 버튼)
|
||||
bool get _tcKeys => !widget.rsync && _look == ExplorerStyle.totalcmd;
|
||||
|
||||
/// 83: 스타일에 맞는 단축키
|
||||
Map<ShortcutActivator, VoidCallback> _styleKeys() {
|
||||
if (widget.rsync) return {const SingleActivator(LogicalKeyboardKey.f5): () => _button(ExplorerButton.refresh)};
|
||||
if (_tcKeys) {
|
||||
return {
|
||||
const SingleActivator(LogicalKeyboardKey.f5): () => _button(ExplorerButton.copy),
|
||||
const SingleActivator(LogicalKeyboardKey.f6): () => _button(ExplorerButton.move),
|
||||
const SingleActivator(LogicalKeyboardKey.f7): () => _button(ExplorerButton.newFolder),
|
||||
const SingleActivator(LogicalKeyboardKey.f8): () => _button(ExplorerButton.delete),
|
||||
const SingleActivator(LogicalKeyboardKey.keyR, control: true): () => _button(ExplorerButton.refresh),
|
||||
// Insert: 표시하고 다음 줄로 · Space: 표시 (TC 처럼)
|
||||
const SingleActivator(LogicalKeyboardKey.insert): () => _markFocused(next: true),
|
||||
const SingleActivator(LogicalKeyboardKey.space): () => _markFocused(next: false),
|
||||
// 81: 빠른 보기 (TC 의 보기 키)
|
||||
const SingleActivator(LogicalKeyboardKey.f3): () => _quickLook(_pane),
|
||||
};
|
||||
}
|
||||
return {
|
||||
// 81: 빠른 보기 (Windows · macOS 처럼 Space)
|
||||
const SingleActivator(LogicalKeyboardKey.space): () => _quickLook(_pane),
|
||||
const SingleActivator(LogicalKeyboardKey.f5): () => _button(ExplorerButton.refresh),
|
||||
const SingleActivator(LogicalKeyboardKey.keyC, control: true): () => _toClipboard(_targets(_pane), move: false),
|
||||
const SingleActivator(LogicalKeyboardKey.keyX, control: true): () => _toClipboard(_targets(_pane), move: true),
|
||||
const SingleActivator(LogicalKeyboardKey.keyV, control: true): () => _paste(_pane, _pane.current),
|
||||
const SingleActivator(LogicalKeyboardKey.keyA, control: true): () {
|
||||
/// 83 · X-plore 비교 5-1 ⑥ (관리자 지시 10/21): 동작마다의 키 → 할 일. 처음 키는 스타일대로 (Total Commander: F5 복사 · F6 이동 ·
|
||||
/// F7 새 폴더 · F8 지우기 · Insert/Space 표시 · Ctrl+R 새로 고침 / Windows 탐색기 · X-plore: Ctrl+C · X · V · A · F5 새로 고침),
|
||||
/// 사용자가 바꾼 것은 설정 explorerKeyOverrides. Rsync 화면은 늘 같은 키 (바꾼 것을 쓰지 않음)
|
||||
Map<ShortcutActivator, VoidCallback> _keyBindings() {
|
||||
final cb = <String, VoidCallback>{
|
||||
'copy': () => _button(ExplorerButton.copy),
|
||||
'move': () => _button(ExplorerButton.move),
|
||||
'newFolder': () => _button(ExplorerButton.newFolder),
|
||||
'delete': () => _button(ExplorerButton.delete),
|
||||
// 65: Shift+Delete = 휴지통을 거치지 않고 (창에서 한 번 더 확인)
|
||||
'deletePermanent': () {
|
||||
_shiftDelete = true;
|
||||
_button(ExplorerButton.delete);
|
||||
},
|
||||
'rename': () => _button(ExplorerButton.rename),
|
||||
'refresh': () => _button(ExplorerButton.refresh),
|
||||
// 81: 빠른 보기
|
||||
'quickLook': () => _quickLook(_pane),
|
||||
'clipCopy': () => _toClipboard(_targets(_pane), move: false),
|
||||
'clipCut': () => _toClipboard(_targets(_pane), move: true),
|
||||
'paste': () => _paste(_pane, _pane.current),
|
||||
'selectAll': () {
|
||||
if (!_selecting) setState(() => _selecting = true);
|
||||
_markWhere(_pane, _pane.current, (_) => true);
|
||||
},
|
||||
// TC: Space 표시 · Insert 표시하고 다음 줄로
|
||||
'mark': () => _markFocused(next: false),
|
||||
'markNext': () => _markFocused(next: true),
|
||||
'filter': () => _openFilter(_panes[_active]),
|
||||
'up': () => _button(ExplorerButton.up),
|
||||
// 75: 지금 창의 뒤로 · 앞으로
|
||||
'back': () => _travel(_panes[_active], forward: false),
|
||||
'forward': () => _travel(_panes[_active], forward: true),
|
||||
'switchPane': () => setState(() => _active = _dual ? 1 - _active : 0),
|
||||
};
|
||||
final keys = widget.rsync
|
||||
? {
|
||||
for (final e in defaultExplorerKeys(tc: false).entries)
|
||||
if (const {'delete', 'deletePermanent', 'rename', 'refresh', 'filter', 'up', 'back', 'forward', 'switchPane'}.contains(e.key))
|
||||
e.key: e.value,
|
||||
}
|
||||
: effectiveExplorerKeys(tc: _tcKeys, overrides: c.settings.explorerKeyOverrides);
|
||||
return {
|
||||
for (final e in keys.entries)
|
||||
if (cb[e.key] != null)
|
||||
for (final a in e.value) a: cb[e.key]!,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import 'ftp_settings.dart';
|
|||
import 'smb_settings.dart';
|
||||
import 'webdav_settings.dart';
|
||||
import 'explorer_look.dart' show ExplorerStyle;
|
||||
import 'explorer_keys_page.dart';
|
||||
import 'theme.dart';
|
||||
import '../l10n/tr.dart';
|
||||
import 'setting_tile.dart';
|
||||
|
|
@ -888,6 +889,16 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
onChanged: (v) => c.updateSettings((x) => x.explorerFolderSize = v ?? 'sort'),
|
||||
),
|
||||
),
|
||||
// X-plore 비교 5-1 ⑥ (관리자 지시 10/21): 단축키 바꾸기 (키보드가 있는 PC 에서만 보임)
|
||||
if (desk)
|
||||
ListTile(
|
||||
title: Text(tr('파일 탐색기 단축키')),
|
||||
subtitle: Text(s.explorerKeyOverrides.isEmpty
|
||||
? tr('스타일대로 (바꾼 것 없음)')
|
||||
: trf('바꾼 것 {0}개', [s.explorerKeyOverrides.length])),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () => ExplorerKeysPage.open(context, c),
|
||||
),
|
||||
SettingTile(
|
||||
title: Text(tr('스타일')),
|
||||
// 83: 스타일마다 단축키 - PC 에서만 (194: 키보드가 없는 폰 · 태블릿에는 쓸모없는 긴 글, 15 지적)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,132 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:jj_mkvmaker/app/app_controller.dart';
|
||||
import 'package:jj_mkvmaker/app/settings.dart';
|
||||
import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart';
|
||||
import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart';
|
||||
import 'package:jj_mkvmaker/services/platform_services.dart';
|
||||
import 'package:jj_mkvmaker/ui/explorer_keys.dart';
|
||||
import 'package:jj_mkvmaker/ui/explorer_keys_page.dart';
|
||||
import 'package:jj_mkvmaker/ui/explorer_page.dart';
|
||||
|
||||
/// X-plore 비교 5-1 ⑥ (관리자 지시 10/21): 파일 탐색기 단축키 바꾸기 - 처음은 83 과 같은 키 그대로, 바꾼 것만 설정에
|
||||
void main() {
|
||||
const k = LogicalKeyboardKey.keyK;
|
||||
List<String> texts(List<SingleActivator> l) => l.map(keyText).toList();
|
||||
|
||||
test('바꾼 것이 없으면 (처음) 83 의 키 그대로 - Total Commander / 그 밖', () {
|
||||
final tc = effectiveExplorerKeys(tc: true, overrides: {});
|
||||
expect(texts(tc['copy']!), ['F5']);
|
||||
expect(texts(tc['move']!), ['F6']);
|
||||
expect(texts(tc['newFolder']!), ['F7']);
|
||||
expect(texts(tc['delete']!), ['Delete', 'F8']);
|
||||
expect(texts(tc['refresh']!), ['Ctrl+R']);
|
||||
expect(texts(tc['mark']!), ['Space']);
|
||||
expect(texts(tc['markNext']!), ['Insert']);
|
||||
expect(texts(tc['quickLook']!), ['F3']);
|
||||
final win = effectiveExplorerKeys(tc: false, overrides: {});
|
||||
expect(texts(win['refresh']!), ['F5']);
|
||||
expect(texts(win['clipCopy']!), ['Ctrl+C']);
|
||||
expect(texts(win['paste']!), ['Ctrl+V']);
|
||||
expect(texts(win['selectAll']!), ['Ctrl+A']);
|
||||
expect(texts(win['quickLook']!), ['Space']);
|
||||
expect(win['copy'], isNull, reason: 'Windows 모양은 F5 가 새로 고침 (복사 키 없음)');
|
||||
expect(texts(win['deletePermanent']!), ['Shift+Delete']);
|
||||
expect(texts(win['back']!), ['Alt+Arrow Left']);
|
||||
});
|
||||
|
||||
test('바꾸기: 그 동작은 새 키만 · 겹치는 다른 동작은 그 키를 잃음 · \'\' 는 없음 · 틀린 값은 처음 키 · 저장 꼴 왕복', () {
|
||||
final spec = keySpec(const SingleActivator(k, control: true, shift: true));
|
||||
expect(parseKeySpec(spec), isA<SingleActivator>().having((a) => keyText(a), 'text', 'Ctrl+Shift+K'));
|
||||
final a = effectiveExplorerKeys(tc: false, overrides: {'newFolder': spec, 'paste': '', 'rename': 'zzz'});
|
||||
expect(texts(a['newFolder']!), ['Ctrl+Shift+K']);
|
||||
expect(a['paste'], isEmpty);
|
||||
expect(texts(a['rename']!), ['F2'], reason: '틀린 값은 처음 키');
|
||||
// F5 를 복사에 주면 새로 고침은 F5 를 잃음
|
||||
final b = effectiveExplorerKeys(tc: false, overrides: {'copy': keySpec(const SingleActivator(LogicalKeyboardKey.f5))});
|
||||
expect(texts(b['copy']!), ['F5']);
|
||||
expect(b['refresh'], isEmpty);
|
||||
expect(keyConflict('copy', const SingleActivator(LogicalKeyboardKey.f5), tc: false, overrides: {}), 'refresh');
|
||||
expect(keyConflict('copy', const SingleActivator(LogicalKeyboardKey.escape), tc: false, overrides: {}), 'escape');
|
||||
expect(keyConflict('copy', const SingleActivator(k, control: true), tc: false, overrides: {}), isNull);
|
||||
// 설정 저장 · 읽기 · 틀린 꼴은 비움
|
||||
final back = AppSettings.fromJson((AppSettings()..explorerKeyOverrides = {'copy': spec}).toJson());
|
||||
expect(back.explorerKeyOverrides, {'copy': spec});
|
||||
expect(AppSettings.fromJson({'explorerKeyOverrides': 'x'}).explorerKeyOverrides, isEmpty);
|
||||
expect(AppSettings().explorerKeyOverrides, isEmpty);
|
||||
});
|
||||
|
||||
late Directory tmp;
|
||||
setUp(() => tmp = Directory.systemTemp.createTempSync('jj_keys_'));
|
||||
tearDown(() => tmp.deleteSync(recursive: true));
|
||||
|
||||
AppController ctl() => AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
|
||||
testWidgets('탐색기: 바꾼 키 (새 폴더 = Ctrl+Shift+K) 를 누르면 그 동작 · 처음 키도 그대로 (F2 이름 변경)', (tester) async {
|
||||
tester.view.physicalSize = const Size(1400, 900);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
final c = ctl();
|
||||
c.settings
|
||||
..explorerPaths = [tmp.path, tmp.path]
|
||||
..explorerStyle = 'windows'
|
||||
..explorerKeyOverrides = {'newFolder': keySpec(const SingleActivator(k, control: true, shift: true))};
|
||||
await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(c: c))));
|
||||
for (var i = 0; i < 20; i++) {
|
||||
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 30)));
|
||||
await tester.pump();
|
||||
}
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
|
||||
await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft);
|
||||
await tester.sendKeyEvent(k);
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft);
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.widgetWithText(AlertDialog, '새 폴더'), findsOneWidget, reason: '바꾼 키로 새 폴더 창');
|
||||
await tester.tap(find.text('취소').last);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
});
|
||||
|
||||
testWidgets('설정 화면: [바꾸기] 에서 누른 키 저장 · 겹치면 알림 · [처음 값으로] · [단축키 없음]', (tester) async {
|
||||
tester.view.physicalSize = const Size(1000, 1600);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
final c = ctl()..settings.explorerStyle = 'windows';
|
||||
await tester.pumpWidget(MaterialApp(home: ExplorerKeysPage(c: c)));
|
||||
Future<void> change(String label) async {
|
||||
await tester.tap(find.descendant(of: find.widgetWithText(ListTile, label), matching: find.text('바꾸기')));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
await change('다른 창으로 복사');
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.f5);
|
||||
await tester.pump();
|
||||
expect(find.textContaining('이미 "새로 고침" 에 쓰는 키입니다'), findsOneWidget);
|
||||
await tester.tap(find.text('저장'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(c.settings.explorerKeyOverrides['copy'], keySpec(const SingleActivator(LogicalKeyboardKey.f5)));
|
||||
expect(find.descendant(of: find.widgetWithText(ListTile, '새로 고침'), matching: find.text('(없음)')), findsOneWidget,
|
||||
reason: '겹친 동작은 키를 잃음');
|
||||
// 처음 값으로
|
||||
await tester.tap(find.descendant(of: find.widgetWithText(ListTile, '다른 창으로 복사'), matching: find.text('처음 값으로')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(c.settings.explorerKeyOverrides, isEmpty);
|
||||
// 단축키 없음
|
||||
await change('붙여넣기');
|
||||
await tester.tap(find.text('단축키 없음'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(c.settings.explorerKeyOverrides['paste'], '');
|
||||
// Esc 는 저장할 수 없음
|
||||
await change('삭제');
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
|
||||
await tester.pump();
|
||||
if (find.byType(AlertDialog).evaluate().isNotEmpty) {
|
||||
expect(tester.widget<FilledButton>(find.widgetWithText(FilledButton, '저장')).onPressed, isNull);
|
||||
}
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue