Compare commits

...

3 Commits

  1. 2
      win_text_editor/assets/config/words_classes.yaml
  2. 1
      win_text_editor/lib/framework/controllers/file_provider.dart
  3. 5
      win_text_editor/lib/framework/controllers/tab_items_controller.dart
  4. 3
      win_text_editor/lib/framework/models/file_node.dart
  5. 23
      win_text_editor/lib/framework/widgets/app_scaffold.dart
  6. 12
      win_text_editor/lib/menus/app_menu.dart
  7. 2
      win_text_editor/lib/modules/call_function/controllers/call_function_controller.dart
  8. 2
      win_text_editor/lib/modules/content_search/controllers/content_search_controller.dart
  9. 2
      win_text_editor/lib/modules/data_compare/controllers/data_compare_controller.dart
  10. 2
      win_text_editor/lib/modules/data_extract/controllers/data_extract_controller.dart
  11. 2
      win_text_editor/lib/modules/data_format/controllers/data_format_controller.dart
  12. 3
      win_text_editor/lib/modules/data_format/models/template_node.dart
  13. 2
      win_text_editor/lib/modules/demo/controllers/demo_controller.dart
  14. 2
      win_text_editor/lib/modules/memory_table/controllers/memory_table_controller.dart
  15. 4
      win_text_editor/lib/modules/module_router.dart
  16. 13
      win_text_editor/lib/modules/outline/controllers/outline_controller.dart
  17. 15
      win_text_editor/lib/modules/outline/controllers/outline_provider.dart
  18. 53
      win_text_editor/lib/modules/outline/models/outline_node.dart
  19. 240
      win_text_editor/lib/modules/outline/services/outline_service.dart
  20. 90
      win_text_editor/lib/modules/outline/widgets/outline_explorer.dart
  21. 73
      win_text_editor/lib/modules/outline/widgets/outline_view.dart
  22. 2
      win_text_editor/lib/modules/template_parser/controllers/template_parser_controller.dart
  23. 15
      win_text_editor/lib/modules/uft_component/controllers/uft_component_controller.dart
  24. 15
      win_text_editor/lib/modules/uft_component/services/uft_component_service.dart
  25. 2
      win_text_editor/lib/modules/xml_search/controllers/xml_search_controller.dart
  26. 2
      win_text_editor/lib/shared/base/base_content_controller.dart
  27. 6
      win_text_editor/lib/shared/components/tree_view.dart
  28. 3
      win_text_editor/lib/shared/models/template_node.dart

2
win_text_editor/assets/config/words_classes.yaml

@ -1,2 +1,2 @@ @@ -1,2 +1,2 @@
outline_name_black_list:
- 历史,日志,名称,比例,数量,金额,次数,属性,对应,分类,姓名,单位,总数,行使,子项,占比,记录,列表,目标,字段,字符串,动作
- 历史,日志,名称,比例,数量,金额,次数,属性,对应,分类,姓名,单位,总数,行使,子项,占比,记录,列表,目标,字段,字符串,动作,方式

1
win_text_editor/lib/framework/controllers/file_provider.dart

