JJ_MKVMaker/lib/ui/explorer_keys_page.dart

147 lines
6.4 KiB
Dart

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: 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: TextStyle(color: JjColors.danger, fontSize: 12)),
)
else if (clash != null)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(trf('이미 "{0}" 에 쓰는 키입니다 - 저장하면 그 동작의 이 키는 없어집니다', [explorerKeyLabel(clash)]),
style: TextStyle(color: JjColors.warning, 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('저장'))),
],
);
}
}