measure_tool.dart 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. import 'package:fis_common/logger/logger.dart';
  2. import 'package:fis_i18n/i18n.dart';
  3. import 'package:fis_measure/interfaces/process/items/item.dart';
  4. import 'package:fis_measure/interfaces/process/items/item_metas.dart';
  5. import 'package:fis_measure/interfaces/process/items/terms.dart';
  6. import 'package:fis_measure/interfaces/process/items/types.dart';
  7. import 'package:fis_measure/interfaces/process/player/play_controller.dart';
  8. import 'package:fis_measure/interfaces/process/workspace/application.dart';
  9. import 'package:fis_measure/process/language/measure_language.dart';
  10. import 'package:fis_measure/process/workspace/measure_data_controller.dart';
  11. import 'package:fis_measure/process/workspace/measure_data_helper.dart';
  12. import 'package:fis_measure/process/workspace/measure_handler.dart';
  13. import 'package:fis_measure/view/measure/combo_widget.dart';
  14. import 'package:fis_measure/view/measure/measure_view_controller.dart';
  15. import 'package:fis_measure/view/player/controller.dart';
  16. import 'package:fis_ui/index.dart';
  17. import 'package:fis_ui/interface/interactive_container.dart';
  18. import 'package:flutter/material.dart';
  19. import 'package:get/get.dart';
  20. import 'package:vid/us/vid_us_image.dart';
  21. /// 测量项页面
  22. class LeftSiderSelectMeasure extends FStatefulWidget
  23. implements FInteractiveContainer {
  24. const LeftSiderSelectMeasure({Key? key}) : super(key: key);
  25. @override
  26. final String pageName = 'LeftSiderSelectMeasure';
  27. @override
  28. FState<LeftSiderSelectMeasure> createState() => LeftSiderSelectMeasureState();
  29. }
  30. class LeftSiderSelectMeasureState extends FState<LeftSiderSelectMeasure> {
  31. /// 数据
  32. IApplication get application => Get.find<IApplication>();
  33. VidPlayerController get playerController =>
  34. Get.find<IPlayerController>() as VidPlayerController;
  35. final measureHandler = Get.find<MeasureHandler>();
  36. final measureData = Get.find<MeasureDataController>();
  37. final measureMetaController = Get.find<MeasureMetaController>();
  38. /// 测量语言包
  39. final measureLanguage = MeasureLanguage();
  40. /// 当前选中的测量项下标
  41. int activeItemIndex = 0;
  42. /// 当前选中的子测量项下标
  43. int activeChildItemIndex = 0;
  44. /// 上一个测量项
  45. int lastActiveItemIndex = 0;
  46. /// Styles
  47. late String fontFamily;
  48. static const Color buttonBackgroundColor = Color.fromRGBO(70, 70, 70, 1);
  49. static const Color buttonBorderColor = Color.fromRGBO(124, 124, 124, 1);
  50. static const Color buttonBorderHighlight = Color.fromRGBO(124, 124, 124, 1);
  51. static const Color buttonTextColor = Colors.white;
  52. static const Color childButtonHighlight = Color.fromRGBO(84, 144, 249, 1);
  53. static const Color childContainerBackground = Color.fromRGBO(60, 60, 60, 1);
  54. static const double buttonBorderHighlightWidth = 1.0;
  55. /// 触发测量项选中事件
  56. void selectItem(int selectIndex) {
  57. if (selectIndex == activeItemIndex) return;
  58. final itemMeta = measureData.curItemMetaList[selectIndex];
  59. changeItem(itemMeta);
  60. // setState(() {
  61. // activeItemIndex = selectIndex;
  62. // activeChildItemIndex = 0;
  63. // });
  64. }
  65. /// 触发子测量项选中事件
  66. void selectChildItem(int selectIndex) {
  67. if (selectIndex == activeChildItemIndex) return;
  68. final item = application.activeMeasureItem!;
  69. if (item is ITopMeasureItem) {
  70. item.switchChild(selectIndex);
  71. activeChildItemIndex = selectIndex;
  72. }
  73. setState(() {});
  74. }
  75. /// 切换测量项
  76. void changeItem(ItemMeta itemMeta) {
  77. int itemIndex =
  78. measureData.curItemMetaList.indexWhere((e) => e.name == itemMeta.name);
  79. int childItemIndex = activeChildItemIndex;
  80. try {
  81. // IMeasureItem? item = application.activeMeasureItem;
  82. // if (item != null &&
  83. // item.meta.name == itemMeta.name &&
  84. // item is ITopMeasureItem &&
  85. // item.isCrossFrameMode) {
  86. // // 跨帧测量,测量项无变化时,自动切到下一个子项
  87. // childItemIndex = item.workingChildIndex + 1;
  88. // if (childItemIndex < item.childItems.length) {
  89. // item.switchChild(childItemIndex);
  90. // item.workingChildChanged.addListener(_onWorkingChildChanged);
  91. // if (mounted) {
  92. // setState(() {
  93. // activeChildItemIndex = childItemIndex;
  94. // });
  95. // }
  96. // return;
  97. // }
  98. // }
  99. application.switchItem(itemMeta);
  100. final item = application.activeMeasureItem!;
  101. if (item is ITopMeasureItem) {
  102. item.switchChild(0);
  103. item.workingChildChanged.addListener(_onWorkingChildChanged);
  104. }
  105. } catch (e) {
  106. logger.e("changeItem failed", e);
  107. }
  108. if (mounted) {
  109. setState(() {
  110. activeItemIndex = itemIndex;
  111. activeChildItemIndex = childItemIndex;
  112. });
  113. }
  114. }
  115. /// 子测量项变化事件监听
  116. void _onWorkingChildChanged(sender, int e) {
  117. setState(() {
  118. activeChildItemIndex = e;
  119. });
  120. }
  121. void _onItemMetaListChanged(Object s, dynamic e) {
  122. try {
  123. if (e != null) {
  124. setState(() {});
  125. }
  126. } catch (e) {
  127. debugPrint("ItemMetaListChanged fail " + e.toString());
  128. }
  129. }
  130. /// 首帧加载完成事件监听: 获取当前图像的测量项,从第一帧取
  131. void _onFirstFrameLoaded(Object sender, VidUsImage e) async {
  132. if (!mounted) return;
  133. _getMeasureItemsList(e);
  134. _getAnnotationList();
  135. }
  136. //初始化时尝试加载第一项测量项(如果有的话)
  137. void _loadFirstItem() {
  138. if (measureData.curItemMetaList.isNotEmpty) {
  139. if (_checkAndHostCrossFrameMeasureItem()) {
  140. return;
  141. }
  142. final itemMeta = measureData.curItemMetaList.first;
  143. changeItem(itemMeta);
  144. // setState(() {
  145. // activeItemIndex = 0;
  146. // activeChildItemIndex = 0;
  147. // });
  148. }
  149. }
  150. /// 注释获取
  151. void _getAnnotationList() async {
  152. List<String> annotationList = [];
  153. var measureCommentItemResult =
  154. await MeasureDataHelper.getCommentsByApplicationAsync(
  155. application.applicationName,
  156. application.categoryName,
  157. );
  158. measureData.measureCommentItemResult =
  159. measureCommentItemResult?.commentItems ?? [];
  160. measureCommentItemResult?.commentItems?.forEach((element) {
  161. annotationList.add(element.text ?? '');
  162. });
  163. measureData.annotationList = annotationList;
  164. measureHandler.onAnnotationsLoaded();
  165. }
  166. void _getMeasureItemsList(VidUsImage e) async {
  167. List<String> getModes = [];
  168. for (var element in e.visuals[0].modes) {
  169. getModes.add(element.type.toString().split('.')[1]);
  170. }
  171. measureData.applicationModes = e.visuals[0].modes;
  172. measureData.currentMode = e.visuals[0].modes.first.type.name;
  173. var measureModeSelection = MeasureModeSelection(
  174. application.applicationName,
  175. application.categoryName,
  176. application.isThirdPart ? ['TPPTissue'] : getModes,
  177. );
  178. measureHandler.measureModeChanged = measureModeSelection;
  179. var measureApplicationDTO =
  180. await MeasureDataHelper.getMeasureApplication(measureModeSelection);
  181. if (measureApplicationDTO != null) {
  182. measureData.measureApplicationVersion =
  183. measureApplicationDTO.version ?? '';
  184. measureData.availableModes = measureApplicationDTO.availableModes ?? [];
  185. if (application.isThirdPart) {
  186. measureData.currentMode = 'TPPTissue';
  187. }
  188. measureMetaController.setAvailableModes(measureData.currentMode);
  189. setState(() {});
  190. }
  191. }
  192. /// 测量项列表变化事件监听
  193. void _onCurItemMetaListChanged(sender, e) {
  194. if (measureData.curItemMetaList.isNotEmpty) {
  195. if (_checkAndHostCrossFrameMeasureItem()) {
  196. return;
  197. }
  198. changeItem(measureData.curItemMetaList[0]);
  199. }
  200. // setState(() {
  201. // activeItemIndex = 0;
  202. // activeChildItemIndex = 0;
  203. // });
  204. }
  205. bool _checkAndHostCrossFrameMeasureItem() {
  206. final crossCxt = application.crossFrameContext;
  207. if (crossCxt != null && !crossCxt.isOver) {
  208. final activeItem = application.activeMeasureItem;
  209. if (activeItem != null &&
  210. activeItem is ITopMeasureItem &&
  211. activeItem.isCrossFrameMode) {
  212. // 跨帧时&当前是跨帧测量项,则保持此测量项
  213. changeItem(activeItem.meta);
  214. return true;
  215. }
  216. }
  217. return false;
  218. }
  219. /// 是否显示测量项翻译事件变化
  220. void _onShowItemTransStateChanged(_, e) {
  221. setState(() {});
  222. }
  223. /// 鼠标右键结束测量事件【即切换到空测量项】,此时缓存被结束的测量项,如果再次收到右击事件,恢复被结束的测量项
  224. void _onRightClickFinishMeasure(_, e) {
  225. if (activeItemIndex == -1) {
  226. changeItem(measureData.curItemMetaList[lastActiveItemIndex]);
  227. setState(() {
  228. activeItemIndex = lastActiveItemIndex;
  229. activeChildItemIndex = 0;
  230. });
  231. } else {
  232. lastActiveItemIndex = activeItemIndex;
  233. final emptyItem = ItemMeta(
  234. "EmptyItem",
  235. measureType: MeasureTypes.Empty,
  236. description: "",
  237. outputs: [],
  238. );
  239. changeItem(emptyItem);
  240. setState(() {
  241. activeItemIndex = -1;
  242. activeChildItemIndex = 0;
  243. });
  244. }
  245. }
  246. @override
  247. void initState() {
  248. measureData.curItemMetaListChanged.addListener(_onCurItemMetaListChanged);
  249. measureData.itemMetaListChanged.addListener(_onItemMetaListChanged);
  250. measureData.showItemTransStateChanged
  251. .addListener(_onShowItemTransStateChanged);
  252. measureHandler.onRightClickFinishMeasure
  253. .addListener(_onRightClickFinishMeasure);
  254. WidgetsBinding.instance.addPostFrameCallback((call) {
  255. _loadFirstItem();
  256. playerController.firstFrameLoaded.addListener(_onFirstFrameLoaded);
  257. });
  258. super.initState();
  259. }
  260. @override
  261. dispose() {
  262. super.dispose();
  263. playerController.firstFrameLoaded.removeListener(_onFirstFrameLoaded);
  264. measureData.curItemMetaListChanged
  265. .removeListener(_onCurItemMetaListChanged);
  266. measureData.itemMetaListChanged.removeListener(_onItemMetaListChanged);
  267. measureData.showItemTransStateChanged
  268. .removeListener(_onShowItemTransStateChanged);
  269. measureHandler.onRightClickFinishMeasure
  270. .removeListener(_onRightClickFinishMeasure);
  271. final item = application.activeMeasureItem;
  272. if (item != null && item is ITopMeasureItem) {
  273. item.workingChildChanged.removeListener(_onWorkingChildChanged);
  274. }
  275. }
  276. @override
  277. FWidget build(BuildContext context) {
  278. fontFamily = Theme.of(context).textTheme.labelLarge!.fontFamily!;
  279. return buildItemsListView(measureData.curItemMetaList);
  280. }
  281. /// 构建测量项列表
  282. FWidget buildItemsListView(List<ItemMeta> itemMetaList) {
  283. return FListView(
  284. shrinkWrap: true,
  285. controller: ScrollController(),
  286. children: List.generate(itemMetaList.length, (i) {
  287. return _buildItemContainer(i);
  288. }));
  289. }
  290. /// 构建测量项容器
  291. FWidget _buildItemContainer(int itemMetaIndex) {
  292. final itemMeta = measureData.curItemMetaList[itemMetaIndex];
  293. final isActived = itemMetaIndex == activeItemIndex; // 是否选中
  294. final hasChildItem = itemMeta.childItems.isNotEmpty; //是否为组合测量项
  295. return FContainer(
  296. decoration: BoxDecoration(
  297. borderRadius: BorderRadius.circular(5),
  298. border: isActived && hasChildItem
  299. ? Border.all(
  300. color: buttonBorderHighlight, width: buttonBorderHighlightWidth)
  301. : null,
  302. ),
  303. margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5),
  304. child: FColumn(children: [
  305. if (isActived && hasChildItem) ...[
  306. _buildItemTitle(itemMetaIndex),
  307. _buildChildItemContainer(itemMeta),
  308. ] else
  309. FContainer(
  310. width: double.infinity,
  311. child: _buildItemButton(itemMetaIndex),
  312. ),
  313. ]),
  314. );
  315. }
  316. // 构建测量项按钮
  317. FWidget _buildItemButton(int itemIndex) {
  318. final itemMeta = measureData.curItemMetaList[itemIndex];
  319. final transValue = measureLanguage.t('measure', itemMeta.description);
  320. // 非英文环境下显示中英文,否则只显示英文
  321. final ifShowCN = !i18nBook.isCurrentEnglish &&
  322. transValue != itemMeta.description &&
  323. measureData.measureSystemSetting.showAnnotation;
  324. final isActived = itemIndex == activeItemIndex;
  325. return FElevatedButton(
  326. name: itemMeta.name,
  327. businessParent: widget,
  328. onPressed: () => selectItem(itemIndex),
  329. child: FColumn(
  330. mainAxisAlignment: MainAxisAlignment.center,
  331. children: [
  332. if (ifShowCN)
  333. FText(
  334. transValue,
  335. maxLines: 1,
  336. overflow: TextOverflow.ellipsis,
  337. style: TextStyle(
  338. color: isActived ? null : buttonTextColor,
  339. fontFamily: fontFamily,
  340. fontSize: 12,
  341. ),
  342. ),
  343. FText(
  344. itemMeta.description,
  345. style: TextStyle(
  346. color: isActived ? null : buttonTextColor,
  347. ),
  348. ),
  349. ],
  350. ),
  351. style: ElevatedButton.styleFrom(
  352. backgroundColor: isActived ? null : buttonBackgroundColor,
  353. fixedSize: const Size.fromHeight(
  354. 50,
  355. ),
  356. side: BorderSide(
  357. color: isActived ? Colors.transparent : buttonBorderColor,
  358. ),
  359. ),
  360. );
  361. }
  362. // 构建测量项标签【用于组合测量项顶部】
  363. FWidget _buildItemTitle(int itemIndex) {
  364. final itemMeta = measureData.curItemMetaList[itemIndex];
  365. final transValue = measureLanguage.t('measure', itemMeta.description);
  366. // 非英文环境下显示中英文,否则只显示英文
  367. final ifShowCN = !i18nBook.isCurrentEnglish &&
  368. transValue != itemMeta.description &&
  369. measureData.measureSystemSetting.showAnnotation;
  370. return FContainer(
  371. decoration: const BoxDecoration(
  372. border: Border(
  373. bottom: BorderSide(
  374. color: buttonBorderHighlight, width: buttonBorderHighlightWidth),
  375. ),
  376. ),
  377. child: FSizedBox(
  378. height: 50,
  379. width: double.infinity,
  380. child: FColumn(
  381. mainAxisAlignment: MainAxisAlignment.center,
  382. children: [
  383. if (ifShowCN)
  384. FText(
  385. transValue,
  386. maxLines: 1,
  387. overflow: TextOverflow.ellipsis,
  388. style: TextStyle(
  389. color: buttonTextColor,
  390. fontFamily: fontFamily,
  391. fontSize: 12,
  392. ),
  393. ),
  394. FText(
  395. itemMeta.description,
  396. style: const TextStyle(
  397. color: buttonTextColor,
  398. ),
  399. ),
  400. ],
  401. ),
  402. ),
  403. );
  404. }
  405. /// 子测量项容器
  406. FWidget _buildChildItemContainer(ItemMeta itemMeta) {
  407. final activeName = itemMeta.description;
  408. final isSpecial = MeasurespecialsupportedTerms.items.contains(activeName);
  409. if (isSpecial) {
  410. //是否为特殊组合测量项
  411. return const SpecialItemHR();
  412. } else {
  413. return FContainer(
  414. decoration: const BoxDecoration(
  415. color: childContainerBackground,
  416. borderRadius: BorderRadius.only(
  417. bottomLeft: Radius.circular(5), bottomRight: Radius.circular(5)),
  418. ),
  419. padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 5),
  420. child: FColumn(
  421. children: (itemMeta.childItems)
  422. .asMap()
  423. .entries
  424. .map(
  425. (e) => _buildChildItemButton(
  426. e.value,
  427. e.key,
  428. ),
  429. )
  430. .toList(),
  431. ),
  432. );
  433. }
  434. }
  435. // 构建子测量项按钮
  436. FWidget _buildChildItemButton(ItemMeta itemMeta, int childItemIndex) {
  437. final isActived = childItemIndex == activeChildItemIndex;
  438. return FContainer(
  439. width: double.infinity,
  440. margin: const EdgeInsetsDirectional.all(5),
  441. child: FElevatedButton(
  442. businessParent: widget,
  443. name: "selectChildItemIdnex:$childItemIndex",
  444. onPressed: () {
  445. selectChildItem(childItemIndex);
  446. },
  447. child: FText(
  448. itemMeta.description,
  449. ),
  450. style: ElevatedButton.styleFrom(
  451. backgroundColor: isActived ? null : buttonBackgroundColor,
  452. side: BorderSide(
  453. color: isActived ? Colors.transparent : buttonBorderColor,
  454. ),
  455. ),
  456. ));
  457. }
  458. }