|
|
@ -1,5 +1,4 @@ |
|
|
|
import 'package:flutter/material.dart'; |
|
|
|
import 'package:flutter/material.dart'; |
|
|
|
import 'package:flutter/services.dart'; |
|
|
|
|
|
|
|
import 'package:provider/provider.dart'; |
|
|
|
import 'package:provider/provider.dart'; |
|
|
|
import 'package:pluto_grid/pluto_grid.dart'; |
|
|
|
import 'package:pluto_grid/pluto_grid.dart'; |
|
|
|
import 'package:path/path.dart' as path; |
|
|
|
import 'package:path/path.dart' as path; |
|
|
@ -11,6 +10,7 @@ import 'dart:io'; |
|
|
|
|
|
|
|
|
|
|
|
import 'package:win_text_editor/modules/xml_search/controllers/xml_search_controller.dart'; |
|
|
|
import 'package:win_text_editor/modules/xml_search/controllers/xml_search_controller.dart'; |
|
|
|
import 'package:win_text_editor/shared/components/my_pluto_column.dart'; |
|
|
|
import 'package:win_text_editor/shared/components/my_pluto_column.dart'; |
|
|
|
|
|
|
|
import 'package:win_text_editor/shared/components/my_pluto_configuration.dart'; |
|
|
|
|
|
|
|
|
|
|
|
class ResultsView extends StatefulWidget { |
|
|
|
class ResultsView extends StatefulWidget { |
|
|
|
const ResultsView({super.key}); |
|
|
|
const ResultsView({super.key}); |
|
|
@ -20,19 +20,29 @@ class ResultsView extends StatefulWidget { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
class _ResultsViewState extends State<ResultsView> { |
|
|
|
class _ResultsViewState extends State<ResultsView> { |
|
|
|
late final PlutoGridStateManager stateManager; |
|
|
|
late final XmlSearchController controller; |
|
|
|
|
|
|
|
PlutoGridStateManager? stateManager; |
|
|
|
|
|
|
|
|
|
|
|
@override |
|
|
|
@override |
|
|
|
Widget build(BuildContext context) { |
|
|
|
void initState() { |
|
|
|
final controller = context.watch<XmlSearchController>(); |
|
|
|
super.initState(); |
|
|
|
|
|
|
|
controller = context.read<XmlSearchController>(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return Card( |
|
|
|
@override |
|
|
|
child: GestureDetector( |
|
|
|
Widget build(BuildContext context) { |
|
|
|
onSecondaryTapDown: (details) { |
|
|
|
// 直接使用Consumer而不是ValueListenableBuilder |
|
|
|
_showContextMenu(context, details.globalPosition, controller); |
|
|
|
return Consumer<XmlSearchController>( |
|
|
|
}, |
|
|
|
builder: (context, controller, child) { |
|
|
|
child: _buildLocateGrid(controller, context), |
|
|
|
return Card( |
|
|
|
), |
|
|
|
child: GestureDetector( |
|
|
|
|
|
|
|
onSecondaryTapDown: (details) { |
|
|
|
|
|
|
|
_showContextMenu(context, details.globalPosition, controller); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
child: _buildResultGrid(controller), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
}, |
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -87,68 +97,40 @@ class _ResultsViewState extends State<ResultsView> { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Widget _buildLocateGrid(XmlSearchController controller, BuildContext context) { |
|
|
|
Widget _buildResultGrid(XmlSearchController controller) { |
|
|
|
return PlutoGrid( |
|
|
|
return PlutoGrid( |
|
|
|
configuration: const PlutoGridConfiguration( |
|
|
|
key: ValueKey(controller.results.hashCode), |
|
|
|
style: PlutoGridStyleConfig( |
|
|
|
configuration: MyPlutoGridConfiguration(), |
|
|
|
rowHeight: 32, |
|
|
|
|
|
|
|
columnHeight: 32, |
|
|
|
|
|
|
|
iconColor: Colors.transparent, |
|
|
|
|
|
|
|
gridBorderRadius: BorderRadius.zero, |
|
|
|
|
|
|
|
columnTextStyle: TextStyle( |
|
|
|
|
|
|
|
color: Colors.black, |
|
|
|
|
|
|
|
fontSize: 14, |
|
|
|
|
|
|
|
fontWeight: FontWeight.normal, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
columnSize: PlutoGridColumnSizeConfig( |
|
|
|
|
|
|
|
autoSizeMode: PlutoAutoSizeMode.scale, |
|
|
|
|
|
|
|
resizeMode: PlutoResizeMode.pushAndPull, |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
columns: _buildColumns(), |
|
|
|
columns: _buildColumns(), |
|
|
|
mode: PlutoGridMode.normal, |
|
|
|
mode: PlutoGridMode.normal, |
|
|
|
rows: _buildRows(controller), |
|
|
|
rows: controller.results.isEmpty ? [] : _buildRows(controller), |
|
|
|
onLoaded: (PlutoGridOnLoadedEvent event) { |
|
|
|
noRowsWidget: const Center(child: Text('没有搜索结果')), |
|
|
|
stateManager = event.stateManager; |
|
|
|
|
|
|
|
stateManager.setSelectingMode(PlutoGridSelectingMode.row); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 添加行选中状态监听器 |
|
|
|
|
|
|
|
stateManager.addListener(() { |
|
|
|
|
|
|
|
if (stateManager.hasFocus) { |
|
|
|
|
|
|
|
final checkedRows = stateManager.checkedRows; |
|
|
|
|
|
|
|
for (var row in checkedRows) { |
|
|
|
|
|
|
|
final result = row.cells['action']?.value as SearchResult?; |
|
|
|
|
|
|
|
if (result != null) { |
|
|
|
|
|
|
|
result.isSelected = true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 更新未选中的行 |
|
|
|
onLoaded: (PlutoGridOnLoadedEvent event) { |
|
|
|
for (var row in stateManager.refRows.where((r) => !checkedRows.contains(r))) { |
|
|
|
stateManager ??= event.stateManager; |
|
|
|
final result = row.cells['action']?.value as SearchResult?; |
|
|
|
}, |
|
|
|
if (result != null) { |
|
|
|
|
|
|
|
result.isSelected = false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
controller.notifyListeners(); |
|
|
|
onRowChecked: (PlutoGridOnRowCheckedEvent e) { |
|
|
|
} |
|
|
|
if (e.isAll) { |
|
|
|
}); |
|
|
|
controller.toggleSelectAll(e.isChecked!); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
final result = e.row?.cells['action']!.value as SearchResult; |
|
|
|
|
|
|
|
result.isSelected = e.isChecked!; |
|
|
|
|
|
|
|
} |
|
|
|
}, |
|
|
|
}, |
|
|
|
noRowsWidget: const Center(child: Text('没有搜索结果')), |
|
|
|
|
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
List<PlutoRow> _buildRows(XmlSearchController controller) { |
|
|
|
List<PlutoRow> _buildRows(XmlSearchController controller) { |
|
|
|
return controller.results.map((result) { |
|
|
|
return controller.results.asMap().entries.map((entry) { |
|
|
|
|
|
|
|
final index = entry.key; |
|
|
|
|
|
|
|
final result = entry.value; |
|
|
|
return PlutoRow( |
|
|
|
return PlutoRow( |
|
|
|
checked: result.isSelected, // 绑定选中状态到内置复选框 |
|
|
|
key: ValueKey('${result.hashCode}_$index'), // 复合key确保唯一性 |
|
|
|
cells: { |
|
|
|
cells: { |
|
|
|
'rowNum': PlutoCell(value: result.rowNum), |
|
|
|
'rowNum': PlutoCell(value: index + 1), |
|
|
|
'content': PlutoCell(value: result.attributeValue), |
|
|
|
'content': PlutoCell(value: result.attributeValue), |
|
|
|
'index': PlutoCell(value: result.index ?? 0), |
|
|
|
'index': PlutoCell(value: result.index), |
|
|
|
'action': PlutoCell(value: result), |
|
|
|
'action': PlutoCell(value: result), |
|
|
|
}, |
|
|
|
}, |
|
|
|
); |
|
|
|
); |
|
|
@ -157,7 +139,7 @@ class _ResultsViewState extends State<ResultsView> { |
|
|
|
|
|
|
|
|
|
|
|
List<PlutoColumn> _buildColumns() { |
|
|
|
List<PlutoColumn> _buildColumns() { |
|
|
|
return [ |
|
|
|
return [ |
|
|
|
MyPlutoColumn(title: '序号', field: 'rowNum', width: 90, checkable: true), |
|
|
|
MyPlutoColumn(title: '#', field: 'rowNum', width: 60, checkable: true), |
|
|
|
MyPlutoColumn( |
|
|
|
MyPlutoColumn( |
|
|
|
title: '搜索内容', |
|
|
|
title: '搜索内容', |
|
|
|
field: 'content', |
|
|
|
field: 'content', |
|
|
@ -190,69 +172,47 @@ class _ResultsViewState extends State<ResultsView> { |
|
|
|
PlutoRow row, |
|
|
|
PlutoRow row, |
|
|
|
SearchResult result, |
|
|
|
SearchResult result, |
|
|
|
) async { |
|
|
|
) async { |
|
|
|
bool confirmed = |
|
|
|
final confirmed = |
|
|
|
await showDialog<bool>( |
|
|
|
await showDialog<bool>( |
|
|
|
context: context, |
|
|
|
context: context, |
|
|
|
builder: (context) { |
|
|
|
builder: |
|
|
|
return Shortcuts( |
|
|
|
(context) => AlertDialog( |
|
|
|
shortcuts: const {SingleActivator(LogicalKeyboardKey.enter): _ConfirmAction()}, |
|
|
|
title: const Text('确认删除'), |
|
|
|
child: Actions( |
|
|
|
content: Text('确定要删除 ${result.attributeValue} 吗?'), |
|
|
|
actions: { |
|
|
|
actions: [ |
|
|
|
_ConfirmAction: CallbackAction<_ConfirmAction>( |
|
|
|
TextButton( |
|
|
|
onInvoke: (_) { |
|
|
|
onPressed: () => Navigator.pop(context, false), |
|
|
|
Navigator.pop(context, true); |
|
|
|
child: const Text('取消'), |
|
|
|
return null; |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
), |
|
|
|
), |
|
|
|
}, |
|
|
|
TextButton( |
|
|
|
child: Focus( |
|
|
|
onPressed: () => Navigator.pop(context, true), |
|
|
|
autofocus: true, |
|
|
|
child: const Text('删除'), |
|
|
|
child: AlertDialog( |
|
|
|
|
|
|
|
title: const Text('确认删除'), |
|
|
|
|
|
|
|
content: Column( |
|
|
|
|
|
|
|
mainAxisSize: MainAxisSize.min, |
|
|
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start, |
|
|
|
|
|
|
|
children: [ |
|
|
|
|
|
|
|
const Text('将所选xml节点删除吗?'), |
|
|
|
|
|
|
|
const SizedBox(height: 8), |
|
|
|
|
|
|
|
Text( |
|
|
|
|
|
|
|
'${result.attributeValue}[${result.index}]', |
|
|
|
|
|
|
|
style: const TextStyle(fontSize: 12, color: Colors.grey), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
actions: [ |
|
|
|
|
|
|
|
TextButton( |
|
|
|
|
|
|
|
onPressed: () => Navigator.pop(context, false), |
|
|
|
|
|
|
|
child: const Text('取消'), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
TextButton( |
|
|
|
|
|
|
|
onPressed: () => Navigator.pop(context, true), |
|
|
|
|
|
|
|
child: const Text('确认'), |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
], |
|
|
|
|
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
); |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
) ?? |
|
|
|
) ?? |
|
|
|
false; |
|
|
|
false; |
|
|
|
|
|
|
|
|
|
|
|
if (confirmed && context.mounted) { |
|
|
|
if (confirmed && context.mounted) { |
|
|
|
try { |
|
|
|
try { |
|
|
|
final controller = context.read<XmlSearchController>(); |
|
|
|
final controller = context.read<XmlSearchController>(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 从数据源删除 |
|
|
|
controller.removeResult(result); |
|
|
|
controller.removeResult(result); |
|
|
|
stateManager.removeRows([row]); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Logger().info('已删除节点文件: ${result.attributeValue}[${result.index}]'); |
|
|
|
Logger().info('已删除节点: ${result.attributeValue}'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ScaffoldMessenger.of( |
|
|
|
|
|
|
|
context, |
|
|
|
|
|
|
|
).showSnackBar(SnackBar(content: Text('已删除: ${result.attributeValue}'))); |
|
|
|
} catch (e) { |
|
|
|
} catch (e) { |
|
|
|
Logger().error('删除失败: ${e.toString()}'); |
|
|
|
Logger().error('删除失败: $e'); |
|
|
|
|
|
|
|
if (context.mounted) { |
|
|
|
|
|
|
|
ScaffoldMessenger.of( |
|
|
|
|
|
|
|
context, |
|
|
|
|
|
|
|
).showSnackBar(SnackBar(content: Text('删除失败: ${e.toString()}'))); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
class _ConfirmAction extends Intent { |
|
|
|
|
|
|
|
const _ConfirmAction(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|