@ -23,6 +23,7 @@ class FileProvider with ChangeNotifier { @@ -23,6 +23,7 @@ class FileProvider with ChangeNotifier {
//
Future<void> setRootPath(String path) async {
_currentRootPath = path;
notifyListeners();
await _loadRootDirectory();
}

5
win_text_editor/lib/framework/controllers/tab_items_controller.dart

@ -111,7 +111,8 @@ class TabItemsController with ChangeNotifier { @@ -111,7 +111,8 @@ class TabItemsController with ChangeNotifier {
activeContentController?.onOpenFolder(folderPath);
}
void handleFileDoubleTap(String filePath) {
void handleFileDoubleTap(String filePath, dynamic appendArg) {
Logger().debug('双击事件参数:filePath:$filePath,appendArg:$appendArg');
final fileName = filePath.split(Platform.pathSeparator).last;
if (fileName == "component.xml") {
openOrActivateTab("标准组件", RouterKey.uftComponent, Icons.extension);
@ -133,7 +134,7 @@ class TabItemsController with ChangeNotifier { @@ -133,7 +134,7 @@ class TabItemsController with ChangeNotifier {
}
}
activeContentController?.onOpenFile(filePath);
activeContentController?.onOpenFile(filePath, appendArg: appendArg);
}
bool hasController(String tabId) {

3
win_text_editor/lib/framework/models/file_node.dart

@ -19,6 +19,9 @@ class FileNode implements TreeNode { @@ -19,6 +19,9 @@ class FileNode implements TreeNode {
@override
String get title => name;
@override
bool get isVisible => true;
FileNode({
required this.name,
required this.path,

23
win_text_editor/lib/framework/widgets/app_scaffold.dart

@ -6,6 +6,8 @@ import 'package:win_text_editor/framework/widgets/tab_view.dart'; @@ -6,6 +6,8 @@ import 'package:win_text_editor/framework/widgets/tab_view.dart';
import 'package:win_text_editor/menus/app_menu.dart';
import 'package:win_text_editor/framework/controllers/file_provider.dart';
import 'package:win_text_editor/framework/widgets/console_panel.dart';
import 'package:win_text_editor/modules/outline/controllers/outline_provider.dart';
import 'package:win_text_editor/modules/outline/widgets/outline_explorer.dart';
class AppScaffold extends StatelessWidget {
const AppScaffold({super.key});
@ -16,6 +18,7 @@ class AppScaffold extends StatelessWidget { @@ -16,6 +18,7 @@ class AppScaffold extends StatelessWidget {
providers: [
ChangeNotifierProvider(create: (_) => FileProvider()),
ChangeNotifierProvider(create: (_) => TabItemsController()),
ChangeNotifierProvider(create: (_) => OutlineProvider()), // Add OutlineProvider
],
child: Scaffold(
backgroundColor: Colors.grey[100],
@ -29,7 +32,7 @@ class AppScaffold extends StatelessWidget { @@ -29,7 +32,7 @@ class AppScaffold extends StatelessWidget {
Consumer<TabItemsController>(
builder: (context, tabManager, child) {
return FileExplorerPane(
onFileDoubleTap: (path) => tabManager.handleFileDoubleTap(path),
onFileDoubleTap: (path) => tabManager.handleFileDoubleTap(path, null),
onFolderDoubleTap: (path) => tabManager.handleFolderDoubleTap(path),
);
},
@ -42,6 +45,24 @@ class AppScaffold extends StatelessWidget { @@ -42,6 +45,24 @@ class AppScaffold extends StatelessWidget {
TabView(tabs: manager.tabs, currentTabId: manager.activeTabId),
),
),
//
SizedBox(
width: 300,
child: Consumer<TabItemsController>(
builder: (context, tabManager, child) {
return OutlineExplorer(
onFileDoubleTap: (path, dynamic appendArg) {
// Handle file double tap if needed
tabManager.handleFileDoubleTap(path, appendArg);
},
onFolderDoubleTap: (path) {
// Handle folder double tap if needed
tabManager.handleFolderDoubleTap(path);
},
);
},
),
),
],
),
),

12
win_text_editor/lib/menus/app_menu.dart

@ -72,11 +72,11 @@ class AppMenu extends StatelessWidget { @@ -72,11 +72,11 @@ class AppMenu extends StatelessWidget {
value: MenuConstants.callFunction,
child: ListTile(leading: Icon(Icons.functions), title: Text('功能号调用')),
),
const PopupMenuDivider(),
const PopupMenuItem<String>(
value: MenuConstants.outline,
child: ListTile(leading: Icon(Icons.outlined_flag_rounded), title: Text('大纲')),
),
// const PopupMenuDivider(),
// const PopupMenuItem<String>(
// value: MenuConstants.outline,
// child: ListTile(leading: Icon(Icons.outlined_flag_rounded), title: Text('大纲')),
// ),
];
}
@ -91,7 +91,7 @@ class AppMenu extends StatelessWidget { @@ -91,7 +91,7 @@ class AppMenu extends StatelessWidget {
List<PopupMenuEntry<String>> _buildFileMenuItems() {
return [
const PopupMenuItem<String>(value: MenuConstants.openFolder, child: Text('打开文件夹...')),
// const PopupMenuItem<String>(value: MenuConstants.openFolder, child: Text('打开文件夹...')),
const PopupMenuItem<String>(value: MenuConstants.save, child: Text('保存')),
const PopupMenuItem<String>(value: MenuConstants.saveAs, child: Text('另存为...')),
const PopupMenuItem<String>(value: MenuConstants.exit, child: Text('退出')),

2
win_text_editor/lib/modules/call_function/controllers/call_function_controller.dart

@ -76,7 +76,7 @@ class CallFunctionController extends BaseContentController { @@ -76,7 +76,7 @@ class CallFunctionController extends BaseContentController {
}
@override
Future<void> onOpenFile(String filePath) async {
Future<void> onOpenFile(String filePath, {dynamic appendArg}) async {
Logger().info("Opening file: $filePath");
try {
modle = await _service.parseXmlFile(filePath);

2
win_text_editor/lib/modules/content_search/controllers/content_search_controller.dart

@ -245,7 +245,7 @@ class ContentSearchController extends BaseContentController { @@ -245,7 +245,7 @@ class ContentSearchController extends BaseContentController {
}
@override
void onOpenFile(String filePath) {
void onOpenFile(String filePath, {dynamic appendArg}) {
//searchDirectory = filePath;
//notifyListeners();
}

2
win_text_editor/lib/modules/data_compare/controllers/data_compare_controller.dart

@ -241,7 +241,7 @@ class DataCompareController extends BaseContentController { @@ -241,7 +241,7 @@ class DataCompareController extends BaseContentController {
}
@override
void onOpenFile(String filePath) {
void onOpenFile(String filePath, {dynamic appendArg}) {
// TODO: implement onOpenFile
}

2
win_text_editor/lib/modules/data_extract/controllers/data_extract_controller.dart

@ -80,7 +80,7 @@ class DataExtractController extends BaseContentController { @@ -80,7 +80,7 @@ class DataExtractController extends BaseContentController {
}
@override
void onOpenFile(String filePath) {
void onOpenFile(String filePath, {dynamic appendArg}) {
// TODO: implement onOpenFile
}

2
win_text_editor/lib/modules/data_format/controllers/data_format_controller.dart

@ -94,7 +94,7 @@ class DataFormatController extends BaseContentController { @@ -94,7 +94,7 @@ class DataFormatController extends BaseContentController {
}
@override
void onOpenFile(String filePath) {}
void onOpenFile(String filePath, {dynamic appendArg}) {}
@override
void onOpenFolder(String folderPath) {

3
win_text_editor/lib/modules/data_format/models/template_node.dart

@ -40,6 +40,9 @@ class TemplateNode implements TreeNode { @@ -40,6 +40,9 @@ class TemplateNode implements TreeNode {
@override
String get id => path;
@override
bool get isVisible => true;
}
enum NodeType { element, attribute, text }

2
win_text_editor/lib/modules/demo/controllers/demo_controller.dart

@ -2,7 +2,7 @@ import 'package:win_text_editor/shared/base/base_content_controller.dart'; @@ -2,7 +2,7 @@ import 'package:win_text_editor/shared/base/base_content_controller.dart';
class DemoController extends BaseContentController {
@override
void onOpenFile(String filePath) {
void onOpenFile(String filePath, {dynamic appendArg}) {
// TODO: implement onOpenFile
}

2
win_text_editor/lib/modules/memory_table/controllers/memory_table_controller.dart

@ -63,7 +63,7 @@ class MemoryTableController extends BaseContentController { @@ -63,7 +63,7 @@ class MemoryTableController extends BaseContentController {
}
@override
Future<void> onOpenFile(String filePath) async {
Future<void> onOpenFile(String filePath, {dynamic appendArg}) async {
Logger().info("Opening file: $filePath");
try {
final tableData = await _service.parseStructureFile(filePath);

4
win_text_editor/lib/modules/module_router.dart

@ -13,8 +13,6 @@ import 'package:win_text_editor/modules/demo/controllers/demo_controller.dart'; @@ -13,8 +13,6 @@ import 'package:win_text_editor/modules/demo/controllers/demo_controller.dart';
import 'package:win_text_editor/modules/demo/widgets/demo_view.dart';
import 'package:win_text_editor/modules/memory_table/controllers/memory_table_controller.dart';
import 'package:win_text_editor/modules/memory_table/widgets/memory_table_view.dart';
import 'package:win_text_editor/modules/outline/controllers/outline_controller.dart';
import 'package:win_text_editor/modules/outline/widgets/outline_view.dart';
import 'package:win_text_editor/modules/uft_component/controllers/uft_component_controller.dart';
import 'package:win_text_editor/modules/uft_component/widgets/uft_component_view.dart';
import 'package:win_text_editor/modules/xml_search/controllers/xml_search_controller.dart';
@ -52,7 +50,6 @@ class ModuleRouter { @@ -52,7 +50,6 @@ class ModuleRouter {
RouterKey.memoryTable: (tab) => MemoryTableController(),
RouterKey.uftComponent: (tab) => UftComponentController(),
RouterKey.callFunction: (tab) => CallFunctionController(),
RouterKey.outline: (tab) => OutlineController(),
RouterKey.demo: (tab) => DemoController(),
};
@ -67,7 +64,6 @@ class ModuleRouter { @@ -67,7 +64,6 @@ class ModuleRouter {
RouterKey.memoryTable: (tab, controller) => MemoryTableView(tabId: tab.id),
RouterKey.uftComponent: (tab, controller) => UftComponentView(tabId: tab.id),
RouterKey.callFunction: (tab, controller) => CallFunctionView(tabId: tab.id),
RouterKey.outline: (tab, controller) => OutlineView(tabId: tab.id),
RouterKey.demo: (tab, controller) => DemoView(tabId: tab.id),
};

13
win_text_editor/lib/modules/outline/controllers/outline_controller.dart

@ -1,13 +0,0 @@ @@ -1,13 +0,0 @@
import 'package:win_text_editor/shared/base/base_content_controller.dart';
class OutlineController extends BaseContentController {
@override
void onOpenFile(String filePath) {
// TODO: implement onOpenFile
}
@override
void onOpenFolder(String folderPath) {
// TODO: implement onOpenFolder
}
}

15
win_text_editor/lib/modules/outline/controllers/outline_provider.dart

@ -7,6 +7,13 @@ class OutlineProvider with ChangeNotifier { @@ -7,6 +7,13 @@ class OutlineProvider with ChangeNotifier {
List<OutlineNode> _outlineNode = [];
bool _isLoading = true;
String? _currentRootPath; //
static OutlineNode rootNode = OutlineNode(
name: "所有Tag",
title: "所有Tag",
value: 'UFTTable',
frequency: 0,
isDirectory: true,
);
bool get isLoading => _isLoading;
bool get hasRoot => _outlineNode.isNotEmpty && _outlineNode[0].isRoot;
@ -18,11 +25,17 @@ class OutlineProvider with ChangeNotifier { @@ -18,11 +25,17 @@ class OutlineProvider with ChangeNotifier {
//
Future<void> setRootPath(String path) async {
if (path == _currentRootPath) {
return;
}
_currentRootPath = path;
await _loadRootDirectory();
}
List<OutlineNode> get outlineNode => _outlineNode;
List<OutlineNode> get outlineNode {
rootNode.children = _outlineNode;
return [rootNode];
}
void toggleExpand(OutlineNode node) {
node.isExpanded = !node.isExpanded;

53
win_text_editor/lib/modules/outline/models/outline_node.dart

@ -6,18 +6,28 @@ class OutlineNode implements TreeNode { @@ -6,18 +6,28 @@ class OutlineNode implements TreeNode {
@override
final String name;
String title;
String title = "";
List<OutlineNode> _children = [];
OutlineNode? parent;
@override
final bool isDirectory;
final bool isRoot;
@override
final int depth;
@override
List<OutlineNode> children;
List<OutlineNode> get children => _children;
@override
bool isExpanded;
final int frequency;
late String uuid;
@override
bool isVisible;
final String value;
String? wordClass;
@ -32,10 +42,16 @@ class OutlineNode implements TreeNode { @@ -32,10 +42,16 @@ class OutlineNode implements TreeNode {
this.wordClass,
List<OutlineNode>? children,
this.title = "",
}) : children = children ?? [];
this.isVisible = true,
}) : _children = children ?? [] {
uuid = DateTime.now().microsecondsSinceEpoch.toRadixString(36);
for (var child in _children) {
child.parent = this;
}
}
@override
String get id => '$depth-$name';
String get id => uuid;
//
@override
@ -47,33 +63,18 @@ class OutlineNode implements TreeNode { @@ -47,33 +63,18 @@ class OutlineNode implements TreeNode {
return Icons.insert_drive_file;
}
set children(List<OutlineNode> nodes) {
_children = nodes;
for (var child in _children) {
child.parent = this;
}
}
//
Widget get icon {
return Icon(iconData, color: isDirectory ? Colors.amber[700] : Colors.blue);
}
OutlineNode copyWith({
String? name,
String? value, // value is not nullable, so we keep it as is
int? frequency,
bool? isDirectory,
bool? isExpanded,
bool? isRoot,
List<OutlineNode>? children,
int? depth,
}) {
return OutlineNode(
name: name ?? this.name,
value: value ?? this.value, // value is not nullable, so we keep it as is
frequency: frequency ?? this.frequency,
isDirectory: isDirectory ?? this.isDirectory,
isExpanded: isExpanded ?? this.isExpanded,
isRoot: isRoot ?? this.isRoot,
children: children ?? this.children,
depth: depth ?? this.depth,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;

240
win_text_editor/lib/modules/outline/services/outline_service.dart

@ -160,14 +160,243 @@ class OutlineService { @@ -160,14 +160,243 @@ class OutlineService {
await _loadUftObject(rootPath, dirNode.value, dirNode);
break;
case 'Component':
await _loadComponent(rootPath, dirNode.value, dirNode);
break;
case 'Business':
//
await _loadBusiness(rootPath, dirNode.value, dirNode);
break;
case 'Atom':
await _loadAtom(rootPath, dirNode.value, dirNode);
break;
default:
Logger().error("操作节点类型不支持: ${dirNode.value}");
}
}
static Future<void> _loadComponent(
String rootPath,
String? fieldName,
OutlineNode parentNode,
) async {
if (fieldName == null || fieldName.isEmpty) return;
final componentFile = File('$rootPath/metadata/component.xml');
if (!await componentFile.exists()) {
Logger().error('component.xml文件不存在');
return;
}
try {
final content = await componentFile.readAsString();
final document = XmlDocument.parse(content);
// items节点
for (final item in document.findAllElements('items')) {
// name属性是否匹配fieldName
if (item.getAttribute('name') == fieldName) {
//
final parentElement = item.parent;
if (parentElement != null) {
// name和chineseName属性
final parentName = parentElement.getAttribute('name');
final parentChineseName = parentElement.getAttribute('chineseName');
if (parentName != null && parentChineseName != null) {
//
parentNode.children.add(
OutlineNode(
name: parentName,
title: parentChineseName,
value: 'Component',
frequency: 0,
isDirectory: false,
depth: 4,
),
);
}
}
}
}
Logger().info('$fieldName 找到 ${parentNode.children.length} 个匹配项');
} catch (e) {
Logger().error('加载Component失败: $e');
rethrow;
}
}
//
static Future<void> _loadAtom(String rootPath, String? fieldName, OutlineNode parentNode) async {
if (fieldName == null || fieldName.isEmpty) return;
final uftatomDir = Directory('$rootPath\\uftatom');
if (!await uftatomDir.exists()) {
Logger().error('uftatom目录不存在');
return;
}
try {
// .uftstructure文件
final uftatomFiles =
await uftatomDir
.list(recursive: true)
.where(
(entity) =>
entity.path.endsWith('.uftatomfunction') ||
entity.path.endsWith('.uftatomservice'),
)
.cast<File>()
.toList();
for (final file in uftatomFiles) {
try {
final content = await file.readAsString();
final document = XmlDocument.parse(content);
String matchType = "I";
//
final List<XmlElement> matchingProperties =
document
.findAllElements('inputParameters')
.where((element) => element.getAttribute('id') == fieldName)
.toList();
if (matchingProperties.isEmpty) {
matchingProperties.addAll(
document
.findAllElements('outputParameters')
.where((element) => element.getAttribute('id') == fieldName),
);
matchType = "O";
}
if (matchingProperties.isEmpty) {
matchingProperties.addAll(
document
.findAllElements('internalParams')
.where((element) => element.getAttribute('id') == fieldName),
);
matchType = "X";
}
if (matchingProperties.isNotEmpty) {
// structure:Structure节点的chineseName
final businessNode = document.findAllElements('business:Function').firstOrNull;
final chineseName = businessNode?.getAttribute('chineseName') ?? '未命名';
//
final fileName = file.path;
//
parentNode.children.add(
OutlineNode(
name: fileName,
title: '$chineseName($matchType)',
value: 'Atom',
frequency: 0,
isDirectory: false, //
depth: 4,
),
);
}
} catch (e) {
Logger().error('解析文件 ${file.path} 失败: $e');
}
}
Logger().info('$fieldName 找到 ${parentNode.children.length} 个匹配项');
} catch (e) {
Logger().error('加载UFT对象失败: $e');
rethrow;
}
}
//
static Future<void> _loadBusiness(
String rootPath,
String? fieldName,
OutlineNode parentNode,
) async {
if (fieldName == null || fieldName.isEmpty) return;
final uftbusinessDir = Directory('$rootPath\\uftbusiness');
if (!await uftbusinessDir.exists()) {
Logger().error('uftbusiness目录不存在');
return;
}
try {
// .uftstructure文件
final uftbusinessFiles =
await uftbusinessDir
.list(recursive: true)
.where((entity) => entity.path.endsWith('.uftfunction'))
.cast<File>()
.toList();
for (final file in uftbusinessFiles) {
try {
final content = await file.readAsString();
final document = XmlDocument.parse(content);
String matchType = "I";
//
final List<XmlElement> matchingProperties =
document
.findAllElements('inputParameters')
.where((element) => element.getAttribute('id') == fieldName)
.toList();
if (matchingProperties.isEmpty) {
matchingProperties.addAll(
document
.findAllElements('outputParameters')
.where((element) => element.getAttribute('id') == fieldName),
);
matchType = "O";
}
if (matchingProperties.isEmpty) {
matchingProperties.addAll(
document
.findAllElements('internalParams')
.where((element) => element.getAttribute('id') == fieldName),
);
matchType = "X";
}
if (matchingProperties.isNotEmpty) {
// structure:Structure节点的chineseName
final businessNode = document.findAllElements('business:Function').firstOrNull;
final chineseName = businessNode?.getAttribute('chineseName') ?? '未命名';
//
final fileName = file.path;
//
parentNode.children.add(
OutlineNode(
name: fileName,
title: '$chineseName($matchType)',
value: 'Business',
frequency: 0,
isDirectory: false, //
depth: 4,
),
);
}
} catch (e) {
Logger().error('解析文件 ${file.path} 失败: $e');
}
}
Logger().info('$fieldName 找到 ${parentNode.children.length} 个匹配项');
} catch (e) {
Logger().error('加载UFT对象失败: $e');
rethrow;
}
}
// UFT对象
static Future<void> _loadUftObject(
String rootPath,
@ -206,8 +435,8 @@ class OutlineService { @@ -206,8 +435,8 @@ class OutlineService {
final structureNode = document.findAllElements('structure:Structure').firstOrNull;
final chineseName = structureNode?.getAttribute('chineseName') ?? '未命名';
//
final fileName = file.path.split('/').last.replaceFirst('.uftstructure', '');
//
final fileName = file.path;
//
parentNode.children.add(
@ -217,7 +446,7 @@ class OutlineService { @@ -217,7 +446,7 @@ class OutlineService {
value: 'UFTTable',
frequency: 0,
isDirectory: false, //
depth: parentNode.depth + 1,
depth: 4,
),
);
}
@ -274,7 +503,7 @@ class OutlineService { @@ -274,7 +503,7 @@ class OutlineService {
title: entry.value,
frequency: 0,
isDirectory: true,
depth: parentNode.depth + 1,
depth: 3,
),
),
);
@ -306,6 +535,7 @@ class OutlineService { @@ -306,6 +535,7 @@ class OutlineService {
frequency: entry.value,
isDirectory: true,
depth: 1,
isRoot: true,
wordClass: _wordClassDict[entry.key],
),
)

90
win_text_editor/lib/modules/outline/widgets/outline_explorer.dart

@ -2,13 +2,14 @@ import 'dart:math'; @@ -2,13 +2,14 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:win_text_editor/framework/controllers/file_provider.dart';
import 'package:win_text_editor/framework/services/file_path_manager.dart';
import 'package:win_text_editor/modules/outline/controllers/outline_provider.dart';
import 'package:win_text_editor/modules/outline/models/outline_node.dart';
import 'package:win_text_editor/shared/components/tree_view.dart';
class OutlineExplorer extends StatefulWidget {
final Function(String)? onFileDoubleTap;
final Function(String, dynamic)? onFileDoubleTap;
final Function(String)? onFolderDoubleTap;
const OutlineExplorer({super.key, this.onFileDoubleTap, this.onFolderDoubleTap});
@ -45,40 +46,49 @@ class _OutlineExplorerState extends State<OutlineExplorer> { @@ -45,40 +46,49 @@ class _OutlineExplorerState extends State<OutlineExplorer> {
return maxDepth;
}
//
// -
List<OutlineNode> _filterNodes(List<OutlineNode> nodes) {
if (_searchQuery.isEmpty) {
return nodes;
void resetVisibility(List<OutlineNode> nodes) {
for (final node in nodes) {
node.isVisible = true;
if (node.isDirectory) {
resetVisibility(node.children);
}
}
}
//
List<OutlineNode> recursiveFilter(List<OutlineNode> nodesToFilter, int currentDepth) {
final List<OutlineNode> result = [];
for (final node in nodesToFilter) {
// (01)
if (node.title.toLowerCase().contains(_searchQuery.toLowerCase())) {
result.add(node);
void applySearchFilter(List<OutlineNode> nodes, String query) {
for (final node in nodes) {
//
if (node.isDirectory && node.depth < 2) {
applySearchFilter(node.children, query);
}
//
if (node.isDirectory && node.children.isNotEmpty) {
result.addAll(recursiveFilter(node.children, currentDepth + 1));
}
//
final bool isMatch = node.title.toLowerCase().contains(query.toLowerCase());
final bool hasVisibleChild =
node.depth < 2 && node.isDirectory && node.children.any((child) => child.isVisible);
node.isVisible = isMatch || hasVisibleChild;
}
}
return result;
if (_searchQuery.isEmpty) {
resetVisibility(nodes);
return nodes;
}
// 0
return recursiveFilter(nodes, 0);
applySearchFilter(nodes, _searchQuery);
return nodes;
}
@override
Widget build(BuildContext context) {
final outlineProvider = Provider.of<OutlineProvider>(context);
_loadLastOpenedFolder(outlineProvider);
final fileProvider = Provider.of<FileProvider>(context); //
// fileProvider rootPath rebuild
_loadLastOpenedFolder(outlineProvider, fileProvider);
return Column(
children: [
@ -91,7 +101,7 @@ class _OutlineExplorerState extends State<OutlineExplorer> { @@ -91,7 +101,7 @@ class _OutlineExplorerState extends State<OutlineExplorer> {
controller: _searchController,
style: const TextStyle(fontSize: 12), // 10
decoration: InputDecoration(
hintText: '搜索节点...',
hintText: '搜索Tag...',
hintStyle: const TextStyle(fontSize: 12), // 10
prefixIcon: const Icon(
Icons.search,
@ -148,7 +158,8 @@ class _OutlineExplorerState extends State<OutlineExplorer> { @@ -148,7 +158,8 @@ class _OutlineExplorerState extends State<OutlineExplorer> {
nodes: _filterNodes(outlineProvider.outlineNode),
config: const TreeViewConfig(showIcons: true, lazyLoad: true),
onNodeTap: (node) => _handleNodeTap(context, node as OutlineNode),
onNodeDoubleTap: (node) => _handleNodeDoubleTap(node as OutlineNode),
onNodeDoubleTap:
(node) => _handleNodeDoubleTap(node as OutlineNode, fileProvider),
),
),
),
@ -184,13 +195,42 @@ class _OutlineExplorerState extends State<OutlineExplorer> { @@ -184,13 +195,42 @@ class _OutlineExplorerState extends State<OutlineExplorer> {
}
}
void _handleNodeDoubleTap(TreeNode node) {}
void _handleNodeDoubleTap(TreeNode node, FileProvider fileProvider) {
final outlineNode = node as OutlineNode;
if (outlineNode.depth < 3) {
outlineNode.isExpanded = !outlineNode.isExpanded;
return;
}
switch (outlineNode.value) {
case "UFTTable":
case "Business":
case "Atom":
if (widget.onFileDoubleTap != null) {
widget.onFileDoubleTap!(outlineNode.name, null);
}
break;
case "Component":
if (widget.onFileDoubleTap != null && fileProvider.rootPath != null) {
widget.onFileDoubleTap!(
'${fileProvider.rootPath}\\metadata\\component.xml',
outlineNode.name,
);
}
break;
}
}
Future<void> _loadLastOpenedFolder(OutlineProvider outlineProvider) async {
if (outlineProvider.rootPath != null && outlineProvider.rootPath!.isNotEmpty) {
Future<void> _loadLastOpenedFolder(
OutlineProvider outlineProvider,
FileProvider? fileProvider,
) async {
// 使 fileProvider
if (fileProvider?.rootPath != null && fileProvider!.rootPath!.isNotEmpty) {
await outlineProvider.setRootPath(fileProvider.rootPath!);
return;
}
//
final String? lastOpenedFolder = await FilePathManager.getLastOpenedFolder();
if (lastOpenedFolder != null) {
await outlineProvider.setRootPath(lastOpenedFolder);

73
win_text_editor/lib/modules/outline/widgets/outline_view.dart

@ -1,73 +0,0 @@ @@ -1,73 +0,0 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:win_text_editor/framework/controllers/tab_items_controller.dart';
import 'package:win_text_editor/modules/outline/controllers/outline_controller.dart';
import 'package:win_text_editor/modules/outline/controllers/outline_provider.dart'; //
import 'package:win_text_editor/modules/outline/widgets/outline_explorer.dart';
class OutlineView extends StatefulWidget {
final String tabId;
const OutlineView({super.key, required this.tabId});
@override
State<OutlineView> createState() => _OutlineViewState();
}
class _OutlineViewState extends State<OutlineView> {
late final OutlineController _controller;
late final OutlineProvider _outlineProvider; // OutlineProvider实例
bool _isControllerFromTabManager = false;
get tabManager => Provider.of<TabItemsController>(context, listen: false);
@override
void initState() {
super.initState();
_outlineProvider = OutlineProvider(); // OutlineProvider
final controllerFromManager = tabManager.getController(widget.tabId);
if (controllerFromManager != null) {
_controller = controllerFromManager;
_isControllerFromTabManager = true;
} else {
_controller = OutlineController();
_isControllerFromTabManager = false;
tabManager.registerController(widget.tabId, _controller);
}
}
@override
void dispose() {
if (!_isControllerFromTabManager) {
_controller.dispose();
}
_outlineProvider.dispose(); // provider
super.dispose();
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<OutlineProvider>.value(
value: _outlineProvider, // OutlineProvider
child: Row(
children: [
const VerticalDivider(width: 1),
SizedBox(
width: 300,
child: OutlineExplorer(
onFileDoubleTap: (path) {
//
},
onFolderDoubleTap: (path) {
//
},
),
),
const VerticalDivider(width: 1),
const Expanded(child: Center(child: Text('大纲'))),
],
),
);
}
}

2
win_text_editor/lib/modules/template_parser/controllers/template_parser_controller.dart

@ -369,7 +369,7 @@ class TemplateParserController extends BaseContentController { @@ -369,7 +369,7 @@ class TemplateParserController extends BaseContentController {
//-------------
@override
void onOpenFile(String filePath) {
void onOpenFile(String filePath, {dynamic appendArg}) {
setFilePath(filePath);
}

15
win_text_editor/lib/modules/uft_component/controllers/uft_component_controller.dart

@ -61,10 +61,13 @@ class UftComponentController extends BaseContentController { @@ -61,10 +61,13 @@ class UftComponentController extends BaseContentController {
}
@override
Future<void> onOpenFile(String filePath) async {
Logger().info("Opening file: $filePath");
Future<void> onOpenFile(String filePath, {dynamic appendArg}) async {
Logger().info("UftComponentController Opening file: $filePath");
try {
final components = await UftComponentService.parseComponentFile(filePath);
final components = await UftComponentService.parseComponentFile(
filePath,
filterName: appendArg,
);
// Update data sources
(componentsSource as ComponentSource).updateData(components);
@ -72,12 +75,16 @@ class UftComponentController extends BaseContentController { @@ -72,12 +75,16 @@ class UftComponentController extends BaseContentController {
// Clear any previous error
_errorMessage = null;
if (appendArg != null) {
updateComponentSelection(0, true);
}
// Notify UI to update
notifyListeners();
} catch (e) {
_errorMessage = e.toString();
notifyListeners();
Logger().error("Error opening file: $e");
Logger().error("Error UftComponentController opening file: $e");
}
}

15
win_text_editor/lib/modules/uft_component/services/uft_component_service.dart

@ -24,9 +24,12 @@ class UftComponentService { @@ -24,9 +24,12 @@ class UftComponentService {
).then((l) => l.firstWhereOrNull((c) => c.name == componentName));
}
static Future<List<UftComponent>> parseComponentFile(String filePath) async {
static Future<List<UftComponent>> parseComponentFile(
String filePath, {
String? filterName,
}) async {
try {
if (_components.isNotEmpty) {
if (_components.isNotEmpty && filterName == null) {
_components.firstWhereOrNull((c) => c.isSelected)?.isSelected = false;
return _components;
}
@ -40,12 +43,14 @@ class UftComponentService { @@ -40,12 +43,14 @@ class UftComponentService {
throw const FormatException("没有找到标准组件文件:component.xml");
}
_components.clear();
// 2. metadata stdfield.stfield
await StdFieldsCache.loadForFile(filePath);
// 3. Read and parse structure file content
final content = await file.readAsString();
_logger.info('加载标准组件');
_logger.info('加载标准组件,过滤词:$filterName');
final document = xml.XmlDocument.parse(content);
final componentNodes = document.findAllElements('items');
@ -62,6 +67,10 @@ class UftComponentService { @@ -62,6 +67,10 @@ class UftComponentService {
final name = node.getAttribute('name') ?? '';
final chineseName = node.getAttribute('chineseName') ?? '';
if (filterName != null && filterName.isNotEmpty && filterName != name) {
continue; //
}
final fields = <Field>[];
List<Index>? indexes = [];
int index = 1;

2
win_text_editor/lib/modules/xml_search/controllers/xml_search_controller.dart

@ -114,7 +114,7 @@ class XmlSearchController extends BaseContentController { @@ -114,7 +114,7 @@ class XmlSearchController extends BaseContentController {
}
@override
void onOpenFile(String filePath) {
void onOpenFile(String filePath, {dynamic appendArg}) {
searchDirectory = filePath;
}

2
win_text_editor/lib/shared/base/base_content_controller.dart

@ -3,5 +3,5 @@ import 'package:flutter/material.dart'; @@ -3,5 +3,5 @@ import 'package:flutter/material.dart';
abstract class BaseContentController with ChangeNotifier {
void onOpenFolder(String folderPath);
void onOpenFile(String filePath);
void onOpenFile(String filePath, {dynamic appendArg});
}

6
win_text_editor/lib/shared/components/tree_view.dart

@ -10,6 +10,7 @@ abstract class TreeNode { @@ -10,6 +10,7 @@ abstract class TreeNode {
List<TreeNode> get children;
int get depth;
IconData? get iconData;
bool get isVisible;
}
///
@ -141,6 +142,7 @@ class _TreeViewState extends State<TreeView> { @@ -141,6 +142,7 @@ class _TreeViewState extends State<TreeView> {
int _countVisibleNodes(List<TreeNode> nodes) {
int count = 0;
for (final node in nodes) {
if (!node.isVisible) continue; //
count++;
if (node.isDirectory && node.isExpanded) {
count += _countVisibleNodes(node.children);
@ -152,8 +154,11 @@ class _TreeViewState extends State<TreeView> { @@ -152,8 +154,11 @@ class _TreeViewState extends State<TreeView> {
TreeNode _getVisibleNode(List<TreeNode> nodes, int index) {
int current = 0;
for (final node in nodes) {
if (!node.isVisible) continue; //
if (current == index) return node;
current++;
if (node.isDirectory && node.isExpanded) {
final childCount = _countVisibleNodes(node.children);
if (index - current < childCount) {
@ -187,6 +192,7 @@ class TreeNodeWidget extends StatelessWidget { @@ -187,6 +192,7 @@ class TreeNodeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (!node.isVisible) return const SizedBox.shrink(); //
return InkWell(
onTap: onTap,
onDoubleTap: onDoubleTap,

3
win_text_editor/lib/shared/models/template_node.dart

@ -21,6 +21,9 @@ class TemplateNode implements TreeNode { @@ -21,6 +21,9 @@ class TemplateNode implements TreeNode {
@override
String get title => name;
@override
bool get isVisible => true;
TemplateNode({
required this.name,
required this.children,

Loading…
Cancel
Save