1/**
2 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15import hilog from '@ohos.hilog';
16import { Theme } from '@ohos.arkui.theme';
17import { LengthMetrics } from '@ohos.arkui.node';
18import { common } from '@kit.AbilityKit';
19import resourceManager from '@ohos.resourceManager';
20
21const IMAGE_NODE_HEIGHT: number = 24;
22const IMAGE_NODE_WIDTH: number = 24;
23const ITEM_WIDTH: number = 0;
24const ITEM_HEIGHT: number = 48;
25const ITEM_HEIGHT_INPUT: number = 32;
26const BORDER_WIDTH_HAS: number = 2;
27const BORDER_WIDTH_NONE: number = 0;
28const NODE_HEIGHT: number = 48;
29const LIST_ITEM_HEIGHT_NONE: number = 0;
30const LIST_ITEM_HEIGHT: number = 48;
31const SHADOW_OFFSETY: number = 10;
32const FLAG_NUMBER: number = 2;
33const DRAG_OPACITY: number = 0.4;
34const DRAG_OPACITY_NONE: number = 1;
35const MIN_FONT_SCALE: number = 1;
36const MAX_FONT_SCALE: number = 2;
37const FLAG_LINE_HEIGHT: string = '1.0vp';
38const X_OFF_SET: string = '0vp';
39const Y_OFF_SET: string = '2.75vp';
40const Y_BOTTOM_OFF_SET: string = '-1.25vp';
41const Y_BASE_PLATE_OFF_SET: string = '1.5vp';
42const COLOR_IMAGE_EDIT: string = '#FFFFFF';
43const COLOR_IMAGE_ROW: string = '#00000000';
44const COLOR_SELECT: string = '#1A0A59F7';
45const SHADOW_COLOR: string = '#00001E';
46const GRAG_POP_UP_HEIGHT: string = '48';
47const FLOOR_MIN_WIDTH: string = '128vp';
48const FLOOR_MAX_WIDTH: string = '208vp';
49const TEXT_MIN_WIDTH: string = '80vp';
50const TEXT_MAX_WIDTH: string = '160vp';
51const MIN_WIDTH: string = '112vp';
52const MAX_WIDTH: string = '192vp';
53const TRANS_COLOR: string = '#00FFFFFF';
54const DELAY_TIME: number = 100;
55const LEVEL_MARGIN: number = 12;
56const MARGIN_OFFSET: number = 8;
57const TAG: string = 'TreeView';
58const LOG_CODE: number = 0x3900;
59
60const ARROW_DOWN: Resource | string = $r('sys.media.ohos_ic_public_arrow_down');
61
62const ARROW_DOWN_WITHE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAIGNIUk0AAHomAAC' +
63  'AhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAEZ0FNQQAAsY58+1GTAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAl' +
64  'wSFlzAAAOxAAADsQBlSsOGwAAAKVJREFUeNpjYBgFo2AU0Bww4pL4////diC1hZGRcSo+A4DqWIDUZCB+AVTbiC7PhEfvByCeAjQgn4Dhy4E' +
65  '4BYgvYFODz4JYIF4DxBOwWYJkeAAQRwBdvxGbIcy4TG9sbPzX0NCwHsjUAuIiIPsDUOwkDsPXkhwHWFwaAsQlQAwyrJsYw4myAIslIPCHGMP' +
66  'xBhGO4PoGxF+AOA9o+NbRTDgKRgFxAAAzj0Grm3RjyAAAAABJRU5ErkJggg=='
67
68const ARROW_RIGHT: Resource | string = $r('sys.media.ohos_ic_public_arrow_right');
69
70const ARROW_RIGHT_WITHE = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAIGNIUk0AAHomAA' +
71  'CAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAEZ0FNQQAAsY58+1GTAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAA' +
72  'lwSFlzAAAOxAAADsQBlSsOGwAAAKFJREFUeNpjYBgFowAE/v//bwHEPOToZSJS3XIg3k6OJcRaUALEFuRYwkyMosbGxusNDQ3XgMwCIHYAsl' +
73  'cDxX5RzQJKLGEmxbvkWMJEaqQxMjKuBVI5QGwDxOnUimR08AFK81DdAmAqArl8DhDfAOKpVLUAavh2IH4CxI7A4HpDMEgpMPwFUXFGS8NJCa' +
74  'L55BgOAixEqqsB4oOkGj4KRggAAN4STB9zyhGzAAAAAElFTkSuQmCC'
75
76enum Event {
77  TOUCH_DOWN = 0,
78  TOUCH_UP = 1,
79  HOVER = 3,
80  HOVER_OVER = 4,
81  FOCUS = 5,
82  BLUR = 6,
83  MOUSE_BUTTON_RIGHT = 7,
84  DRAG = 8,
85}
86
87enum MenuOperation {
88  ADD_NODE = 0,
89  REMOVE_NODE = 1,
90  MODIFY_NODE = 2,
91  COMMIT_NODE = 3,
92}
93
94enum PopUpType {
95  HINTS = 0,
96  WARNINGS = 1,
97}
98
99enum InputError {
100  INVALID_ERROR = 0,
101  LENGTH_ERROR = 1,
102  NONE = 2,
103}
104
105enum Flag {
106  DOWN_FLAG = 0,
107  UP_FLAG = 1,
108  NONE = 2,
109}
110
111export enum NodeStatus {
112  EXPAND = 0,
113  COLLAPSE,
114}
115
116export enum InteractionStatus {
117  NORMAL = 0,
118  SELECTED,
119  EDIT,
120  FINISH_EDIT,
121  DRAG_INSERT,
122  FINISH_DRAG_INSERT,
123}
124
125enum CollapseImageType {
126  ARROW_DOWN = 0,
127  ARROW_RIGHT,
128  ARROW_DOWN_WHITE,
129  ARROW_RIGHT_WHITE,
130}
131
132interface ChildNodeInfo {
133  isHasChildNode: boolean;
134  childNum: number;
135  allChildNum: number;
136}
137
138interface NodeItemView {
139  imageNode?: ImageNode;
140  inputText: InputText;
141  mainTitleNode: MainTitleNode;
142  imageCollapse?: CollapseImageNode;
143  fontColor?: ResourceColor;
144}
145
146interface Status {
147  normal: ResourceColor;
148  hover: ResourceColor;
149  press: ResourceColor;
150  selected: ResourceColor;
151  highLight?: ResourceColor;
152}
153
154interface NodeBorder {
155  borderWidth: Resource | number;
156  borderColor: ResourceColor;
157  borderRadius: Resource;
158}
159
160interface PopUpInfo {
161  popUpIsShow: boolean;
162  popUpEnableArrow: boolean;
163  popUpColor?: ResourceColor;
164  popUpText?: string | Resource;
165  popUpTextColor?: ResourceColor;
166}
167
168interface BorderWidth {
169  has: Resource | number;
170  none: Resource | number;
171}
172
173interface TextSetting {
174  fontColor: ResourceColor;
175  fontSize: Resource;
176  fontWeight: FontWeight;
177}
178
179interface NodeInfoView {
180  itemId?: number;
181  itemIcon?: Resource | string;
182  itemTitle?: ResourceStr;
183  isFolder?: boolean;
184}
185
186interface FloorConstraintSize {
187  minWidth: string;
188  maxWidth: string;
189}
190
191interface TextConstraintSize {
192  minWidth1: string;
193  maxWidth1: string;
194  minWidth2: string;
195  maxWidth2: string;
196}
197
198interface Padding {
199  left: Resource;
200  right: Resource;
201}
202
203interface ItemPadding {
204  left: Resource;
205  right: Resource;
206  top: Resource;
207  bottom: Resource;
208}
209
210interface Shadow {
211  radius: Resource;
212  color: string;
213  offsetX?: number;
214  offsetY?: number;
215}
216
217interface DragPopup {
218  floorConstraintSize: FloorConstraintSize;
219  textConstraintSize: TextConstraintSize;
220  padding: Padding;
221  backgroundColor: ResourceColor;
222  height: string;
223  shadow: Shadow;
224  borderRadius: Resource;
225  fontColor: ResourceColor;
226  fontSize: Resource;
227  fontWeight: FontWeight;
228  imageOpacity: Resource;
229}
230
231interface DragNodeParam {
232  parentId: number,
233  currentId: number,
234  data: NodeParam,
235}
236
237interface FlagLine {
238  flagLineHeight: string;
239  flagLineColor: Resource;
240  xOffset: string;
241  yTopOffset: string;
242  yBottomOffset: string;
243  yBasePlateOffset: string;
244}
245
246interface SubTitleStyle {
247  normalFontColor: ResourceColor;
248  highLightFontColor: ResourceColor;
249  fontSize: Resource;
250  fontWeight: FontWeight;
251  margin: Padding;
252}
253
254interface NodeItemViewFactory {
255  createNode: () => NodeItemView;
256  createNodeByNodeParam: (nodeParam: NodeParam) => NodeItemView;
257}
258
259class TreeViewNodeItemFactory implements NodeItemViewFactory {
260  private static instance: TreeViewNodeItemFactory;
261
262  private constructor() {
263  }
264
265  /**
266   * TreeViewNodeItemFactory singleton function
267   *
268   * @returns TreeViewNodeItemFactory
269   */
270  public static getInstance(): TreeViewNodeItemFactory {
271    if (!TreeViewNodeItemFactory.instance) {
272      TreeViewNodeItemFactory.instance = new TreeViewNodeItemFactory();
273    }
274    return TreeViewNodeItemFactory.instance;
275  }
276
277  /**
278   * TreeViewNodeItemFactory create default node
279   *
280   * @returns NodeItemView
281   */
282  public createNode(): NodeItemView {
283    return {
284      imageNode: undefined,
285      inputText: new InputText(),
286      mainTitleNode: new MainTitleNode(''),
287      imageCollapse: undefined,
288      fontColor: undefined,
289    };
290  }
291
292  /**
293   * TreeViewNodeItemFactory create node by node parameter
294   *
295   * @param nodeParam node parameter
296   * @returns NodeItemView
297   */
298  public createNodeByNodeParam(nodeParam: NodeParam): NodeItemView {
299    let nodeItemView: NodeItemView = this.createNode();
300    if (nodeParam.icon) {
301      nodeItemView.imageNode = new ImageNode(
302        nodeParam.icon,
303        $r('sys.float.ohos_id_alpha_content_fourth'),
304        IMAGE_NODE_HEIGHT,
305        IMAGE_NODE_WIDTH,
306        nodeParam.selectedIcon,
307        nodeParam.editIcon,
308      );
309    }
310    if (nodeParam.primaryTitle) {
311      nodeItemView.mainTitleNode = new MainTitleNode(nodeParam.primaryTitle);
312    }
313    return nodeItemView;
314  }
315}
316
317let emptyNodeInfo: NodeParam = {
318  isFolder: true,
319  icon: '',
320  selectedIcon: '',
321  editIcon: '',
322  container: () => {
323  },
324  secondaryTitle: '',
325  primaryTitle: '',
326  parentNodeId: -1,
327  currentNodeId: -1,
328}
329
330class TreeViewTheme {
331  private static instance: TreeViewTheme;
332  public itemSelectedBgColor: ResourceColor = '#1A0A59F7';
333  public primaryTitleFontColor: ResourceColor = $r('sys.color.ohos_id_color_primary');
334  public secondaryTitleFontColor: ResourceColor = $r('sys.color.ohos_id_color_secondary');
335  public primaryTitleActiveFontColor: ResourceColor = $r('sys.color.ohos_id_color_text_primary_activated');
336  public itemPressedBgColor: ResourceColor = $r('sys.color.ohos_id_color_click_effect');
337  public itemHoverBgColor: ResourceColor = $r('sys.color.ohos_id_color_hover');
338  public borderFocusedColor: ResourceColor = $r('sys.color.ohos_id_color_focused_outline');
339  public leftIconColor: ResourceColor = $r('sys.color.icon_secondary');
340  public leftIconActiveColor: ResourceColor = $r('sys.color.icon_secondary');
341  public arrowIconColor: ResourceColor = $r('sys.color.icon_tertiary');
342
343  private constructor() {
344
345  }
346
347  /**
348   * TreeViewTheme singleton function
349   *
350   * @returns TreeViewNodeItemFactory
351   */
352  public static getInstance(): TreeViewTheme {
353    if (!TreeViewTheme.instance) {
354      TreeViewTheme.instance = new TreeViewTheme();
355    }
356    return TreeViewTheme.instance;
357  }
358}
359
360@Observed
361export class NodeInfo {
362  public imageSource: Resource | string | undefined = '';
363  private nodeHeight: Resource | number;
364  private nodeItemView: NodeItemView;
365  private nodeLeftPadding: number;
366  private nodeColor: ResourceColor;
367  private nodeIsShow: boolean;
368  private status: Status;
369  private nodeBorder: NodeBorder;
370  private popUpInfo: PopUpInfo;
371  private listItemHeight: number;
372  private isShowTitle: boolean;
373  private isShowInputText: boolean;
374  private isSelected: boolean;
375  public readonly borderWidth: BorderWidth =
376    { has: BORDER_WIDTH_HAS/* 2vp */, none: BORDER_WIDTH_NONE/* 0vp */ }
377  /* parameter of the drag event.*/
378  private nodeParam: NodeParam;
379  private node: NodeItem;
380  private canShowFlagLine: boolean = false;
381  private isOverBorder: boolean = false;
382  private canShowBottomFlagLine: boolean = false;
383  private isHighLight: boolean = false;
384  private flagLineLeftMargin: number;
385  private isModify: boolean = false;
386  private treeViewTheme: TreeViewTheme = TreeViewTheme.getInstance();
387  public fontColor: ResourceColor = '';
388
389  constructor(node: NodeItem, nodeParam: NodeParam) {
390    this.node = node;
391    this.nodeParam = nodeParam;
392    this.nodeItemView = TreeViewNodeItemFactory.getInstance().createNodeByNodeParam(nodeParam);
393    this.popUpInfo = {
394      popUpIsShow: false,
395      popUpEnableArrow: false,
396      popUpColor: undefined,
397      popUpText: '',
398      popUpTextColor: undefined,
399    };
400    this.nodeHeight = NODE_HEIGHT;
401    this.nodeLeftPadding = node.nodeLevel * LEVEL_MARGIN + MARGIN_OFFSET; // calculate left padding
402    this.nodeColor = $r('sys.color.ohos_id_color_background');
403    this.nodeIsShow = (this.node.nodeLevel > 0) ? false : true;
404    this.listItemHeight = (this.node.nodeLevel > 0) ? LIST_ITEM_HEIGHT_NONE : LIST_ITEM_HEIGHT;
405    this.isShowTitle = true;
406    this.isShowInputText = false;
407    this.isSelected = false;
408    this.status = {
409      normal: $r('sys.color.ohos_id_color_background_transparent'),
410      hover: this.treeViewTheme.itemHoverBgColor,
411      press: this.treeViewTheme.itemPressedBgColor,
412      selected: this.treeViewTheme.itemSelectedBgColor,
413      highLight: $r('sys.color.ohos_id_color_activated')
414    };
415    this.nodeBorder = {
416      borderWidth: BORDER_WIDTH_NONE,
417      borderColor: this.treeViewTheme.borderFocusedColor,
418      borderRadius: $r('sys.float.ohos_id_corner_radius_clicked')
419    };
420    this.flagLineLeftMargin = node.nodeLevel * LEVEL_MARGIN + MARGIN_OFFSET;
421  }
422
423  /**
424   * NodeInfo add collapse image
425   *
426   * @param isHasChildNode whether node has child node
427   */
428  addImageCollapse(isHasChildNode: boolean): void {
429    if (isHasChildNode) {
430      this.nodeItemView.imageCollapse =
431        CollapseImageNodeFlyweightFactory.getCollapseImageNodeByType(CollapseImageType.ARROW_RIGHT);
432    } else {
433      this.nodeItemView.imageCollapse = undefined;
434    }
435  }
436
437  setFontColor(color: ResourceColor): void {
438    this.fontColor = color
439  }
440
441  getFontColor(): ResourceColor {
442    return this.fontColor;
443  }
444
445  getPopUpInfo(): PopUpInfo {
446    return this.popUpInfo;
447  }
448
449  setPopUpIsShow(isShow: boolean): void {
450    this.popUpInfo.popUpIsShow = isShow;
451  }
452
453  setPopUpEnableArrow(popUpEnableArrow: boolean): void {
454    this.popUpInfo.popUpEnableArrow = popUpEnableArrow;
455  }
456
457  setPopUpColor(color: ResourceColor): void {
458    this.popUpInfo.popUpColor = color;
459  }
460
461  setPopUpText(text: string | Resource | undefined): void {
462    this.popUpInfo.popUpText = text;
463  }
464
465  setPopUpTextColor(popUpTextColor: ResourceColor): void {
466    this.popUpInfo.popUpTextColor = popUpTextColor;
467  }
468
469  getIsShowTitle(): boolean {
470    return this.isShowTitle;
471  }
472
473  getIsShowInputText(): boolean {
474    return this.isShowInputText;
475  }
476
477  setTitleAndInputTextStatus(isModify: boolean): void {
478    if (isModify) {
479      this.isShowTitle = false;
480      this.isShowInputText = true;
481    } else {
482      this.isShowTitle = true;
483      this.isShowInputText = false;
484    }
485  }
486
487  handleImageCollapseAfterAddNode(isAddImageCollapse: boolean): void {
488    // listTree this node already has ImageCollapse.
489    if (isAddImageCollapse) {
490      this.nodeItemView.imageCollapse =
491        CollapseImageNodeFlyweightFactory.getCollapseImageNodeByType(CollapseImageType.ARROW_DOWN);
492    } else {
493      this.nodeItemView.imageCollapse = undefined;
494    }
495  }
496
497  setNodeColor(nodeColor: ResourceColor | undefined): void {
498    if (nodeColor === undefined) {
499      return;
500    }
501    this.nodeColor = nodeColor;
502  }
503
504  getNodeColor(): ResourceColor {
505    return this.nodeColor;
506  }
507
508  setListItemHeight(listItemHeight: number): void {
509    this.listItemHeight = listItemHeight;
510  }
511
512  getListItemHeight(): number {
513    return this.listItemHeight;
514  }
515
516  getNodeCurrentNodeId(): number {
517    return this.node.currentNodeId;
518  }
519
520  getNodeParentNodeId(): number {
521    return this.node.parentNodeId;
522  }
523
524  getNodeLeftPadding(): number {
525    return this.nodeLeftPadding;
526  }
527
528  getNodeHeight(): Resource | number {
529    return this.nodeHeight;
530  }
531
532  setNodeIsShow(nodeIsShow: boolean): void {
533    this.nodeIsShow = nodeIsShow;
534  }
535
536  getNodeIsShow(): boolean {
537    return this.nodeIsShow;
538  }
539
540  getNodeItem(): NodeItemView {
541    return this.nodeItemView;
542  }
543
544  getNodeStatus(): Status {
545    return this.status;
546  }
547
548  getNodeBorder(): NodeBorder {
549    return this.nodeBorder;
550  }
551
552  setNodeBorder(isClearFocusStatus: boolean): void {
553    this.nodeBorder.borderWidth = isClearFocusStatus ? this.borderWidth.has : this.borderWidth.none;
554  }
555
556  getChildNodeInfo(): ChildNodeInfo {
557    return this.node.childNodeInfo;
558  }
559
560  getMenu(): () => void {
561    return this.nodeParam.container as () => void;
562  }
563
564  setIsSelected(isSelected: boolean): void {
565    this.isSelected = isSelected;
566  }
567
568  getIsSelected(): boolean {
569    return this.isSelected;
570  }
571
572  /* To gain the information while to alter node. */
573  getNodeInfoData(): NodeParam {
574    return this.nodeParam;
575  }
576
577  /* To gain the tree Node(NodeItem) while to alter node. */
578  public getNodeInfoNode(): NodeItem {
579    return this.node;
580  }
581
582  public getIsFolder(): boolean | undefined {
583    return this.nodeParam.isFolder;
584  }
585
586  public setCanShowFlagLine(canShowFlagLine: boolean): void {
587    this.canShowFlagLine = canShowFlagLine;
588  }
589
590  public getCanShowFlagLine(): boolean {
591    return this.canShowFlagLine;
592  }
593
594  public setFlagLineLeftMargin(currentNodeLevel: number | undefined): void {
595    if (currentNodeLevel === undefined) {
596      return;
597    }
598    this.flagLineLeftMargin = currentNodeLevel * LEVEL_MARGIN + MARGIN_OFFSET; // calculate
599  }
600
601  public getFlagLineLeftMargin(): number {
602    return this.flagLineLeftMargin;
603  }
604
605  public getNodeLevel(): number {
606    return this.node.nodeLevel;
607  }
608
609  public setIsOverBorder(isOverBorder: boolean): void {
610    this.isOverBorder = isOverBorder;
611  }
612
613  public getIsOverBorder(): boolean {
614    return this.isOverBorder;
615  }
616
617  public setCanShowBottomFlagLine(canShowBottomFlagLine: boolean): void {
618    this.canShowBottomFlagLine = canShowBottomFlagLine;
619  }
620
621  public getCanShowBottomFlagLine(): boolean {
622    return this.canShowBottomFlagLine;
623  }
624
625  public setIsHighLight(isHighLight: boolean): void {
626    this.isHighLight = isHighLight;
627  }
628
629  public getIsHighLight(): boolean {
630    return this.isHighLight;
631  }
632
633  public setIsModify(isModify: boolean): void {
634    this.isModify = isModify;
635  }
636
637  public getIsModify(): boolean {
638    return this.isModify;
639  }
640}
641
642/**
643 * Control style of operation element.
644 * @enum { TreeListenType }
645 * @syscap SystemCapability.ArkUI.ArkUI.Full
646 * @since 10
647 */
648/**
649 * Control style of operation element.
650 * @enum { TreeListenType }
651 * @syscap SystemCapability.ArkUI.ArkUI.Full
652 * @atomicservice
653 * @since 11
654 */
655export enum TreeListenType {
656  /**
657   * register listener after a node is clicked.
658   * @syscap SystemCapability.ArkUI.ArkUI.Full
659   * @since 10
660   */
661  /**
662   * register listener after a node is clicked.
663   * @syscap SystemCapability.ArkUI.ArkUI.Full
664   * @atomicservice
665   * @since 11
666   */
667  NODE_CLICK = 'NodeClick',
668
669  /**
670   * register listener after a node is add.
671   * @syscap SystemCapability.ArkUI.ArkUI.Full
672   * @since 10
673   */
674  /**
675   * register listener after a node is add.
676   * @syscap SystemCapability.ArkUI.ArkUI.Full
677   * @atomicservice
678   * @since 11
679   */
680  NODE_ADD = 'NodeAdd',
681
682  /**
683   * register listener after a node is deleted.
684   * @syscap SystemCapability.ArkUI.ArkUI.Full
685   * @since 10
686   */
687  /**
688   * register listener after a node is deleted.
689   * @syscap SystemCapability.ArkUI.ArkUI.Full
690   * @atomicservice
691   * @since 11
692   */
693  NODE_DELETE = 'NodeDelete',
694
695  /**
696   * register listener after a node is modified.
697   * @syscap SystemCapability.ArkUI.ArkUI.Full
698   * @since 10
699   */
700  /**
701   * register listener after a node is modified.
702   * @syscap SystemCapability.ArkUI.ArkUI.Full
703   * @atomicservice
704   * @since 11
705   */
706  NODE_MODIFY = 'NodeModify',
707
708  /**
709   * register listener after a node is moved.
710   * @syscap SystemCapability.ArkUI.ArkUI.Full
711   * @since 10
712   */
713  /**
714   * register listener after a node is moved.
715   * @syscap SystemCapability.ArkUI.ArkUI.Full
716   * @atomicservice
717   * @since 11
718   */
719  NODE_MOVE = 'NodeMove',
720}
721
722/**
723 * Declare class TreeListener.
724 * @syscap SystemCapability.ArkUI.ArkUI.Full
725 * @since 10
726 */
727/**
728 * Declare class TreeListener.
729 * @syscap SystemCapability.ArkUI.ArkUI.Full
730 * @atomicservice
731 * @since 11
732 */
733export class TreeListener {
734  public _events: [(callbackParam: CallbackParam) => void] | [] = [];
735  _once_events: [(callbackParam: CallbackParam) => void] | [] = [];
736
737  constructor() {
738  }
739
740  /**
741   * Event registration and processing.
742   *
743   * The event will not be destroyed after being processed.
744   *
745   * @param { type } event Registered Events.
746   * @param callback.
747   * @syscap SystemCapability.ArkUI.ArkUI.Full
748   * @since 10
749   */
750  /**
751   * Event registration and processing.
752   *
753   * The event will not be destroyed after being processed.
754   *
755   * @param { type } event Registered Events.
756   * @param callback.
757   * @syscap SystemCapability.ArkUI.ArkUI.Full
758   * @atomicservice
759   * @since 11
760   */
761  public on(type: TreeListenType, callback: (callbackParam: CallbackParam) => void): void {
762    if (Array.isArray(type)) {
763      for (let i = 0, l = type.length; i < l; i++) {
764        this.on((type as TreeListenType[])[i], callback);
765      }
766    } else {
767      (this._events[type] || (this._events[type] = [])).push(callback);
768    }
769  }
770
771  /**
772   * Event registration and processing.
773   *
774   * After the event is processed once, it will be destroyed.
775   *
776   * @param { type } event Registered Events.
777   * @param callback.
778   * @syscap SystemCapability.ArkUI.ArkUI.Full
779   * @since 10
780   */
781  /**
782   * Event registration and processing.
783   *
784   * After the event is processed once, it will be destroyed.
785   *
786   * @param { type } event Registered Events.
787   * @param callback.
788   * @syscap SystemCapability.ArkUI.ArkUI.Full
789   * @atomicservice
790   * @since 11
791   */
792  public once(type: TreeListenType, callback?: (callbackParam: CallbackParam) => void): void {
793    if (Array.isArray(type)) {
794      this.off(type, callback);
795    } else {
796      (this._once_events[type] || (this._once_events[type] = [])).push(callback);
797    }
798  }
799
800  /**
801   * Destroy event.
802   *
803   * @param type Registered Events.
804   * @param callback Event callback.
805   * @since 10
806   */
807  public off(type: TreeListenType, callback?: (callbackParam: CallbackParam) => void): void {
808    if (type === null) {
809      this._events = [];
810    }
811    if (Array.isArray(type)) {
812      for (let i: number = 0, l: number = type.length; i < l; i++) {
813        this.off((type as TreeListenType[])[i], callback);
814      }
815    }
816    let cbs: [(callbackParam: CallbackParam) => void] = this._events[type];
817    if (!cbs) {
818      return;
819    }
820    if (callback === null) {
821      this._events[type] = null;
822    }
823    let i: number = cbs.length;
824    while (i--) {
825      let cb: (callbackParam: CallbackParam) => void = cbs[i];
826      if (cb === callback) {
827        cbs.splice(i, 1);
828        break;
829      }
830    }
831  }
832
833  /**
834   * Triggers all callbacks of an event with parameters.
835   *
836   * @param event Registered Events.
837   * @param argument Parameters returned by the callback event.
838   * @since 10
839   */
840  public emit(event: TreeListenType, argument: CallbackParam) {
841    if (this._once_events[event]) {
842      let cbsOnce: ((callbackParam: CallbackParam) => void)[] =
843        Array.from<(callbackParam: CallbackParam) => void>(this._once_events[event]);
844      if (cbsOnce) {
845        for (let i: number = 0, l: number = cbsOnce.length; i < l; i++) {
846          try {
847            cbsOnce[i](argument);
848          } catch (e) {
849            throw new Error('once function callbacks error.');
850          }
851        }
852        this._once_events[event] = null;
853      }
854    } else if (this._events[event]) {
855      let cbsOn: ((callbackParam: CallbackParam) => void)[] =
856        Array.from<(callbackParam: CallbackParam) => void>(this._events[event]);
857      if (cbsOn) {
858        for (let i: number = 0, l: number = cbsOn.length; i < l; i++) {
859          try {
860            cbsOn[i](argument);
861          } catch (e) {
862            throw new Error('on function callbacks error.');
863          }
864        }
865      }
866    }
867  }
868}
869
870/**
871 * Declare class TreeListenerManager.
872 * @syscap SystemCapability.ArkUI.ArkUI.Full
873 * @since 10
874 */
875/**
876 * Declare class TreeListenerManager.
877 * @syscap SystemCapability.ArkUI.ArkUI.Full
878 * @atomicservice
879 * @since 11
880 */
881export class TreeListenerManager {
882  public static readonly APP_KEY_EVENT_BUS = 'app_key_event_bus';
883  private appEventBus: TreeListener;
884
885  private constructor() {
886    this.appEventBus = new TreeListener();
887  }
888
889  /**
890   * Get instance of treeListenerManager.
891   * @return treeListenerManager instance.
892   * @static
893   * @syscap SystemCapability.ArkUI.ArkUI.Full
894   * @since 10
895   */
896  /**
897   * Get instance of treeListenerManager.
898   * @return treeListenerManager instance.
899   * @static
900   * @syscap SystemCapability.ArkUI.ArkUI.Full
901   * @atomicservice
902   * @since 11
903   */
904  static getInstance(): TreeListenerManager {
905    if (AppStorage.Get('app_key_event_bus') === undefined) {
906      AppStorage.SetOrCreate('app_key_event_bus', new TreeListenerManager())
907    }
908    return AppStorage.Get('app_key_event_bus') as TreeListenerManager;
909  }
910
911  /**
912   * Get treeListener.
913   * @return treeListener object
914   * @syscap SystemCapability.ArkUI.ArkUI.Full
915   * @since 10
916   */
917  /**
918   * Get treeListener.
919   * @return treeListener object
920   * @syscap SystemCapability.ArkUI.ArkUI.Full
921   * @atomicservice
922   * @since 11
923   */
924  public getTreeListener(): TreeListener {
925    return this.appEventBus;
926  }
927}
928
929/**
930 * Declare TreeView Component
931 * @syscap SystemCapability.ArkUI.ArkUI.Full
932 * @since 10
933 */
934@Component
935export struct TreeView {
936  /**
937   * Node data source of TreeView
938   * @type TreeController
939   * @syscap SystemCapability.ArkUI.ArkUI.Full
940   * @since 10
941   */
942  /**
943   * Node data source of TreeView
944   * @type TreeController
945   * @syscap SystemCapability.ArkUI.ArkUI.Full
946   * @atomicservice
947   * @since 11
948   */
949  treeController: TreeController = new TreeController();
950  @State nodeList: NodeInfo[] = [];
951  listNodeDataSource: ListNodeDataSource = new ListNodeDataSource();
952  @State item: NodeInfo[] | null = null;
953  @State touchCount: number = 0;
954  @State dropSelectedIndex: number = 0;
955  @State viewLastIndex: number = -1;
956  @State followingSystemFontScale: boolean = false;
957  @State maxAppFontScale: number = 1;
958  @State listItemBgColor: ResourceColor = $r('sys.color.ohos_id_color_background_transparent');
959  @Provide treeViewTheme: TreeViewTheme = TreeViewTheme.getInstance();
960
961  @Builder
962  NullBuilder() {
963  };
964
965  @BuilderParam private listTreeViewMenu: () => void = this.NullBuilder;
966  private readonly MAX_CN_LENGTH: number = 254;
967  private readonly MAX_EN_LENGTH: number = 255;
968  private readonly INITIAL_INVALID_VALUE = -1;
969  private readonly MAX_TOUCH_DOWN_COUNT = 0;
970  private isMultiPress: boolean = false;
971  private touchDownCount: number = this.INITIAL_INVALID_VALUE;
972  private appEventBus: TreeListener = TreeListenerManager.getInstance().getTreeListener();
973  private readonly itemPadding: ItemPadding = {
974    left: $r('sys.float.ohos_id_card_margin_start'),
975    right: $r('sys.float.ohos_id_card_margin_end'),
976    top: $r('sys.float.ohos_id_text_margin_vertical'),
977    bottom: $r('sys.float.ohos_id_text_margin_vertical'),
978  };
979  private readonly textInputPadding: ItemPadding =
980    { left: $r('sys.float.padding_level0'), right: $r('sys.float.padding_level0'),
981      top: $r('sys.float.padding_level0'), bottom: $r('sys.float.padding_level0') }
982
983  onWillApplyTheme(theme: Theme) {
984    this.treeViewTheme.itemSelectedBgColor = theme.colors.interactiveSelect;
985    this.treeViewTheme.itemPressedBgColor = theme.colors.interactivePressed;
986    this.treeViewTheme.itemHoverBgColor = theme.colors.interactiveHover;
987    this.treeViewTheme.primaryTitleFontColor = theme.colors.fontPrimary;
988    this.treeViewTheme.secondaryTitleFontColor = theme.colors.fontSecondary;
989    this.treeViewTheme.primaryTitleActiveFontColor = theme.colors.interactiveActive;
990    this.treeViewTheme.borderFocusedColor = theme.colors.interactiveFocus;
991    this.treeViewTheme.leftIconColor = theme.colors.iconSecondary;
992    this.treeViewTheme.leftIconActiveColor = theme.colors.interactiveActive;
993    this.treeViewTheme.arrowIconColor = theme.colors.iconPrimary;
994    this.treeController.treeViewTheme = this.treeViewTheme;
995  }
996
997  aboutToAppear(): void {
998    if (this.treeController !== null) {
999      this.listNodeDataSource = this.treeController.getListNodeDataSource();
1000      this.nodeList = this.treeController.getListNodeDataSource().listNode;
1001      this.item = this.treeController.getListNodeDataSource().listNode;
1002    }
1003    let uiContent: UIContext = this.getUIContext();
1004    this.followingSystemFontScale = uiContent.isFollowingSystemFontScale();
1005    this.maxAppFontScale = uiContent.getMaxFontScale();
1006  }
1007
1008  decideFontScale() {
1009    let uiContent: UIContext = this.getUIContext();
1010    let systemFontScale = (uiContent.getHostContext() as common.UIAbilityContext)?.config?.fontSizeScale ?? 1;
1011    if (!this.followingSystemFontScale) {
1012      return 1;
1013    }
1014    return Math.min(systemFontScale, this.maxAppFontScale, MAX_FONT_SCALE)
1015  }
1016
1017  @Builder
1018  popupForShowTitle(text: string | Resource, backgroundColor: Resource, fontColor: Resource) {
1019    Row() {
1020      Text(text)
1021        .fontSize($r('sys.float.ohos_id_text_size_body2'))
1022        .fontWeight('regular').fontColor(fontColor)
1023        .minFontScale(MIN_FONT_SCALE)
1024        .maxFontScale(this.decideFontScale())
1025    }.backgroundColor(backgroundColor)
1026    .border({ radius: $r('sys.float.ohos_id_elements_margin_horizontal_l') })
1027    .padding({
1028      left: $r('sys.float.ohos_id_elements_margin_horizontal_l'),
1029      right: $r('sys.float.ohos_id_elements_margin_horizontal_l'),
1030      top: $r('sys.float.ohos_id_card_margin_middle'),
1031      bottom: $r('sys.float.ohos_id_card_margin_middle'),
1032    })
1033  }
1034
1035  @Builder
1036  builder() {
1037    this.listTreeViewMenu()
1038  }
1039
1040  /* Set the popup of dragging node. */
1041  @Builder
1042  draggingPopup(item: NodeInfo) {
1043    Row() {
1044      if (item.getNodeItem().imageNode) {
1045        Row() {
1046          Image(item.getNodeItem().imageNode?.normalSource)
1047            .objectFit(ImageFit.Contain)
1048            .height(item.getNodeItem().imageNode?.itemHeight)
1049            .width(item.getNodeItem().imageNode?.itemWidth)
1050            .opacity(this.listNodeDataSource.getDragPopupPara().imageOpacity)
1051            .matchTextDirection((item.getNodeItem().imageCollapse?.collapseSource === ARROW_RIGHT || item.getNodeItem()
1052              .imageCollapse?.collapseSource === ARROW_RIGHT_WITHE) ? true : false)
1053        }
1054        .backgroundColor(COLOR_IMAGE_ROW)
1055        .margin({ end: getLengthMetricsByResourceOrNumber(item.getNodeItem().imageNode?.itemRightMargin) })
1056        .height(item.getNodeItem().imageNode?.itemHeight)
1057        .width(item.getNodeItem().imageNode?.itemWidth)
1058      }
1059
1060      Row() {
1061        if (item.getNodeItem().mainTitleNode && item.getIsShowTitle()) {
1062          Text(item.getNodeItem().mainTitleNode?.title)
1063            .maxLines(1)
1064            .minFontScale(MIN_FONT_SCALE)
1065            .maxFontScale(this.decideFontScale())
1066            .fontSize(item.getNodeItem().mainTitleNode?.size)
1067            .fontColor(this.listNodeDataSource.getDragPopupPara().fontColor)
1068            .fontWeight(this.listNodeDataSource.getDragPopupPara().fontWeight)
1069            .textOverflow({ overflow: TextOverflow.Ellipsis })
1070        }
1071      }
1072      .constraintSize({
1073        minWidth: item.getNodeItem().imageNode ?
1074        this.listNodeDataSource.getDragPopupPara().textConstraintSize.minWidth1 :
1075        this.listNodeDataSource.getDragPopupPara().textConstraintSize.minWidth2,
1076        maxWidth: item.getNodeItem().imageNode ?
1077        this.listNodeDataSource.getDragPopupPara().textConstraintSize.maxWidth1 :
1078        this.listNodeDataSource.getDragPopupPara().textConstraintSize.maxWidth2,
1079      })
1080    }
1081    .constraintSize({
1082      minWidth: this.listNodeDataSource.getDragPopupPara().floorConstraintSize.minWidth,
1083      maxWidth: this.listNodeDataSource.getDragPopupPara().floorConstraintSize.maxWidth,
1084    })
1085    .height(this.listNodeDataSource.getDragPopupPara().height)
1086    .backgroundColor(this.listNodeDataSource.getDragPopupPara().backgroundColor)
1087    .padding({
1088      start: LengthMetrics.resource(this.listNodeDataSource.getDragPopupPara().padding.left),
1089      end: LengthMetrics.resource(this.listNodeDataSource.getDragPopupPara().padding.right),
1090    })
1091    .shadow({
1092      radius: $r('sys.float.ohos_id_corner_radius_default_m'),
1093      color: SHADOW_COLOR,
1094      offsetY: 0,
1095    })
1096    .borderRadius(this.listNodeDataSource.getDragPopupPara().borderRadius) // need to doubleCheck.
1097  }
1098
1099  clearLastIndexColor(): void {
1100    if (this.viewLastIndex === -1 || this.viewLastIndex >= this.nodeList.length) {
1101      return;
1102    }
1103    this.setImageSources(this.viewLastIndex, InteractionStatus.NORMAL);
1104
1105    this.nodeList[this.viewLastIndex].setNodeColor($r('sys.color.ohos_id_color_background_transparent'))
1106    this.nodeList[this.viewLastIndex].fontColor = this.treeViewTheme.primaryTitleFontColor;
1107    this.listNodeDataSource.listNode[this.viewLastIndex].setNodeColor($r('sys.color.ohos_id_color_background_transparent'));
1108    this.listNodeDataSource.listNode[this.viewLastIndex].fontColor = this.treeViewTheme.primaryTitleFontColor;
1109    this.listNodeDataSource.listNode[this.viewLastIndex].setIsSelected(false);
1110    this.listNodeDataSource.setImageSource(this.viewLastIndex, InteractionStatus.NORMAL);
1111  }
1112
1113  setImageSources(index: number, interactionStatus: InteractionStatus): void {
1114    let nodeInfo: NodeInfo = this.nodeList[index];
1115    nodeInfo.setIsSelected(interactionStatus === InteractionStatus.SELECTED ||
1116      interactionStatus === InteractionStatus.EDIT || interactionStatus === InteractionStatus.FINISH_EDIT);
1117    if (nodeInfo.getNodeItem().mainTitleNode !== null && interactionStatus !== InteractionStatus.DRAG_INSERT &&
1118      interactionStatus !== InteractionStatus.FINISH_DRAG_INSERT) {
1119      nodeInfo.getNodeItem().mainTitleNode?.setMainTitleSelected(interactionStatus === InteractionStatus.SELECTED ||
1120        interactionStatus === InteractionStatus.FINISH_EDIT);
1121    }
1122    if (nodeInfo.getNodeItem().imageNode !== null) {
1123      nodeInfo.getNodeItem().imageNode?.setImageSource(interactionStatus);
1124    }
1125  }
1126
1127  build() {
1128    List({}) {
1129      LazyForEach(this.listNodeDataSource, (itemInner: NodeInfo) => {
1130        ListItem() {
1131          Row() {
1132            TreeViewInner({
1133              item: itemInner,
1134              listNodeDataSource: this.listNodeDataSource,
1135              index: this.listNodeDataSource.findIndex(itemInner.getNodeCurrentNodeId()),
1136              listTreeViewMenu: this.listTreeViewMenu,
1137            })
1138          }
1139          .onTouch((event: TouchEvent) => {
1140            this.viewLastIndex = this.listNodeDataSource.getLastIndex();
1141            let index: number = this.listNodeDataSource.findIndex(itemInner.getNodeCurrentNodeId());
1142
1143            if (event.type === TouchType.Down) {
1144              if (index !== this.viewLastIndex) {
1145                this.clearLastIndexColor();
1146                this.listNodeDataSource.lastIndex = index;
1147                this.listNodeDataSource.setClickIndex(index);
1148              }
1149            }
1150            if (event.type === TouchType.Up) {
1151              this.listNodeDataSource.listNode[index].setIsSelected(true);
1152              this.listNodeDataSource.setImageSource(index, InteractionStatus.SELECTED);
1153              if (this.listNodeDataSource.listNode[index].getNodeItem().imageNode !== null) {
1154                this.listNodeDataSource.listNode[index].imageSource = this.listNodeDataSource.listNode[index]
1155                  .getNodeItem().imageNode?.source;
1156              }
1157
1158              if (index !== this.viewLastIndex) {
1159                this.clearLastIndexColor();
1160                this.listNodeDataSource.lastIndex = index;
1161                this.listNodeDataSource.setClickIndex(index);
1162              }
1163              this.viewLastIndex = index;
1164            }
1165
1166            if (this.listNodeDataSource.getLastIndex() !== -1 && index !== this.listNodeDataSource.getLastIndex()) {
1167              this.listNodeDataSource.setPopUpInfo(
1168                PopUpType.WARNINGS,
1169                InputError.NONE,
1170                false,
1171                this.listNodeDataSource.getLastIndex()
1172              );
1173              this.listNodeDataSource.setItemVisibilityOnEdit(
1174                this.listNodeDataSource.getLastIndex(),
1175                MenuOperation.COMMIT_NODE
1176              );
1177            }
1178          })
1179        }
1180        .width('100%')
1181        .height(itemInner.getListItemHeight())
1182        .padding({
1183          start: LengthMetrics.resource(this.itemPadding.left),
1184          end: LengthMetrics.resource(this.itemPadding.right)
1185        })
1186        .align(Alignment.Start)
1187        .onDragStart((event: DragEvent, extraParams: string) => {
1188          if (this.listNodeDataSource.getIsDrag() || this.listNodeDataSource.getIsInnerDrag() || this.isMultiPress) {
1189            hilog.error(LOG_CODE, TAG, 'drag error, a item has been dragged');
1190            return;
1191          }
1192          this.dropSelectedIndex = JSON.parse(extraParams).selectedIndex;
1193          let currentNodeIndex: number = JSON.parse(extraParams).selectedIndex;
1194          let currentNodeInfo: NodeInfo = this.listNodeDataSource.getData(currentNodeIndex) as NodeInfo;
1195          let currentItemNodeId: number = itemInner.getNodeCurrentNodeId();
1196          /* handle the situation of drag error, currentNodeIndex is not found in onDragStart. */
1197          if (currentNodeIndex >= this.listNodeDataSource.totalCount() || currentNodeIndex === undefined) {
1198            hilog.error(LOG_CODE, TAG, 'drag error, currentNodeIndex is not found in onDragStart');
1199            return;
1200          }
1201
1202          this.listNodeDataSource.setIsInnerDrag(true);
1203          this.listNodeDataSource.setIsDrag(true);
1204          this.listNodeDataSource.setCurrentNodeInfo(currentNodeInfo);
1205          this.listNodeDataSource.setDraggingCurrentNodeId(currentNodeInfo?.getNodeCurrentNodeId());
1206          this.listNodeDataSource.setDraggingParentNodeId(currentNodeInfo?.getNodeParentNodeId());
1207
1208          /* set the opacity of the dragging node. */
1209          let draggingNodeOpacity: number = DRAG_OPACITY;
1210          this.listNodeDataSource.setListItemOpacity(draggingNodeOpacity);
1211          this.listNodeDataSource.notifyDataChange(currentNodeIndex);
1212
1213          /**
1214           * handle the situation of drag is too fast,it attribute a fault to OH.
1215           * OH has Solved on real machine.
1216           */
1217          if (currentItemNodeId !== currentNodeInfo?.getNodeCurrentNodeId()) {
1218            hilog.error(LOG_CODE, TAG, 'drag is too fast, it attribute a fault to OH');
1219            this.listNodeDataSource.setIsDrag(false);
1220            return;
1221          }
1222
1223          return this.draggingPopup(currentNodeInfo);
1224        })
1225      }, (item: NodeInfo) => JSON.stringify(item))
1226    }
1227
1228    /* Move the dragged node. */
1229    .onDragMove((event: DragEvent, extraParams: string) => {
1230      if (this.isMultiPress) {
1231        hilog.error(LOG_CODE, TAG, 'drag error, a item has been dragged');
1232        return;
1233      }
1234      let nodeHeight: number = LIST_ITEM_HEIGHT;
1235
1236      /* flag the position of the focus on the node. */
1237      let flag: Flag = Math.floor(
1238        event.getY() /
1239          (nodeHeight / FLAG_NUMBER)) %
1240        FLAG_NUMBER ? Flag.DOWN_FLAG : Flag.UP_FLAG;
1241
1242      /* Record the node position to which the dragged node moves. */
1243      let index: number = JSON.parse(extraParams).insertIndex;
1244
1245      /* Handle the situation where the focus(index) exceeds the list area. */
1246      let isOverBorder: boolean = false;
1247      if (index >= this.listNodeDataSource.totalCount()) {
1248        flag = Flag.DOWN_FLAG;
1249        index = this.listNodeDataSource.totalCount() - 1;
1250        this.listNodeDataSource.getData(index)?.setIsOverBorder(true);
1251        isOverBorder = true;
1252      } else {
1253        this.listNodeDataSource.getData(index)?.setIsOverBorder(false);
1254      }
1255
1256      let currentNodeInfo: NodeInfo | undefined = this.listNodeDataSource.getData(index);
1257      let currentNodeId: number | undefined = currentNodeInfo?.getNodeCurrentNodeId();
1258
1259      /**
1260       * handle a situation that "draggingCurrentNodeId" is parent of "insertNodeCurrentNodeId";
1261       * do not perform some functions.
1262       */
1263      if (index !== this.listNodeDataSource.getLastPassIndex() && this.listNodeDataSource.getIsInnerDrag()) {
1264        let isParentNodeOfInsertNode: boolean = this.listNodeDataSource.getIsParentOfInsertNode(currentNodeId);
1265        if (isParentNodeOfInsertNode) {
1266          this.listNodeDataSource.setPassIndex(index);
1267          if (currentNodeId !== undefined) {
1268            this.listNodeDataSource.clearTimeOutAboutDelayHighLightAndExpand(
1269              this.listNodeDataSource.findIndex(currentNodeId));
1270          }
1271          this.listNodeDataSource.setFlag(Flag.NONE);
1272          return;
1273        }
1274      }
1275      this.listNodeDataSource.setLastPassIndex(index);
1276
1277      /* Set the visibility of the flag line. */
1278      this.listNodeDataSource.setVisibility(flag, index - 1, isOverBorder);
1279
1280      /* Automatically HighLight one second delay and expand after two second delay. */
1281      if (currentNodeId !== undefined && currentNodeId !== this.listNodeDataSource.getDraggingCurrentNodeId()) {
1282        this.listNodeDataSource.delayHighLightAndExpandNode(this.listNodeDataSource.findIndex(currentNodeId),
1283          currentNodeId, index);
1284      }
1285    })
1286
1287    /* DragEvent Enter. */
1288    .onDragEnter((event: DragEvent, extraParams: string) => {
1289      if (this.listNodeDataSource.getIsInnerDrag()) {
1290        this.listNodeDataSource.setIsDrag(true);
1291
1292        /* set the opacity of the dragging node. */
1293        let draggingNodeOpacity: number = DRAG_OPACITY;
1294        this.listNodeDataSource.setListItemOpacity(draggingNodeOpacity);
1295      }
1296    })
1297
1298    /* DragEvent Leave. */
1299    .onDragLeave((event: DragEvent, extraParams: string) => {
1300      this.listNodeDataSource.hideLastLine();
1301      this.listNodeDataSource.clearLastTimeoutHighLight();
1302      this.listNodeDataSource.clearLastTimeoutExpand();
1303      let draggingNodeOpacity: number = DRAG_OPACITY_NONE;
1304      this.listNodeDataSource.setListItemOpacity(draggingNodeOpacity);
1305      this.listNodeDataSource.setIsDrag(false);
1306      this.listNodeDataSource.notifyDataReload();
1307    })
1308
1309    /* DragEvent Drop. */
1310    .onDrop((event: DragEvent, extraParams: string) => {
1311      this.listNodeDataSource.clearLastTimeoutExpand();
1312      let draggingNodeOpacity: number = DRAG_OPACITY_NONE;
1313      this.listNodeDataSource.setListItemOpacity(draggingNodeOpacity);
1314      let insertNodeIndex: number = JSON.parse(extraParams).insertIndex;
1315      let currentNodeIndex: number = this.dropSelectedIndex;
1316
1317      if (currentNodeIndex - 1 > this.listNodeDataSource.totalCount() || currentNodeIndex === undefined) {
1318        hilog.error(LOG_CODE, TAG, 'drag error, currentNodeIndex is not found');
1319        this.listNodeDataSource.setIsDrag(false);
1320        return;
1321      }
1322
1323      if (insertNodeIndex === this.listNodeDataSource.totalCount()) {
1324        hilog.info(LOG_CODE, TAG, 'need to insert into the position of the last line');
1325        insertNodeIndex -= 1;
1326      }
1327
1328      let insertNodeInfo: NodeInfo | undefined = this.listNodeDataSource.getData(insertNodeIndex);
1329      if (insertNodeInfo === undefined) {
1330        return;
1331      }
1332      let insertNodeCurrentNodeId: number = insertNodeInfo.getNodeCurrentNodeId();
1333
1334      /* outer node is move in. */
1335      if (!this.listNodeDataSource.getIsDrag() || !this.listNodeDataSource.getIsInnerDrag()) {
1336        this.listNodeDataSource.clearLastTimeoutHighLight();
1337        this.listNodeDataSource.setIsInnerDrag(false);
1338        this.listNodeDataSource.hideLastLine();
1339        this.listNodeDataSource.initialParameterAboutDelayHighLightAndExpandIndex();
1340        this.listNodeDataSource.refreshSubtitle(insertNodeCurrentNodeId);
1341        this.listNodeDataSource.notifyDataReload();
1342        return;
1343      }
1344
1345      let currentNodeInfo: NodeInfo | null = this.listNodeDataSource.getCurrentNodeInfo();
1346      let insertNodeParentNodeId: number = insertNodeInfo.getNodeParentNodeId();
1347      let draggingCurrentNodeId: number = this.listNodeDataSource.getDraggingCurrentNodeId();
1348      let draggingParentNodeId: number = this.listNodeDataSource.getDraggingParentNodeId();
1349
1350      /**
1351       * handle a situation that "draggingCurrentNodeId" is parent of "insertNodeCurrentNodeId".
1352       * drag is fail.
1353       */
1354      let isParentNodeOfInsertNode: boolean = this.listNodeDataSource.getIsParentOfInsertNode(insertNodeCurrentNodeId);
1355      if (isParentNodeOfInsertNode) {
1356        this.listNodeDataSource.clearLastTimeoutHighLight();
1357        this.listNodeDataSource.setIsInnerDrag(false);
1358        this.listNodeDataSource.hideLastLine();
1359        this.listNodeDataSource.notifyDataChange(insertNodeIndex);
1360        this.listNodeDataSource.initialParameterAboutDelayHighLightAndExpandIndex();
1361        this.listNodeDataSource.setIsDrag(false);
1362
1363        /* set the position of focus. */
1364        let currentFocusIndex: number = this.listNodeDataSource.findIndex(draggingCurrentNodeId);
1365        this.listNodeDataSource.setClickIndex(currentFocusIndex);
1366        this.listNodeDataSource.handleEvent(Event.DRAG, currentFocusIndex);
1367        return;
1368      }
1369
1370      /* Collapse drag node. */
1371      if (this.listNodeDataSource.getExpandAndCollapseInfo(draggingCurrentNodeId) === NodeStatus.EXPAND) {
1372        this.listNodeDataSource.expandAndCollapseNode(
1373          this.listNodeDataSource.findIndex(draggingCurrentNodeId));
1374      }
1375
1376      let flag: boolean = false;
1377
1378      /* Expand insert node. */
1379      if (this.listNodeDataSource.getExpandAndCollapseInfo(insertNodeCurrentNodeId) === NodeStatus.COLLAPSE) {
1380        let currentIndex: number = this.listNodeDataSource.findIndex(insertNodeCurrentNodeId);
1381        if (this.listNodeDataSource.listNode[currentIndex].getIsHighLight()) {
1382          this.listNodeDataSource.expandAndCollapseNode(currentIndex);
1383        }
1384        flag = true;
1385      }
1386
1387      /* alter dragNode. */
1388      this.listNodeDataSource.setLastDelayHighLightId();
1389      if (currentNodeInfo !== null && draggingCurrentNodeId !== insertNodeCurrentNodeId) {
1390        this.listNodeDataSource.alterDragNode(insertNodeParentNodeId, insertNodeCurrentNodeId,
1391          draggingParentNodeId, draggingCurrentNodeId, currentNodeInfo);
1392        this.listNodeDataSource.hideLastLine();
1393      } else {
1394        /*the position of dragNode is equal with the position of insertNode. */
1395        this.listNodeDataSource.hideLastLine();
1396        this.listNodeDataSource.setLastPassId(draggingCurrentNodeId);
1397        this.listNodeDataSource.hideLastLine();
1398      }
1399      let lastDelayHighLightIndex: number =
1400        this.listNodeDataSource.findIndex(this.listNodeDataSource.getLastDelayHighLightId());
1401      this.listNodeDataSource.setLastDelayHighLightIndex(lastDelayHighLightIndex);
1402      this.listNodeDataSource.clearLastTimeoutHighLight();
1403      this.listNodeDataSource.initialParameterAboutDelayHighLightAndExpandIndex();
1404      this.listNodeDataSource.setIsDrag(false);
1405
1406      /* set the position of focus. */
1407      let currentFocusIndex: number = this.listNodeDataSource.findIndex(draggingCurrentNodeId);
1408      this.listNodeDataSource.setClickIndex(currentFocusIndex);
1409      this.listNodeDataSource.handleEvent(Event.DRAG, currentFocusIndex);
1410
1411      /* innerDrag is over. */
1412      this.listNodeDataSource.setIsInnerDrag(false);
1413      this.listNodeDataSource.notifyDataReload();
1414
1415      this.listNodeDataSource.listNode[currentFocusIndex].fontColor = this.treeViewTheme.primaryTitleActiveFontColor;
1416      if (this.viewLastIndex !== -1 && currentNodeIndex !== this.viewLastIndex) {
1417        this.listNodeDataSource.listNode[this.viewLastIndex].getNodeItem()
1418          .mainTitleNode?.setMainTitleSelected(false);
1419        this.listNodeDataSource.listNode[this.viewLastIndex].getNodeItem()
1420          .mainTitleNode?.setMainTitleHighLight(false);
1421      }
1422
1423      if (this.listNodeDataSource.listNode[this.viewLastIndex] !== null) {
1424        this.listNodeDataSource.listNode[this.viewLastIndex].fontColor = this.treeViewTheme.primaryTitleFontColor;
1425      }
1426
1427      this.listNodeDataSource.lastIndex = this.viewLastIndex;
1428      if (this.listNodeDataSource.listNode[this.viewLastIndex]) {
1429        if (this.listNodeDataSource.listNode[this.viewLastIndex].getNodeItem()
1430          .imageNode !== null) {
1431
1432          this.listNodeDataSource.listNode[this.viewLastIndex].getNodeItem()
1433            .imageNode?.setImageSource(InteractionStatus.NORMAL);
1434          this.listNodeDataSource.listNode[this.viewLastIndex].imageSource =
1435            this.listNodeDataSource.listNode[this.viewLastIndex].getNodeItem()
1436              .imageNode?.source;
1437        }
1438      }
1439
1440      if (this.listNodeDataSource.listNode[this.viewLastIndex]) {
1441        this.listNodeDataSource.listNode[this.viewLastIndex]
1442          .setNodeColor($r('sys.color.ohos_id_color_background_transparent'));
1443      }
1444
1445      this.listNodeDataSource.lastIndex = currentFocusIndex;
1446    })
1447  }
1448}
1449
1450/**
1451 * Declare CallbackParam
1452 * @type CallbackParam
1453 * @syscap SystemCapability.ArkUI.ArkUI.Full
1454 * @since 10
1455 */
1456/**
1457 * Declare CallbackParam
1458 * @type CallbackParam
1459 * @syscap SystemCapability.ArkUI.ArkUI.Full
1460 * @atomicservice
1461 * @since 11
1462 */
1463export interface CallbackParam {
1464  /**
1465   * Get the currentNodeId.
1466   * @type { number }
1467   * @syscap SystemCapability.ArkUI.ArkUI.Full
1468   * @since 10
1469   */
1470  /**
1471   * Get the currentNodeId.
1472   * @type { number }
1473   * @syscap SystemCapability.ArkUI.ArkUI.Full
1474   * @atomicservice
1475   * @since 11
1476   */
1477  currentNodeId: number,
1478
1479  /**
1480   * Get the parentNodeId.
1481   * @type { number }
1482   * @syscap SystemCapability.ArkUI.ArkUI.Full
1483   * @since 10
1484   */
1485  /**
1486   * Get the parentNodeId.
1487   * @type { number }
1488   * @syscap SystemCapability.ArkUI.ArkUI.Full
1489   * @atomicservice
1490   * @since 11
1491   */
1492  parentNodeId?: number,
1493
1494  /**
1495   * Get the childIndex.
1496   * @type { number }
1497   * @syscap SystemCapability.ArkUI.ArkUI.Full
1498   * @since 10
1499   */
1500  /**
1501   * Get the childIndex.
1502   * @type { number }
1503   * @syscap SystemCapability.ArkUI.ArkUI.Full
1504   * @atomicservice
1505   * @since 11
1506   */
1507  childIndex?: number,
1508}
1509
1510/**
1511 * Declare NodeParam
1512 * @typedef NodeParam
1513 * @syscap SystemCapability.ArkUI.ArkUI.Full
1514 * @since 10
1515 */
1516/**
1517 * Declare NodeParam
1518 * @typedef NodeParam
1519 * @syscap SystemCapability.ArkUI.ArkUI.Full
1520 * @atomicservice
1521 * @since 11
1522 */
1523export interface NodeParam {
1524  /**
1525   * Set the parentNodeId.
1526   * @type { number }
1527   * @syscap SystemCapability.ArkUI.ArkUI.Full
1528   * @since 10
1529   */
1530  /**
1531   * Set the parentNodeId.
1532   * @type { number }
1533   * @syscap SystemCapability.ArkUI.ArkUI.Full
1534   * @atomicservice
1535   * @since 11
1536   */
1537  parentNodeId?: number,
1538
1539  /**
1540   * Set currentNodeId.
1541   * @type { number }
1542   * @syscap SystemCapability.ArkUI.ArkUI.Full
1543   * @since 10
1544   */
1545  /**
1546   * Set currentNodeId.
1547   * @type { number }
1548   * @syscap SystemCapability.ArkUI.ArkUI.Full
1549   * @atomicservice
1550   * @since 11
1551   */
1552  currentNodeId?: number,
1553
1554  /**
1555   * Set catalog whether is floder.
1556   * @type { boolean }
1557   * @syscap SystemCapability.ArkUI.ArkUI.Full
1558   * @since 10
1559   */
1560  /**
1561   * Set catalog whether is floder.
1562   * @type { boolean }
1563   * @syscap SystemCapability.ArkUI.ArkUI.Full
1564   * @atomicservice
1565   * @since 11
1566   */
1567  isFolder?: boolean,
1568
1569  /**
1570   * Set the icon resource.
1571   * @type { ResourceStr }
1572   * @syscap SystemCapability.ArkUI.ArkUI.Full
1573   * @since 10
1574   */
1575  /**
1576   * Set the icon resource.
1577   * @type { ResourceStr }
1578   * @syscap SystemCapability.ArkUI.ArkUI.Full
1579   * @atomicservice
1580   * @since 11
1581   */
1582  icon?: ResourceStr,
1583
1584  /**
1585   * Set selected icon resource.
1586   * @type { ResourceStr }
1587   * @syscap SystemCapability.ArkUI.ArkUI.Full
1588   * @since 10
1589   */
1590  /**
1591   * Set selected icon resource.
1592   * @type { ResourceStr }
1593   * @syscap SystemCapability.ArkUI.ArkUI.Full
1594   * @atomicservice
1595   * @since 11
1596   */
1597  selectedIcon?: ResourceStr,
1598
1599  /**
1600   * Set edit icon resource.
1601   * @type { ResourceStr }
1602   * @syscap SystemCapability.ArkUI.ArkUI.Full
1603   * @since 10
1604   */
1605  /**
1606   * Set edit icon resource.
1607   * @type { ResourceStr }
1608   * @syscap SystemCapability.ArkUI.ArkUI.Full
1609   * @atomicservice
1610   * @since 11
1611   */
1612  editIcon?: ResourceStr,
1613
1614  /**
1615   * Set primary title content.
1616   * @type { ResourceStr }
1617   * @syscap SystemCapability.ArkUI.ArkUI.Full
1618   * @since 10
1619   */
1620  /**
1621   * Set primary title content.
1622   * @type { ResourceStr }
1623   * @syscap SystemCapability.ArkUI.ArkUI.Full
1624   * @atomicservice
1625   * @since 11
1626   */
1627  primaryTitle?: ResourceStr,
1628
1629  /**
1630   * Set secondary title content.
1631   * @type { ResourceStr }
1632   * @syscap SystemCapability.ArkUI.ArkUI.Full
1633   * @since 10
1634   */
1635  /**
1636   * Set secondary title content.
1637   * @type { ResourceStr }
1638   * @syscap SystemCapability.ArkUI.ArkUI.Full
1639   * @atomicservice
1640   * @since 11
1641   */
1642  secondaryTitle?: ResourceStr,
1643
1644  /**
1645   * Set subcomponent binded on tree item.
1646   * @type { () => void }
1647   * @syscap SystemCapability.ArkUI.ArkUI.Full
1648   * @since 10
1649   */
1650  /**
1651   * Set subcomponent binded on tree item.
1652   * @type { () => void }
1653   * @syscap SystemCapability.ArkUI.ArkUI.Full
1654   * @atomicservice
1655   * @since 11
1656   */
1657  container?: () => void,
1658}
1659
1660/**
1661 * Declare TreeController.
1662 * @syscap SystemCapability.ArkUI.ArkUI.Full
1663 * @since 10
1664 */
1665/**
1666 * Declare TreeController.
1667 * @syscap SystemCapability.ArkUI.ArkUI.Full
1668 * @atomicservice
1669 * @since 11
1670 */
1671export class TreeController {
1672  public readonly ROOT_NODE_ID: number = -1;
1673  private nodeIdList: number[] = [];
1674  private listNodeDataSource: ListNodeDataSource = new ListNodeDataSource();
1675  private initBuild: boolean = true;
1676  public treeViewTheme: TreeViewTheme = TreeViewTheme.getInstance();
1677
1678  public getListNodeDataSource(): ListNodeDataSource {
1679    return this.listNodeDataSource;
1680  }
1681
1682  public getClickNodeChildrenInfo(): NodeInfoView[] {
1683    let clickNodeId: number = this.listNodeDataSource.getClickNodeId();
1684    return this.listNodeDataSource.getClickNodeChildrenInfo(clickNodeId);
1685  }
1686
1687  public getChildrenId(): number[] {
1688    let clickNodeId: number = this.listNodeDataSource.getClickNodeId();
1689    return this.listNodeDataSource.getClickChildId(clickNodeId);
1690  }
1691
1692  /**
1693   * Delete a node.
1694   * Register an ON_ITEM_DELETE callback through the EventBus mechanism to obtain the IDs of all deleted nodes.
1695   * @syscap SystemCapability.ArkUI.ArkUI.Full
1696   * @since 10
1697   */
1698  /**
1699   * Delete a node.
1700   * Register an ON_ITEM_DELETE callback through the EventBus mechanism to obtain the IDs of all deleted nodes.
1701   * @syscap SystemCapability.ArkUI.ArkUI.Full
1702   * @atomicservice
1703   * @since 11
1704   */
1705  public removeNode(): void {
1706    let clickNodeId: number = this.listNodeDataSource.getClickNodeId();
1707    if (clickNodeId < 0) {
1708      return;
1709    }
1710    let parentNodeId: number = this.listNodeDataSource.findParentNodeId(clickNodeId);
1711    let removeNodeIdList: number[] = this.listNodeDataSource.removeNode(clickNodeId, parentNodeId);
1712    this.listNodeDataSource.refreshData(
1713      MenuOperation.REMOVE_NODE,
1714      parentNodeId,
1715      removeNodeIdList
1716    );
1717    this.nodeIdList.splice(this.nodeIdList.indexOf(clickNodeId), 1);
1718    this.listNodeDataSource.lastIndex = -1;
1719  }
1720
1721  /**
1722   * Modify the node name.
1723   * Register an ON_ITEM_MODIFY callback to obtain the ID, parent node ID, and node name of the modified node.
1724   * @syscap SystemCapability.ArkUI.ArkUI.Full
1725   * @since 10
1726   */
1727  /**
1728   * Modify the node name.
1729   * Register an ON_ITEM_MODIFY callback to obtain the ID, parent node ID, and node name of the modified node.
1730   * @syscap SystemCapability.ArkUI.ArkUI.Full
1731   * @atomicservice
1732   * @since 11
1733   */
1734  public modifyNode(): void {
1735    let clickNodeId: number = this.listNodeDataSource.getClickNodeId();
1736    this.listNodeDataSource.setItemVisibilityOnEdit(clickNodeId, MenuOperation.MODIFY_NODE);
1737  }
1738
1739  /**
1740   * add new node
1741   *
1742   * @param initBuild whether is in initialization process
1743   */
1744  public add(initBuild: boolean): void {
1745    let clickNodeId: number = this.listNodeDataSource.getClickNodeId();
1746    if (clickNodeId === this.listNodeDataSource.ROOT_NODE_ID || !this.listNodeDataSource.getIsFolder(clickNodeId)) {
1747      return;
1748    }
1749    let newNodeParam: NodeParam = this.listNodeDataSource.getNewNodeParam(clickNodeId);
1750    this.nodeIdList.push(this.nodeIdList[this.nodeIdList.length - 1] + 1);
1751    let newNodeId: number = this.nodeIdList[this.nodeIdList.length - 1];
1752    let addNodeResult: boolean = this.listNodeDataSource.addNode(clickNodeId, newNodeId,
1753      {
1754        isFolder: newNodeParam.isFolder,
1755        icon: newNodeParam.icon,
1756        selectedIcon: newNodeParam.selectedIcon,
1757        editIcon: newNodeParam.editIcon,
1758        primaryTitle: '新建文件夹',
1759        container: newNodeParam.container,
1760        secondaryTitle: newNodeParam.secondaryTitle as ResourceStr,
1761      }, initBuild);
1762    if (!addNodeResult) {
1763      return;
1764    }
1765    this.listNodeDataSource.refreshData(MenuOperation.ADD_NODE, clickNodeId, [newNodeId]);
1766
1767    this.listNodeDataSource.setPopUpInfo(
1768      PopUpType.WARNINGS,
1769      InputError.NONE,
1770      false,
1771      this.listNodeDataSource.getLastIndex()
1772    );
1773    this.listNodeDataSource.setItemVisibilityOnEdit(
1774      this.listNodeDataSource.getLastIndex(),
1775      MenuOperation.COMMIT_NODE
1776    );
1777    this.listNodeDataSource.listNode[this.listNodeDataSource.getLastIndex()]
1778      .setFontColor(this.treeViewTheme.primaryTitleFontColor);
1779    let newNodeIndex: number = this.listNodeDataSource.findIndex(newNodeId);
1780    this.listNodeDataSource.setClickIndex(newNodeIndex);
1781    this.listNodeDataSource.handleEvent(Event.TOUCH_UP, newNodeIndex);
1782  }
1783
1784  /**
1785   * Initialize the interface of the tree view. This interface is used to generate ListNodeDataSource data.
1786   * addNode is only designed for initialization. It can only be invoked during initialization.
1787   *
1788   * A maximum of 50 directory levels can be added.
1789   *
1790   * @param nodeParam Configuration information of the newly added node.
1791   *
1792   * For details, see the comment description of NodeParam.
1793   * @return ListTreeNode Tree view component proxy class.
1794   * @syscap SystemCapability.ArkUI.ArkUI.Full
1795   * @since 10
1796   */
1797  /**
1798   * Initialize the interface of the tree view. This interface is used to generate ListNodeDataSource data.
1799   * addNode is only designed for initialization. It can only be invoked during initialization.
1800   *
1801   * A maximum of 50 directory levels can be added.
1802   *
1803   * @param nodeParam Configuration information of the newly added node.
1804   *
1805   * For details, see the comment description of NodeParam.
1806   * @return ListTreeNode Tree view component proxy class.
1807   * @syscap SystemCapability.ArkUI.ArkUI.Full
1808   * @atomicservice
1809   * @since 11
1810   */
1811  public addNode(nodeParam?: NodeParam): TreeController {
1812    if (nodeParam === undefined) {
1813      this.add(this.initBuild);
1814      return this;
1815    } else {
1816      let addNodeResult: boolean = false;
1817      if (nodeParam.primaryTitle !== undefined &&
1818        !this.listNodeDataSource.checkMainTitleIsValid(nodeParam.primaryTitle.toString())) {
1819        throw new Error('ListTreeNode[addNode]: ' +
1820          'The directory name cannot contain the following characters\ /: *? "< > | or exceeds the maximum length.');
1821        return this;
1822      }
1823      if (nodeParam.primaryTitle === null && nodeParam.icon === null) {
1824        throw new Error('ListTreeNode[addNode]: ' +
1825          'The icon and directory name cannot be empty at the same time.');
1826        return this;
1827      }
1828      if (nodeParam.currentNodeId === this.ROOT_NODE_ID || nodeParam.currentNodeId === null) {
1829        throw new Error('ListTreeNode[addNode]: currentNodeId can not be -1 or null.');
1830        return this;
1831      }
1832      if (nodeParam.currentNodeId !== undefined) {
1833        this.nodeIdList.push(nodeParam.currentNodeId);
1834      }
1835      if (nodeParam.parentNodeId !== undefined) {
1836        if (nodeParam.currentNodeId !== undefined) {
1837          addNodeResult =
1838            this.listNodeDataSource.addNode(nodeParam.parentNodeId, nodeParam.currentNodeId, nodeParam, this.initBuild);
1839        }
1840      }
1841      if (!addNodeResult) {
1842        return this;
1843      }
1844      if (!this.initBuild && nodeParam.parentNodeId !== undefined) {
1845        let newNodeId: number = this.nodeIdList[this.nodeIdList.length - 1];
1846        this.listNodeDataSource.refreshData(
1847          MenuOperation.ADD_NODE,
1848          nodeParam.parentNodeId,
1849          [newNodeId]
1850        );
1851      }
1852      return this;
1853    }
1854  }
1855
1856  /**
1857   * this interface is called when a secondaryTitle needs to be updated.
1858   *
1859   * @Param parentId ID of the parent node.
1860   * @Param parentSubTitle secondaryTitle of parent node.
1861   * @Param currentSubTitle secondaryTitle of current node.
1862   *
1863   * @syscap SystemCapability.ArkUI.ArkUI.Full
1864   * @since 10
1865   */
1866  /**
1867   * this interface is called when a secondaryTitle needs to be updated.
1868   *
1869   * @Param parentId ID of the parent node.
1870   * @Param parentSubTitle secondaryTitle of parent node.
1871   * @Param currentSubTitle secondaryTitle of current node.
1872   *
1873   * @syscap SystemCapability.ArkUI.ArkUI.Full
1874   * @atomicservice
1875   * @since 11
1876   */
1877  public refreshNode(parentId: number, parentSubTitle: ResourceStr, CurrentSubtitle: ResourceStr): void {
1878    this.listNodeDataSource.setNodeSubtitlePara(parentId, parentSubTitle, CurrentSubtitle);
1879  }
1880
1881  /**
1882   * After the initialization is complete by calling the addNode interface,
1883   * call this interface to complete initialization.
1884   *
1885   * This interface must be called when you finish initializing the ListTreeView by addNode.
1886   * @syscap SystemCapability.ArkUI.ArkUI.Full
1887   * @since 10
1888   */
1889  /**
1890   * After the initialization is complete by calling the addNode interface,
1891   * call this interface to complete initialization.
1892   *
1893   * This interface must be called when you finish initializing the ListTreeView by addNode.
1894   * @syscap SystemCapability.ArkUI.ArkUI.Full
1895   * @atomicservice
1896   * @since 11
1897   */
1898  public buildDone(): void {
1899    this.listNodeDataSource.initSection();
1900    this.listNodeDataSource.delayInit();
1901    this.listNodeDataSource.updateAllChildNum();
1902    delaySortNodeIdList(this.nodeIdList);
1903    this.initBuild = false;
1904  }
1905}
1906
1907class BasicDataSource implements IDataSource {
1908  private listeners: DataChangeListener[] = []
1909
1910  public totalCount(): number {
1911    return 0;
1912  }
1913
1914  public getData(index: number): NodeInfo | undefined {
1915    return undefined;
1916  }
1917
1918  registerDataChangeListener(listener: DataChangeListener): void {
1919    if (this.listeners.indexOf(listener) < 0) {
1920      this.listeners.push(listener);
1921    }
1922  }
1923
1924  unregisterDataChangeListener(listener: DataChangeListener): void {
1925    const pos = this.listeners.indexOf(listener);
1926    if (pos >= 0) {
1927      this.listeners.splice(pos, 1);
1928    }
1929  }
1930
1931  notifyDataReload(): void {
1932    this.listeners.forEach(listener => {
1933      listener.onDataReloaded();
1934    })
1935  }
1936
1937  notifyDataAdd(index: number): void {
1938    this.listeners.forEach(listener => {
1939      listener.onDataAdd(index);
1940    })
1941  }
1942
1943  notifyDataChange(index: number | undefined): void {
1944    if (index === undefined) {
1945      return;
1946    }
1947    this.listeners.forEach(listener => {
1948      listener.onDataChange(index);
1949    })
1950  }
1951
1952  notifyDataDelete(index: number): void {
1953    this.listeners.forEach(listener => {
1954      listener.onDataDelete(index);
1955    })
1956  }
1957
1958  notifyDataMove(from: number, to: number): void {
1959    this.listeners.forEach(listener => {
1960      listener.onDataMove(from, to);
1961    })
1962  }
1963}
1964
1965/**
1966 * delay update all parentnodes childNum
1967 *
1968 * @param isAdd whether addNode or delete node
1969 * @param count node count
1970 * @param nodeIdNodeItemMap nodeId and nodeItem relation map
1971 * @param updateNodeIdList nodeId list whose childNum need update
1972 */
1973function delayUpdateParentChildNum(isAdd: boolean, count: number,
1974  nodeIdNodeItemMap: Map<number, NodeItem>, updateNodeIdList: Array<number>): void {
1975  let taskId: number = setTimeout(() => {
1976    updateNodeIdList.forEach((parentNodeId) => {
1977      updateParentChildNumHandler(parentNodeId, nodeIdNodeItemMap, isAdd, count);
1978    });
1979    clearTimeout(taskId);
1980  }, DELAY_TIME);
1981}
1982
1983/**
1984 * delay update all parentnodes child number handler
1985 *
1986 * @param parentNodeId parent node id
1987 * @param nodeIdNodeItemMap nodeId and nodeItem relation map
1988 * @param isAdd whether addNode or delete node
1989 * @param count node count
1990 */
1991function updateParentChildNumHandler(parentNodeId: number, nodeIdNodeItemMap: Map<number, NodeItem>,
1992  isAdd: boolean, count: number) {
1993  let tmpParentNodeId: number = parentNodeId;
1994  while (tmpParentNodeId >= 0) {
1995    if (nodeIdNodeItemMap.has(tmpParentNodeId)) {
1996      let parent: NodeItem = nodeIdNodeItemMap.get(tmpParentNodeId) as NodeItem;
1997      parent.getChildNodeInfo().allChildNum =
1998        isAdd ? parent.getChildNodeInfo().allChildNum + count : parent.getChildNodeInfo().allChildNum - count;
1999      tmpParentNodeId = parent.parentNodeId;
2000    } else {
2001      hilog.error(LOG_CODE, TAG, 'updateParentChildNumHandler: parent node not found');
2002      break;
2003    }
2004  }
2005}
2006
2007/**
2008 * delay sort nodeId list
2009 *
2010 * @param nodeIdList nodeId list
2011 */
2012function delaySortNodeIdList(nodeIdList: Array<number>): void {
2013  let taskId: number = setTimeout(() => {
2014    nodeIdList.sort((a, b) => a - b);
2015    clearTimeout(taskId);
2016  }, DELAY_TIME);
2017}
2018
2019class ListNodeDataSource extends BasicDataSource {
2020  public readonly ROOT_NODE_ID = -1;
2021  public _root: NodeItem = new NodeItem(emptyNodeInfo);
2022  private readonly maxNodeLevel: number = 50;
2023  private readonly MAX_CN_LENGTH: number = 254;
2024  private readonly MAX_EN_LENGTH: number = 255;
2025  private readonly INITIAL_INVALID_VALUE = -1;
2026  public listNode: NodeInfo[] = [];
2027  public loadedListNode: NodeInfo[] = [];
2028  public nodeIdNodeItemMap: Map<number, NodeItem> = new Map<number, NodeItem>();
2029  public nodeIdNodeParamMap: Map<number, NodeParam> = new Map<number, NodeParam>();
2030  public lastIndex: number = -1; // record the last focused node.
2031  public thisIndex: number = -1; // records clicked nodes in the current period.
2032  private modifyNodeIndex: number = -1; // records the nodes edited in the current period.
2033  public modifyNodeId: number = -1;
2034  private currentOperation?: MenuOperation;
2035  private expandAndCollapseInfo: Map<number, NodeStatus> = new Map<number, NodeStatus>();
2036  public loadedNodeIdAndIndexMap: Map<number, number> = new Map<number, number>();
2037  public nodeIdAndNodeIndexMap: Map<number, number> = new Map<number, number>();
2038  private isTouchDown: boolean = false;
2039  private appEventBus: TreeListener = TreeListenerManager.getInstance().getTreeListener();
2040  /* parameter of the drag event. */
2041  private isInnerDrag: boolean = false; // Judge whether it is an internal drag event.
2042  // It is used to handle events(For example, prevent press events) during global drag.
2043  private isDrag: boolean = false;
2044  private draggingCurrentNodeId: number = this.INITIAL_INVALID_VALUE; // Record the current ID of the dragged node.
2045  private draggingParentNodeId: number = this.INITIAL_INVALID_VALUE; // Record the parent ID of the dragged node.
2046  private currentNodeInfo: NodeInfo | null = null; // To solve the problem of currentIndex missed in onDrop event.
2047  private listItemOpacity: number = 1; // It is used to set the opacity of the node when dragged.
2048  private lastPassIndex: number = this.INITIAL_INVALID_VALUE; // record the last passing node index in drag.
2049  private lastPassId?: number = this.INITIAL_INVALID_VALUE; // record the last passing node Id in drag.
2050  private thisPassIndex: number = this.INITIAL_INVALID_VALUE; // record the current passing node in drag.
2051  // record last passing node in delay expand event.
2052  private lastDelayExpandIndex: number = this.INITIAL_INVALID_VALUE;
2053  private timeoutExpandId: number = this.INITIAL_INVALID_VALUE;
2054  private lastTimeoutExpandId: number = this.INITIAL_INVALID_VALUE;
2055  private clearTimeoutExpandId: number = this.INITIAL_INVALID_VALUE;
2056  private timeoutHighLightId: number = this.INITIAL_INVALID_VALUE;
2057  private lastTimeoutHighLightId: number = this.INITIAL_INVALID_VALUE;
2058  private clearTimeoutHighLightId: number = this.INITIAL_INVALID_VALUE;
2059  // record last passing node in HighLight event.
2060  private lastDelayHighLightIndex: number = this.INITIAL_INVALID_VALUE;
2061  //record last passing node Id in HighLight event.
2062  private lastDelayHighLightId: number = this.INITIAL_INVALID_VALUE;
2063  private nodeIdAndSubtitleMap: Map<number, ResourceStr> = new Map<number, ResourceStr>();
2064  private flag: Flag = Flag.NONE;
2065  private selectedParentNodeId: number = this.INITIAL_INVALID_VALUE;
2066  private selectedParentNodeSubtitle: ResourceStr = '';
2067  private insertNodeSubtitle: ResourceStr = '';
2068  private currentFocusNodeId: number = this.INITIAL_INVALID_VALUE;
2069  private lastFocusNodeId: number = this.INITIAL_INVALID_VALUE;
2070  private addFocusNodeId: number = this.INITIAL_INVALID_VALUE;
2071  private treeViewTheme: TreeViewTheme = TreeViewTheme.getInstance();
2072  public updateNodeIdList: number[] = [];
2073  public readonly FLAG_LINE: FlagLine = {
2074    flagLineHeight: FLAG_LINE_HEIGHT,
2075    flagLineColor: $r('sys.color.ohos_id_color_emphasize'),
2076    xOffset: X_OFF_SET,
2077    yTopOffset: Y_OFF_SET,
2078    yBottomOffset: Y_BOTTOM_OFF_SET,
2079    yBasePlateOffset: Y_BASE_PLATE_OFF_SET,
2080  }
2081  private readonly DRAG_POPUP: DragPopup = {
2082    floorConstraintSize: { minWidth: FLOOR_MIN_WIDTH, maxWidth: FLOOR_MAX_WIDTH },
2083    textConstraintSize: {
2084      minWidth1: TEXT_MIN_WIDTH,
2085      maxWidth1: TEXT_MAX_WIDTH,
2086      minWidth2: MIN_WIDTH,
2087      maxWidth2: MAX_WIDTH,
2088    },
2089    padding: { left: $r('sys.float.padding_level4'), right: $r('sys.float.padding_level4') },
2090    backgroundColor: COLOR_IMAGE_EDIT,
2091    height: GRAG_POP_UP_HEIGHT,
2092    shadow: {
2093      radius: $r('sys.float.ohos_id_corner_radius_default_m'),
2094      color: SHADOW_COLOR,
2095      offsetX: 0,
2096      offsetY: SHADOW_OFFSETY,
2097    },
2098    borderRadius: $r('sys.float.ohos_id_corner_radius_clicked'),
2099    fontColor: this.treeViewTheme.primaryTitleFontColor,
2100    fontSize: $r('sys.float.ohos_id_text_size_body1'),
2101    fontWeight: FontWeight.Regular,
2102    imageOpacity: $r('sys.float.ohos_id_alpha_content_fourth')
2103  };
2104  private readonly subTitle: SubTitleStyle = {
2105    normalFontColor: this.treeViewTheme.secondaryTitleFontColor,
2106    highLightFontColor: $r('sys.color.ohos_id_color_primary_contrary'),
2107    fontSize: $r('sys.float.ohos_id_text_size_body2'),
2108    fontWeight: FontWeight.Regular,
2109    margin: { left: $r('sys.float.padding_level2'), right: $r('sys.float.padding_level12') }
2110  }
2111
2112  constructor() {
2113    super();
2114    this._root.nodeLevel = -1;
2115    this.nodeIdNodeItemMap.set(-1, this._root);
2116    this.nodeIdNodeParamMap.set(-1, emptyNodeInfo);
2117  }
2118
2119  private checkIndex(index: number): boolean {
2120    if (index < 0 || index >= this.listNode.length) {
2121      hilog.error(LOG_CODE, TAG, 'check index fail');
2122      return false;
2123    }
2124    return true;
2125  }
2126
2127  public changeNodeColor(index: number, color: ResourceColor | undefined): void {
2128    if (!this.checkIndex(index)) {
2129      return;
2130    }
2131    this.listNode[index].setNodeColor(color);
2132    this.listNode[index].setNodeBorder(false);
2133  }
2134
2135  private getNodeColor(index: number): ResourceColor {
2136    return this.listNode[index].getNodeColor();
2137  }
2138
2139  private handleFocusEffect(index: number, isClearFocusStatus: boolean): void {
2140    if (this.listNode[index].getNodeIsShow()) {
2141      this.listNode[index].setNodeBorder(isClearFocusStatus);
2142    }
2143  }
2144
2145  public setImageSource(index: number, interactionStatus: InteractionStatus): void {
2146    if (!this.checkIndex(index)) {
2147      return;
2148    }
2149    let nodeInfo: NodeInfo = this.listNode[index];
2150    nodeInfo.setIsSelected(interactionStatus === InteractionStatus.SELECTED ||
2151      interactionStatus === InteractionStatus.EDIT || interactionStatus === InteractionStatus.FINISH_EDIT);
2152    if (nodeInfo.getNodeItem().mainTitleNode !== null && interactionStatus !== InteractionStatus.DRAG_INSERT &&
2153      interactionStatus !== InteractionStatus.FINISH_DRAG_INSERT) {
2154      nodeInfo.getNodeItem().mainTitleNode?.setMainTitleSelected(interactionStatus === InteractionStatus.SELECTED ||
2155        interactionStatus === InteractionStatus.FINISH_EDIT);
2156    }
2157    if (nodeInfo.getNodeItem().imageNode !== null) {
2158      nodeInfo.getNodeItem().imageNode?.setImageSource(interactionStatus);
2159    }
2160  }
2161
2162  private setImageCollapseSource(index: number, interactionStatus: InteractionStatus): void {
2163    let nodeInfo: NodeInfo = this.listNode[index];
2164    if (nodeInfo.getNodeItem().imageCollapse !== undefined) {
2165      nodeInfo.getNodeItem().imageCollapse = CollapseImageNodeFlyweightFactory.getCollapseImageNode(interactionStatus,
2166        this.expandAndCollapseInfo.get(nodeInfo.getNodeCurrentNodeId()), nodeInfo.getNodeItem().imageCollapse?.type);
2167    }
2168  }
2169
2170  public clearLastIndexStatus(): void {
2171    if (!this.checkIndex(this.lastIndex)) {
2172      return;
2173    }
2174    this.setImageSource(this.lastIndex, InteractionStatus.NORMAL);
2175    this.changeNodeColor(this.lastIndex, this.listNode[this.lastIndex].getNodeStatus().normal);
2176    this.handleFocusEffect(this.lastIndex, false);
2177    this.notifyDataChange(this.loadedNodeIdAndIndexMap.get(this.listNode[this.lastIndex].getNodeCurrentNodeId()));
2178  }
2179
2180  private loadedListNodeFunction(): void {
2181    let index: number = 0;
2182    this.loadedNodeIdAndIndexMap.clear();
2183    this.nodeIdAndNodeIndexMap.clear();
2184    this.loadedListNode.splice(0, this.loadedListNode.length);
2185    for (let i: number = 0; i < this.listNode.length; i++) {
2186      this.nodeIdAndNodeIndexMap.set(this.listNode[i].getNodeCurrentNodeId(), i);
2187      if (this.listNode[i].getNodeIsShow()) {
2188        this.loadedNodeIdAndIndexMap.set(this.listNode[i].getNodeCurrentNodeId(), index++);
2189        this.loadedListNode.push(this.listNode[i]);
2190      }
2191    }
2192  }
2193
2194  private changeNodeStatus(clickIndex: number): void {
2195    if (clickIndex >= this.listNode.length) {
2196      hilog.error(LOG_CODE, TAG, 'changeNodeStatus clickIndex error.');
2197      return;
2198    }
2199    let thisIndex: number = clickIndex;
2200    let nodeId: number = this.listNode[clickIndex].getNodeCurrentNodeId();
2201    if (this.expandAndCollapseInfo.get(nodeId) === NodeStatus.EXPAND) {
2202      this.expandAndCollapseInfo.set(nodeId, NodeStatus.COLLAPSE);
2203      this.listNode[thisIndex].getNodeItem()
2204        .imageCollapse = CollapseImageNodeFlyweightFactory.changeImageCollapseSource(NodeStatus.COLLAPSE,
2205        this.listNode[thisIndex].getNodeItem().imageCollapse?.isCollapse);
2206    } else if (this.expandAndCollapseInfo.get(nodeId) === NodeStatus.COLLAPSE) {
2207      this.expandAndCollapseInfo.set(nodeId, NodeStatus.EXPAND);
2208      this.listNode[thisIndex].getNodeItem()
2209        .imageCollapse = CollapseImageNodeFlyweightFactory.changeImageCollapseSource(NodeStatus.EXPAND,
2210        this.listNode[thisIndex].getNodeItem().imageCollapse?.isCollapse);
2211    }
2212  }
2213
2214  private handleExpandAndCollapse(clickIndex: number, isRefreshList: boolean): void {
2215    if (clickIndex >= this.listNode.length) {
2216      hilog.error(LOG_CODE, TAG, 'handleExpandAndCollapse clickIndex error.');
2217      return;
2218    }
2219    let thisIndex: number = clickIndex;
2220    let nodeId: number = this.listNode[thisIndex].getNodeCurrentNodeId();
2221    if (!this.expandAndCollapseInfo.has(nodeId)) {
2222      return;
2223    }
2224
2225    let rootNodeStatus: NodeStatus | undefined = this.expandAndCollapseInfo.get(nodeId);
2226    if (this.listNode[thisIndex].getChildNodeInfo().isHasChildNode && rootNodeStatus === NodeStatus.COLLAPSE) {
2227      for (let i: number = 0; i < this.listNode[thisIndex].getChildNodeInfo().allChildNum; i++) {
2228        this.listNode[thisIndex + 1 + i].setNodeIsShow(false);
2229        this.listNode[thisIndex + 1 + i].setListItemHeight(LIST_ITEM_HEIGHT_NONE);
2230      }
2231      this.loadedListNodeFunction();
2232      this.notifyDataReload();
2233      return;
2234    }
2235
2236    let childNum: number[] | null = new Array(this.listNode[thisIndex].getChildNodeInfo().childNum);
2237    childNum[0] = thisIndex + 1;
2238    let index: number = 1;
2239    while (index < this.listNode[thisIndex].getChildNodeInfo().childNum) {
2240      childNum[index] = childNum[index - 1] + this.listNode[childNum[index - 1]].getChildNodeInfo().allChildNum + 1;
2241      index++;
2242    }
2243    if (rootNodeStatus === NodeStatus.EXPAND) {
2244      for (let i: number = 0; i < childNum.length; i++) {
2245        this.listNode[childNum[i]].setNodeIsShow(true);
2246        this.listNode[childNum[i]].setListItemHeight(LIST_ITEM_HEIGHT);
2247        let nodeId: number = this.listNode[childNum[i]].getNodeCurrentNodeId();
2248        if (this.expandAndCollapseInfo.get(nodeId) === NodeStatus.EXPAND) {
2249          this.handleExpandAndCollapse(childNum[i], false);
2250        }
2251      }
2252    }
2253    childNum = null;
2254    if (isRefreshList) {
2255      this.loadedListNodeFunction();
2256      this.notifyDataReload();
2257    }
2258  }
2259
2260  /**
2261   * update all parentNodes childNum
2262   */
2263  public updateAllChildNum(): void {
2264    delayUpdateParentChildNum(true, 1, this.nodeIdNodeItemMap, this.updateNodeIdList);
2265  }
2266
2267  private resetData(listNode: Array<NodeInfo>): void {
2268    listNode.splice(0, listNode.length);
2269    this.loadedNodeIdAndIndexMap.clear();
2270    this.loadedListNode.splice(0, this.loadedListNode.length);
2271    this.nodeIdAndNodeIndexMap.clear();
2272    this.nodeIdAndSubtitleMap.clear();
2273  }
2274
2275  private initHandler(listNode: Array<NodeInfo>, startLevel: number, endLevel?: number): void {
2276    let index: number = 0;
2277    let listIndex: number = 0;
2278    this.resetData(listNode);
2279    try {
2280      this.traverseSectionNodeDF((node: NodeItem): boolean => {
2281        if (node.getCurrentNodeId() >= 0 && this.nodeIdNodeParamMap.has(node.getCurrentNodeId())) {
2282          let nodeInfo: NodeInfo =
2283            new NodeInfo(node, this.nodeIdNodeParamMap.get(node.getCurrentNodeId()) as NodeParam);
2284          nodeInfo.addImageCollapse(node.getChildNodeInfo().isHasChildNode);
2285          listNode.push(nodeInfo);
2286          this.nodeIdAndNodeIndexMap.set(nodeInfo.getNodeCurrentNodeId(), listIndex++);
2287          index = this.nodeDFHandler(nodeInfo, index);
2288        }
2289        return false;
2290      }, this._root, startLevel, endLevel);
2291    } catch (err) {
2292      hilog.error(LOG_CODE, TAG, 'traverseSectionNodeDF function callbacks error.');
2293      this.resetData(listNode);
2294    }
2295  }
2296
2297  private nodeDFHandler(nodeInfo: NodeInfo, index: number): number {
2298    if (nodeInfo.getChildNodeInfo().isHasChildNode) {
2299      this.expandAndCollapseInfo.set(nodeInfo.getNodeCurrentNodeId(), NodeStatus.COLLAPSE);
2300    }
2301    if (nodeInfo.getNodeIsShow()) {
2302      this.loadedNodeIdAndIndexMap.set(nodeInfo.getNodeCurrentNodeId(), index++);
2303      this.loadedListNode.push(nodeInfo);
2304    }
2305    if (nodeInfo.getIsFolder()) {
2306      if (nodeInfo.getNodeInfoData().secondaryTitle !== undefined) {
2307        this.nodeIdAndSubtitleMap.set(
2308          nodeInfo.getNodeCurrentNodeId(),
2309          nodeInfo.getNodeInfoData().secondaryTitle as ResourceStr
2310        );
2311      } else {
2312        this.nodeIdAndSubtitleMap.set(nodeInfo.getNodeCurrentNodeId(), '');
2313      }
2314    }
2315    return index;
2316  }
2317
2318  /**
2319   * update delay init all nodes
2320   */
2321  public delayInit(): void {
2322    let timeId: number = setTimeout(() => {
2323      let listNode: NodeInfo[] = [];
2324      this.initHandler(listNode, 0);
2325      this.listNode.splice(0, this.listNode.length);
2326      this.listNode.push(...listNode);
2327      this.listNode.forEach((value: NodeInfo, index: number) => {
2328        this.notifyDataDelete(index);
2329        this.notifyDataAdd(index);
2330      });
2331      clearTimeout(timeId);
2332    }, DELAY_TIME);
2333  }
2334
2335  /**
2336   * update delay init some nodes
2337   */
2338  public initSection(): void {
2339    this.initHandler(this.listNode, 0, 1);
2340  }
2341
2342  private refreshRemoveNodeData(removeNodeIdList: number[], parentNodeInfo: NodeInfo): void {
2343    let deleteIndexList: number[] = [];
2344    if (removeNodeIdList.length === 0) {
2345      return;
2346    }
2347    let startIndex: number | undefined = undefined;
2348    for (let i: number = 0; i < removeNodeIdList.length; i++) {
2349      if (this.loadedNodeIdAndIndexMap.has(removeNodeIdList[i])) {
2350        let loadedIndex: number = this.loadedNodeIdAndIndexMap.get(removeNodeIdList[i]) as number;
2351        deleteIndexList.push(loadedIndex);
2352      }
2353      if (startIndex === undefined && this.nodeIdAndNodeIndexMap.has(removeNodeIdList[i])) {
2354        startIndex = this.nodeIdAndNodeIndexMap.get(removeNodeIdList[i]);
2355      }
2356      if (startIndex !== undefined) {
2357        let deleteNode: NodeInfo[] | null = this.listNode.splice(startIndex, 1);
2358        deleteNode = null;
2359      }
2360      if (this.expandAndCollapseInfo.has(removeNodeIdList[i])) {
2361        this.expandAndCollapseInfo.delete(removeNodeIdList[i]); // delete deleteNode expandAndCollapseInfo.
2362      }
2363    }
2364    deleteIndexList.forEach((value) => {
2365      this.notifyDataDelete(value); // notifyDataDelete do not  update data.
2366      this.notifyDataChange(value); // call notifyDataChange to update data.
2367    })
2368    if (parentNodeInfo.getNodeItem().imageCollapse === null) {
2369      if (this.nodeIdAndNodeIndexMap.has(parentNodeInfo.getNodeCurrentNodeId())) {
2370        let parentIndex: number = this.nodeIdAndNodeIndexMap.get(parentNodeInfo.getNodeCurrentNodeId()) as number;
2371        this.listNode[parentIndex]?.handleImageCollapseAfterAddNode(false);
2372      }
2373      // delete deleteNode parentNode expandAndCollapseInfo.
2374      this.expandAndCollapseInfo.delete(parentNodeInfo.getNodeCurrentNodeId());
2375      this.notifyDataChange(this.loadedNodeIdAndIndexMap.get(parentNodeInfo.getNodeCurrentNodeId()));
2376    }
2377    let callbackParam: CallbackParam = {
2378      currentNodeId: parentNodeInfo.getNodeCurrentNodeId(),
2379      parentNodeId: parentNodeInfo.getNodeParentNodeId(),
2380    };
2381    this.loadedListNodeFunction();
2382    this.appEventBus.emit(TreeListenType.NODE_DELETE, callbackParam);
2383  }
2384
2385  private refreshAddNodeData(addNodeIdList: number[]): void {
2386    let addNodeInfo: NodeInfo = new NodeInfo(new NodeItem(emptyNodeInfo), emptyNodeInfo);
2387    if (this.nodeIdNodeItemMap.has(addNodeIdList[0])) {
2388      let node: NodeItem = this.nodeIdNodeItemMap.get(addNodeIdList[0]) as NodeItem;
2389      addNodeInfo = new NodeInfo(node, this.nodeIdNodeParamMap.get(addNodeIdList[0]) as NodeParam);
2390      addNodeInfo.addImageCollapse(node.getChildNodeInfo().isHasChildNode);
2391    }
2392    addNodeInfo.setIsModify(true);
2393
2394    let index: number = 0;
2395    for (let i: number = 0; i < this.listNode.length; i++) {
2396      if (this.listNode[i].getNodeCurrentNodeId() === addNodeInfo.getNodeParentNodeId()) {
2397        index = i;
2398        if (this.listNode[i].getNodeItem().imageCollapse === null) {
2399          this.listNode[i].handleImageCollapseAfterAddNode(true);
2400          this.notifyDataChange(index);
2401        } else if (this.expandAndCollapseInfo.get(this.listNode[i].getNodeCurrentNodeId()) === NodeStatus.COLLAPSE) {
2402          this.changeNodeStatus(index);
2403        }
2404        this.listNode.splice(i + 1, 0, addNodeInfo);
2405        this.listNode[i + 1].setTitleAndInputTextStatus(true);
2406        this.listNode[i + 1].setNodeIsShow(true);
2407        this.listNode[i + 1].setListItemHeight(LIST_ITEM_HEIGHT);
2408        this.nodeIdAndNodeIndexMap.set(addNodeIdList[0], i + 1);
2409        this.setImageSource(i + 1, InteractionStatus.EDIT);
2410        this.currentOperation = MenuOperation.ADD_NODE;
2411        this.notifyDataAdd(i + 1);
2412        this.notificationNodeInfo(i + 1, this.currentOperation);
2413        break;
2414      }
2415    }
2416    this.modifyNodeIndex = index + 1;
2417    this.setClickIndex(index);
2418    this.lastIndex = index;
2419    this.expandAndCollapseInfo.set(addNodeInfo.getNodeParentNodeId(), NodeStatus.EXPAND);
2420    this.handleExpandAndCollapse(index, true);
2421  }
2422
2423  public refreshData(operation: MenuOperation,
2424    parentNodeId: number, changeNodeIdList: number[]): void {
2425    let parentNodeInfo: NodeInfo = new NodeInfo(new NodeItem(emptyNodeInfo), emptyNodeInfo);
2426    if (this.nodeIdNodeItemMap.has(parentNodeId)) {
2427      let parentNode: NodeItem = this.nodeIdNodeItemMap.get(parentNodeId) as NodeItem;
2428      parentNodeInfo = new NodeInfo(parentNode, this.nodeIdNodeParamMap.get(parentNodeId) as NodeParam);
2429      parentNodeInfo.addImageCollapse(parentNode.getChildNodeInfo().isHasChildNode);
2430    }
2431
2432    if (operation === MenuOperation.REMOVE_NODE) {
2433      this.nodeIdAndSubtitleMap.set(parentNodeId, this.selectedParentNodeSubtitle);
2434      this.notifyDataChange(this.loadedNodeIdAndIndexMap.get(parentNodeId));
2435      this.refreshRemoveNodeData(changeNodeIdList, parentNodeInfo);
2436    }
2437
2438    if (operation === MenuOperation.ADD_NODE) {
2439      this.addFocusNodeId = changeNodeIdList[0];
2440      this.nodeIdAndSubtitleMap.set(this.getClickNodeId(), this.selectedParentNodeSubtitle);
2441      this.nodeIdAndSubtitleMap.set(changeNodeIdList[0], this.insertNodeSubtitle);
2442      this.refreshAddNodeData(changeNodeIdList);
2443    }
2444  }
2445
2446  public setClickIndex(index: number): void {
2447    this.thisIndex = index;
2448  }
2449
2450  public getClickNodeId(): number {
2451    if (!this.checkIndex(this.thisIndex)) {
2452      return -1;
2453    }
2454    return this.listNode[this.thisIndex].getNodeCurrentNodeId();
2455  }
2456
2457  public expandAndCollapseNode(clickIndex: number): void {
2458    this.changeNodeStatus(clickIndex);
2459    this.handleExpandAndCollapse(clickIndex, true);
2460  }
2461
2462  public getIsTouchDown(): boolean {
2463    return this.isTouchDown;
2464  }
2465
2466  public getLastIndex(): number {
2467    return this.lastIndex;
2468  }
2469
2470  public findIndex(currentNodeId: number): number {
2471    let thisIndex: number = -1;
2472    if (this.nodeIdAndNodeIndexMap.has(currentNodeId)) {
2473      thisIndex = this.nodeIdAndNodeIndexMap.get(currentNodeId) as number;
2474    }
2475    return thisIndex;
2476  }
2477
2478  public handleEventDrag(index: number): void {
2479    if (!this.checkIndex(index)) {
2480      return;
2481    }
2482    this.setImageSource(index, InteractionStatus.NORMAL);
2483    this.changeNodeColor(index, this.listNode[index].getNodeStatus().normal);
2484    this.handleFocusEffect(index, false);
2485    this.notifyDataChange(this.loadedNodeIdAndIndexMap.get(this.listNode[index].getNodeCurrentNodeId()));
2486  }
2487
2488  public handleEvent(event: Event, index: number): void {
2489    /* Return while the event is dragging event. */
2490    if (this.isDrag) {
2491      return;
2492    }
2493    if (!this.checkIndex(index)) {
2494      return;
2495    }
2496
2497    if (event === Event.TOUCH_DOWN || event === Event.TOUCH_UP || event === Event.MOUSE_BUTTON_RIGHT) {
2498      if (index !== this.lastIndex) {
2499        this.clearLastIndexStatus();
2500      }
2501    }
2502
2503    this.eventHandler(index, event);
2504  }
2505
2506  private eventHandler(index: number, event: Event): void {
2507    let lazyForEachIndex: number =
2508      this.loadedNodeIdAndIndexMap.get(this.listNode[index].getNodeCurrentNodeId()) as number;
2509    switch (event) {
2510      case Event.TOUCH_DOWN:
2511        this.isTouchDown = true;
2512        this.changeNodeColor(index, this.listNode[index].getNodeStatus().press);
2513        this.notifyDataChange(lazyForEachIndex);
2514        break;
2515      case Event.TOUCH_UP: {
2516        this.touchUpHandler(index, lazyForEachIndex);
2517        break;
2518      }
2519      case Event.HOVER:
2520        if (this.getNodeColor(index) !== this.listNode[index].getNodeStatus().selected) {
2521          this.changeNodeColor(index, this.listNode[index].getNodeStatus().hover);
2522          this.notifyDataChange(lazyForEachIndex);
2523        }
2524        break;
2525      case Event.HOVER_OVER:
2526        if (this.getNodeColor(index) !== this.listNode[index].getNodeStatus().selected) {
2527          this.changeNodeColor(index, this.listNode[index].getNodeStatus().normal);
2528          this.notifyDataChange(lazyForEachIndex);
2529        }
2530        break;
2531      case Event.FOCUS:
2532        this.handleFocusEffect(index, true);
2533        this.notifyDataChange(lazyForEachIndex);
2534        break;
2535      case Event.BLUR:
2536        this.handleFocusEffect(index, false);
2537        this.notifyDataChange(lazyForEachIndex);
2538        break;
2539      case Event.MOUSE_BUTTON_RIGHT:
2540        this.lastIndex = index;
2541        this.finishEditing();
2542        break;
2543      case Event.DRAG:
2544        this.isTouchDown = false;
2545        let nodeInfo: NodeInfo = this.listNode[index];
2546        this.setImageSource(index, InteractionStatus.SELECTED);
2547        this.lastIndex = index;
2548        this.changeNodeColor(index, nodeInfo.getNodeStatus().selected);
2549        this.notifyDataChange(lazyForEachIndex);
2550        break;
2551      default:
2552        break;
2553    }
2554  }
2555
2556  private touchUpHandler(index: number, lazyForEachIndex: number): void {
2557    if (this.isInnerDrag) {
2558      this.isInnerDrag = false;
2559    }
2560    this.isTouchDown = false;
2561    let nodeInfo: NodeInfo = this.listNode[index];
2562    this.setImageSource(index, InteractionStatus.SELECTED);
2563    nodeInfo.setFontColor(this.treeViewTheme.primaryTitleFontColor);
2564    this.lastIndex = index;
2565    this.changeNodeColor(index, nodeInfo.getNodeStatus().selected);
2566    this.notifyDataChange(lazyForEachIndex);
2567  }
2568
2569  private notificationNodeInfo(addNodeId: number, operation: MenuOperation | undefined): void {
2570    if (operation === MenuOperation.MODIFY_NODE) {
2571      let modifyNodeInfo: NodeInfo = this.listNode[this.modifyNodeIndex];
2572      let backParamModify: CallbackParam = {
2573        currentNodeId: modifyNodeInfo?.getNodeCurrentNodeId(),
2574        parentNodeId: modifyNodeInfo?.getNodeParentNodeId(),
2575      };
2576      this.appEventBus.emit(TreeListenType.NODE_MODIFY, backParamModify);
2577    } else if (operation === MenuOperation.ADD_NODE) {
2578      let addNodeInfo: NodeInfo = this.listNode[addNodeId];
2579      if (addNodeInfo === undefined) {
2580        return;
2581      }
2582      let icon: Resource | string | undefined = (addNodeInfo.getNodeItem().imageNode !== undefined) ?
2583        addNodeInfo.getNodeItem().imageNode?.source : undefined;
2584      let selectedIcon: Resource | string | undefined = (addNodeInfo.getNodeItem().imageNode !== undefined) ?
2585        addNodeInfo.getNodeItem().imageNode?.selectedSource : undefined;
2586      let editIcon: Resource | string | undefined = (addNodeInfo.getNodeItem().imageNode !== undefined) ?
2587        addNodeInfo.getNodeItem().imageNode?.editSource : undefined;
2588      let callbackParam: CallbackParam = {
2589        currentNodeId: addNodeInfo?.getNodeCurrentNodeId(),
2590        parentNodeId: addNodeInfo?.getNodeParentNodeId(),
2591      };
2592      this.appEventBus.emit(TreeListenType.NODE_ADD, callbackParam);
2593    }
2594  }
2595
2596  public finishEditing(): void {
2597    if (this.modifyNodeIndex !== -1) {
2598      this.setImageSource(this.modifyNodeIndex, InteractionStatus.FINISH_EDIT);
2599      this.setImageCollapseSource(this.modifyNodeIndex, InteractionStatus.FINISH_EDIT);
2600      this.listNode[this.modifyNodeIndex].setIsModify(false);
2601      this.listNode[this.modifyNodeIndex].setTitleAndInputTextStatus(false);
2602      this.notificationNodeInfo(this.modifyNodeIndex, this.currentOperation);
2603      this.notifyDataChange(this.modifyNodeIndex);
2604    }
2605  }
2606
2607  public setItemVisibilityOnEdit(nodeId: number, operation: MenuOperation): void {
2608    let index: number = -1;
2609    if (nodeId === -1) {
2610      return;
2611    }
2612    if (operation === MenuOperation.MODIFY_NODE) {
2613      for (let i: number = 0; i < this.listNode.length; i++) { // nodeId to find index
2614        if (this.listNode[i]?.getNodeCurrentNodeId() === nodeId) {
2615          index = i;
2616          break;
2617        }
2618      }
2619      let nodeInfo: NodeInfo = this.listNode[index];
2620      if (nodeInfo === undefined) {
2621        return;
2622      }
2623      nodeInfo.setIsModify(true);
2624      if (nodeInfo.getNodeItem().mainTitleNode === null) {
2625        return; // no title
2626      }
2627
2628      this.currentOperation = MenuOperation.MODIFY_NODE;
2629      nodeInfo.setTitleAndInputTextStatus(true);
2630      this.setImageSource(index, InteractionStatus.EDIT);
2631      this.setImageCollapseSource(index, InteractionStatus.EDIT);
2632      this.modifyNodeIndex = index;
2633      if (nodeInfo.getNodeItem().inputText) {
2634        if (nodeInfo.getNodeItem().imageCollapse !== null) {
2635          nodeInfo.getNodeItem().inputText.rightMargin =
2636            $r('sys.float.ohos_id_text_paragraph_margin_xs');
2637        } else {
2638          nodeInfo.getNodeItem().inputText.rightMargin =
2639            $r('sys.float.ohos_id_elements_margin_horizontal_m');
2640        }
2641      }
2642      this.notifyDataChange(this.loadedNodeIdAndIndexMap.get(nodeId));
2643    }
2644    index = nodeId;
2645    if (operation === MenuOperation.COMMIT_NODE) {
2646      let nodeInfo: NodeInfo = this.listNode[index];
2647      if (nodeInfo === undefined) {
2648        return;
2649      }
2650      nodeInfo.setTitleAndInputTextStatus(false);
2651      nodeInfo.setIsModify(false);
2652      this.setImageSource(index, InteractionStatus.FINISH_EDIT);
2653      this.setImageCollapseSource(index, InteractionStatus.FINISH_EDIT);
2654      this.notificationNodeInfo(this.modifyNodeIndex, this.currentOperation);
2655      this.notifyDataChange(this.loadedNodeIdAndIndexMap.get(nodeInfo?.getNodeCurrentNodeId()));
2656    }
2657  }
2658
2659  public setPopUpInfo(popUpType: PopUpType, inputError: InputError, isShow: boolean, index: number): void {
2660    if (!this.checkIndex(index)) {
2661      return;
2662    }
2663    let nodeInfo: NodeInfo = this.listNode[index];
2664    if (nodeInfo === undefined) {
2665      return;
2666    }
2667    nodeInfo.setPopUpIsShow(isShow);
2668    // this.listNode index to lazyForEach index.
2669    let lazyForEachIndex: number = this.loadedNodeIdAndIndexMap.get(nodeInfo.getNodeCurrentNodeId()) as number;
2670    if (!isShow) {
2671      this.notifyDataChange(lazyForEachIndex);
2672      return;
2673    }
2674    if (popUpType === PopUpType.HINTS) {
2675      if (nodeInfo.getNodeItem().mainTitleNode !== null) {
2676        nodeInfo.setPopUpText(nodeInfo.getNodeItem().mainTitleNode?.title);
2677      } else {
2678        nodeInfo.setPopUpText('');
2679        nodeInfo.setPopUpIsShow(false);
2680      }
2681      nodeInfo.setPopUpEnableArrow(false);
2682      nodeInfo.setPopUpColor($r('sys.color.ohos_id_color_background'));
2683      nodeInfo.setPopUpTextColor(this.treeViewTheme.secondaryTitleFontColor);
2684    } else if (popUpType === PopUpType.WARNINGS) {
2685      if (nodeInfo.getNodeItem().inputText !== null) {
2686        if (inputError === InputError.INVALID_ERROR) {
2687          nodeInfo.setPopUpText('invalid error');
2688        } else if (inputError === InputError.LENGTH_ERROR) {
2689          nodeInfo.setPopUpText('length error');
2690        }
2691        nodeInfo.setPopUpEnableArrow(true);
2692        nodeInfo.setPopUpColor($r('sys.color.ohos_id_color_help_tip_bg'));
2693        nodeInfo.setPopUpTextColor($r('sys.color.ohos_id_color_text_hint_contrary'));
2694      }
2695    }
2696    this.notifyDataChange(lazyForEachIndex);
2697  }
2698
2699  public setShowPopUpTimeout(timeout: number, index: number): void {
2700    if (!this.checkIndex(index)) {
2701      return;
2702    }
2703    if (this.listNode[index].getNodeItem().mainTitleNode !== null) {
2704      this.listNode[index].getNodeItem().mainTitleNode.popUpTimeout = timeout;
2705    }
2706    let lazyForEachIndex: number =
2707      this.loadedNodeIdAndIndexMap.get(this.listNode[index].getNodeCurrentNodeId()) as number;
2708    this.notifyDataChange(lazyForEachIndex);
2709  }
2710
2711  public setMainTitleNameOnEdit(index: number, text: string): void {
2712    this.modifyNodeIndex = index;
2713    if (this.listNode[index].getNodeItem().mainTitleNode !== null) {
2714      this.listNode[index].getNodeItem().mainTitleNode.title = text;
2715    }
2716  }
2717
2718  public totalCount(): number {
2719    return this.loadedNodeIdAndIndexMap.size;
2720  }
2721
2722  public getData(index: number): NodeInfo | undefined {
2723    if (index < 0 || index >= this.loadedListNode.length) {
2724      return undefined;
2725    }
2726    return this.loadedListNode[index];
2727  }
2728
2729  public addData(index: number, data: NodeInfo): void {
2730    if (!this.checkIndex(index)) {
2731      return;
2732    }
2733    this.listNode.splice(index, 0, data);
2734    this.nodeIdAndNodeIndexMap.set(data.getNodeCurrentNodeId(), index);
2735    this.loadedListNodeFunction();
2736    this.notifyDataAdd(index);
2737  }
2738
2739  public pushData(data: NodeInfo): void {
2740    this.listNode.push(data);
2741    this.nodeIdAndNodeIndexMap.set(data.getNodeCurrentNodeId(), this.listNode.length);
2742    this.loadedListNodeFunction();
2743    this.notifyDataAdd(this.listNode.length - 1);
2744  }
2745
2746  public setIsInnerDrag(isInnerDrag: boolean): void {
2747    this.isInnerDrag = isInnerDrag;
2748  }
2749
2750  public getIsInnerDrag(): boolean {
2751    return this.isInnerDrag;
2752  }
2753
2754  public setIsDrag(isDrag: boolean): void {
2755    this.isDrag = isDrag;
2756  }
2757
2758  public getIsDrag(): boolean {
2759    return this.isDrag;
2760  }
2761
2762  public setCurrentNodeInfo(currentNodeInfo: NodeInfo | undefined): void {
2763    if (currentNodeInfo === undefined) {
2764      return;
2765    }
2766    this.currentNodeInfo = currentNodeInfo;
2767  }
2768
2769  public getCurrentNodeInfo(): NodeInfo | null {
2770    return this.currentNodeInfo;
2771  }
2772
2773  public setDraggingParentNodeId(draggingParentNodeId: number | undefined): void {
2774    if (draggingParentNodeId === undefined) {
2775      return;
2776    }
2777    this.draggingParentNodeId = draggingParentNodeId;
2778  }
2779
2780  public getDraggingParentNodeId(): number {
2781    return this.draggingParentNodeId;
2782  }
2783
2784  public getDraggingCurrentNodeId(): number {
2785    return this.draggingCurrentNodeId;
2786  }
2787
2788  public setDraggingCurrentNodeId(draggingCurrentNodeId: number | undefined): void {
2789    if (draggingCurrentNodeId === undefined) {
2790      return;
2791    }
2792    this.draggingCurrentNodeId = draggingCurrentNodeId;
2793  }
2794
2795  public setListItemOpacity(listItemOpacity: number): void {
2796    this.listItemOpacity = listItemOpacity;
2797  }
2798
2799  public getListItemOpacity(item: NodeInfo): number {
2800    return item.getNodeCurrentNodeId() === this.getDraggingCurrentNodeId() ? this.listItemOpacity : 1;
2801  }
2802
2803  public getDragPopupPara(): DragPopup {
2804    return this.DRAG_POPUP;
2805  }
2806
2807  public setLastPassIndex(lastPassIndex: number): void {
2808    this.lastPassIndex = lastPassIndex;
2809  }
2810
2811  public getLastPassIndex(): number {
2812    return this.lastPassIndex;
2813  }
2814
2815  public getIsParentOfInsertNode(insertNodeId: number | undefined): boolean {
2816    if (this.currentNodeInfo === null || insertNodeId === undefined) {
2817      return false;
2818    }
2819    let selectedNodeItem: NodeItem = this.currentNodeInfo.getNodeInfoNode();
2820    let parentId: number = selectedNodeItem.currentNodeId;
2821    let insertParentId: number | undefined = this.nodeIdNodeItemMap.get(insertNodeId as number)?.parentNodeId;
2822    while (insertParentId !== undefined && insertParentId !== -1) {
2823      if (parentId === insertParentId) {
2824        return true;
2825      } else {
2826        insertParentId = this.nodeIdNodeItemMap.get(insertParentId as number)?.parentNodeId;
2827      }
2828    }
2829    return false;
2830  }
2831
2832  public setPassIndex(thisPassIndex: number): void {
2833    this.thisPassIndex = thisPassIndex;
2834  }
2835
2836  public getPassIndex(): number {
2837    return this.thisPassIndex;
2838  }
2839
2840  public clearTimeOutAboutDelayHighLightAndExpand(currentIndex: number): void {
2841    if (this.lastPassId !== this.INITIAL_INVALID_VALUE && this.loadedNodeIdAndIndexMap.has(this.lastPassId as number)) {
2842      let index: number = this.loadedNodeIdAndIndexMap.get(this.lastPassId as number) as number;
2843      this.listNode.forEach((value) => {
2844        if (value.getNodeCurrentNodeId() === this.lastPassId) {
2845          value.setCanShowFlagLine(false);
2846          return;
2847        }
2848      })
2849      this.notifyDataChange(index);
2850    }
2851
2852    if ((this.lastTimeoutHighLightId !== this.INITIAL_INVALID_VALUE &&
2853      this.clearTimeoutHighLightId !== this.lastTimeoutHighLightId)) {
2854      clearTimeout(this.lastTimeoutHighLightId);
2855      if (this.lastDelayHighLightIndex !== this.INITIAL_INVALID_VALUE) {
2856        this.clearHighLight(this.lastDelayHighLightIndex);
2857        let index: number = this.loadedNodeIdAndIndexMap
2858          .get(this.listNode[this.lastDelayHighLightIndex].getNodeCurrentNodeId()) as number;
2859        this.notifyDataChange(index);
2860      }
2861      this.clearTimeoutHighLightId = this.lastTimeoutHighLightId;
2862    }
2863    this.lastTimeoutHighLightId = this.timeoutHighLightId;
2864    this.lastDelayHighLightIndex = currentIndex;
2865
2866    if ((this.lastTimeoutExpandId !== this.INITIAL_INVALID_VALUE &&
2867      this.clearTimeoutExpandId !== this.lastTimeoutExpandId)) {
2868      clearTimeout(this.lastTimeoutExpandId);
2869      this.clearTimeoutExpandId = this.lastTimeoutExpandId;
2870    }
2871    this.lastTimeoutExpandId = this.timeoutExpandId;
2872    this.lastDelayExpandIndex = this.INITIAL_INVALID_VALUE;
2873  }
2874
2875  public clearHighLight(currentIndex: number): void {
2876    if (!this.checkIndex(currentIndex)) {
2877      return;
2878    }
2879    this.changeNodeColor(currentIndex, this.listNode[currentIndex].getNodeStatus().normal);
2880    this.changeNodeHighLightColor(currentIndex, false);
2881    this.setImageSource(currentIndex, InteractionStatus.FINISH_DRAG_INSERT);
2882    this.setImageCollapseSource(currentIndex, InteractionStatus.FINISH_DRAG_INSERT);
2883    this.listNode[currentIndex].setIsHighLight(false);
2884  }
2885
2886  private changeNodeHighLightColor(index: number, isHighLight: boolean): void {
2887    if (this.listNode[index].getNodeItem().mainTitleNode && this.listNode[index].getIsShowTitle()) {
2888      this.listNode[index].getNodeItem().mainTitleNode?.setMainTitleHighLight(isHighLight);
2889    }
2890  }
2891
2892  public setVisibility(flag: Flag, index: number, isOverBorder: boolean): void {
2893    let isChanged: boolean = (this.thisPassIndex !== index || this.flag !== flag) ? true : false;
2894    this.thisPassIndex = index;
2895    if ((isChanged || isOverBorder) && this.isInnerDrag) {
2896      this.flag = flag;
2897      let currentNodeId: number | undefined = this.getData(index)?.getNodeCurrentNodeId();
2898      let currentNodeLevel: number | undefined = this.getData(index)?.getNodeLevel();
2899      if (currentNodeId !== undefined) {
2900        currentNodeLevel = (this.expandAndCollapseInfo.get(currentNodeId) === NodeStatus.EXPAND &&
2901          this.flag === Flag.DOWN_FLAG) ? (currentNodeLevel ? currentNodeLevel + 1 : undefined) : currentNodeLevel;
2902        if (this.lastPassId !== this.INITIAL_INVALID_VALUE && this.loadedNodeIdAndIndexMap.has(this.lastPassId as number)) {
2903          let lastIndex: number = this.loadedNodeIdAndIndexMap.get(this.lastPassId as number) as number;
2904          this.listNode.forEach((value) => {
2905            if (value.getNodeCurrentNodeId() === this.lastPassId) {
2906              value.setCanShowFlagLine(false);
2907            }
2908          })
2909          this.notifyDataChange(lastIndex);
2910        }
2911        if (this.flag === Flag.DOWN_FLAG && index < this.totalCount() - 1) {
2912          this.getData(index)?.setCanShowFlagLine(false);
2913          this.getData(index + 1)?.setCanShowFlagLine(true);
2914          this.getData(index)?.setCanShowBottomFlagLine(false);
2915          this.getData(index + 1)?.setFlagLineLeftMargin(currentNodeLevel);
2916          this.notifyDataChange(index);
2917          this.notifyDataChange(index + 1);
2918          this.lastPassId = this.getData(index + 1)?.getNodeCurrentNodeId();
2919        } else if (this.flag === Flag.UP_FLAG && index < this.totalCount() - 1) {
2920          this.getData(index)?.setCanShowFlagLine(true);
2921          this.getData(index + 1)?.setCanShowFlagLine(false);
2922          this.getData(index)?.setCanShowBottomFlagLine(false);
2923          this.getData(index)?.setFlagLineLeftMargin(currentNodeLevel);
2924          this.notifyDataChange(index);
2925          this.notifyDataChange(index + 1);
2926          this.lastPassId = this.getData(index)?.getNodeCurrentNodeId();
2927        } else if (index >= this.totalCount() - 1) {
2928          if (this.flag === Flag.DOWN_FLAG) {
2929            this.getData(index)?.setCanShowFlagLine(false);
2930            this.getData(index)?.setCanShowBottomFlagLine(true);
2931          } else {
2932            this.getData(index)?.setCanShowFlagLine(true);
2933            this.getData(index)?.setCanShowBottomFlagLine(false);
2934          }
2935          this.getData(index)?.setFlagLineLeftMargin(currentNodeLevel);
2936          this.notifyDataChange(index);
2937          this.lastPassId = this.getData(index)?.getNodeCurrentNodeId();
2938        }
2939      }
2940    }
2941  }
2942
2943  public delayHighLightAndExpandNode(currentIndex: number, currentNodeId: number, showIndex: number): void {
2944    let isChangIndex: boolean = currentIndex !== this.lastDelayExpandIndex ? true : false;
2945    let isOverBorder: boolean | undefined = this.getData(showIndex)?.getIsOverBorder();
2946    if (isOverBorder) {
2947      this.lastDelayExpandIndex = this.INITIAL_INVALID_VALUE;
2948    } else {
2949      this.lastDelayExpandIndex = currentIndex;
2950    }
2951    if (isOverBorder || isChangIndex) {
2952
2953      /* highLight node time-out. */
2954      let canDelayHighLight: boolean | undefined = !isOverBorder && (!this.isInnerDrag ||
2955        (this.expandAndCollapseInfo.get(currentNodeId) === NodeStatus.COLLAPSE && this.isInnerDrag) ||
2956        (!this.expandAndCollapseInfo.has(currentNodeId) && this.listNode[currentIndex].getIsFolder()));
2957      if (canDelayHighLight) {
2958        /* set hoverState color before highLight. */
2959        this.changeNodeColor(currentIndex, this.listNode[currentIndex].getNodeStatus().hover);
2960        this.notifyDataChange(showIndex);
2961
2962        let delayHighLightTime: number = this.isInnerDrag ? 1000 : 0; // ms
2963        this.timeoutHighLightId = setTimeout(() => {
2964          this.delayHighLight(currentIndex);
2965        }, delayHighLightTime);
2966      }
2967      if (isOverBorder || (this.lastTimeoutHighLightId !== this.INITIAL_INVALID_VALUE &&
2968        this.clearTimeoutHighLightId !== this.lastTimeoutHighLightId)) {
2969        clearTimeout(this.lastTimeoutHighLightId);
2970        if (this.lastDelayHighLightIndex !== this.INITIAL_INVALID_VALUE) {
2971          this.clearHighLight(this.lastDelayHighLightIndex);
2972          this.notifyDataReload();
2973        }
2974        this.clearTimeoutHighLightId = this.lastTimeoutHighLightId;
2975      }
2976      this.lastTimeoutHighLightId = this.timeoutHighLightId;
2977      this.lastDelayHighLightIndex = currentIndex;
2978
2979      /* alter flagLine and expand node time-out. */
2980      if (!isOverBorder && this.expandAndCollapseInfo.get(currentNodeId) === NodeStatus.COLLAPSE) {
2981        let firstChildNodeId: number | undefined =
2982          this.getData(showIndex)?.getNodeInfoNode().children[0]?.currentNodeId;
2983        let delayAlterFlagLineAndExpandNodeTime: number = 2000; // ms
2984        this.timeoutExpandId = setTimeout(() => {
2985          this.clearHighLight(this.lastDelayHighLightIndex);
2986          if (firstChildNodeId !== undefined) {
2987            this.alterFlagLineAndExpandNode(currentIndex, firstChildNodeId);
2988          }
2989        }, delayAlterFlagLineAndExpandNodeTime);
2990      }
2991      if (isOverBorder || (this.lastTimeoutExpandId !== this.INITIAL_INVALID_VALUE &&
2992        this.clearTimeoutExpandId !== this.lastTimeoutExpandId)) {
2993        clearTimeout(this.lastTimeoutExpandId);
2994        this.clearTimeoutExpandId = this.lastTimeoutExpandId;
2995      }
2996      this.lastTimeoutExpandId = this.timeoutExpandId;
2997    }
2998  }
2999
3000  public delayHighLight(currentIndex: number): void {
3001    this.listNode.forEach((value) => {
3002      if (value.getNodeCurrentNodeId() === this.lastPassId) {
3003        value.setCanShowFlagLine(false);
3004        value.setCanShowBottomFlagLine(false);
3005        return;
3006      }
3007    })
3008    this.changeNodeColor(currentIndex, this.listNode[currentIndex].getNodeStatus().highLight);
3009    this.listNode[currentIndex].setIsHighLight(true);
3010    this.changeNodeHighLightColor(currentIndex, true);
3011    this.setImageSource(currentIndex, InteractionStatus.DRAG_INSERT);
3012    this.setImageCollapseSource(currentIndex, InteractionStatus.DRAG_INSERT);
3013    this.notifyDataReload();
3014  }
3015
3016  public alterFlagLineAndExpandNode(currentIndex: number, firstChildNodeId: number): void {
3017    this.listNode.forEach((value) => {
3018      if (value.getNodeCurrentNodeId() === this.lastPassId) {
3019        value.setCanShowFlagLine(false);
3020        value.setCanShowBottomFlagLine(false);
3021      }
3022    })
3023    this.listNode.forEach((value) => {
3024      if (this.isInnerDrag && value.getNodeCurrentNodeId() === firstChildNodeId) {
3025        value.setCanShowFlagLine(true);
3026      }
3027    })
3028    this.changeNodeStatus(currentIndex);
3029    this.handleExpandAndCollapse(currentIndex, true);
3030    this.lastPassId = firstChildNodeId;
3031  }
3032
3033  public hideLastLine(): void {
3034    if (this.lastPassId !== this.INITIAL_INVALID_VALUE && this.loadedNodeIdAndIndexMap.has(this.lastPassId as number)) {
3035      this.listNode.forEach((value) => {
3036        if (value.getNodeCurrentNodeId() === this.lastPassId) {
3037          value.setCanShowFlagLine(false);
3038          value.setCanShowBottomFlagLine(false);
3039          return;
3040        }
3041      })
3042      let index: number = this.loadedNodeIdAndIndexMap.get(this.lastPassId as number) as number;
3043      this.notifyDataChange(index);
3044    }
3045  }
3046
3047  public clearLastTimeoutHighLight(): void {
3048    if (this.lastTimeoutHighLightId !== this.INITIAL_INVALID_VALUE &&
3049      this.clearTimeoutHighLightId !== this.lastTimeoutHighLightId) {
3050      clearTimeout(this.lastTimeoutHighLightId);
3051      if (this.lastDelayHighLightIndex !== this.INITIAL_INVALID_VALUE) {
3052        this.clearHighLight(this.lastDelayHighLightIndex);
3053      }
3054    }
3055  }
3056
3057  public clearLastTimeoutExpand(): void {
3058    if (this.lastTimeoutExpandId !== this.INITIAL_INVALID_VALUE &&
3059      this.clearTimeoutExpandId !== this.lastTimeoutExpandId) {
3060      clearTimeout(this.lastTimeoutExpandId);
3061    }
3062  }
3063
3064  public getSubtitle(currentNodeId: number): string | undefined {
3065    if (this.nodeIdAndSubtitleMap.has(currentNodeId)) {
3066      if (typeof this.nodeIdAndSubtitleMap.get(currentNodeId) === 'number') {
3067        return this.nodeIdAndSubtitleMap.get(currentNodeId)?.toString();
3068      } else {
3069        return this.nodeIdAndSubtitleMap.get(currentNodeId) as string;
3070      }
3071    } else {
3072      return '';
3073    }
3074  }
3075
3076  public hasSubtitle(currentNodeId: number): boolean {
3077    return this.nodeIdAndSubtitleMap.has(currentNodeId);
3078  }
3079
3080  public initialParameterAboutDelayHighLightAndExpandIndex(): void {
3081    this.lastDelayHighLightIndex = this.INITIAL_INVALID_VALUE;
3082    this.lastDelayExpandIndex = this.INITIAL_INVALID_VALUE;
3083    this.lastPassIndex = this.INITIAL_INVALID_VALUE;
3084    this.draggingCurrentNodeId = this.INITIAL_INVALID_VALUE;
3085    this.flag = Flag.NONE;
3086  }
3087
3088  public refreshSubtitle(insertNodeCurrentNodeId: number): void {
3089    this.nodeIdAndSubtitleMap.set(this.selectedParentNodeId, this.selectedParentNodeSubtitle);
3090    this.nodeIdAndSubtitleMap.set(insertNodeCurrentNodeId, this.insertNodeSubtitle);
3091    this.notifyDataChange(this.loadedNodeIdAndIndexMap.get(this.selectedParentNodeId));
3092    this.notifyDataChange(this.loadedNodeIdAndIndexMap.get(insertNodeCurrentNodeId));
3093  }
3094
3095  public setNodeSubtitlePara(
3096    selectedParentNodeId: number,
3097    selectedParentNodeSubtitle: ResourceStr,
3098    insertNodeSubtitle: ResourceStr): void {
3099    this.selectedParentNodeId = selectedParentNodeId;
3100    this.selectedParentNodeSubtitle = selectedParentNodeSubtitle;
3101    this.insertNodeSubtitle = insertNodeSubtitle;
3102  }
3103
3104  public getInsertNodeSubtitle(): ResourceStr {
3105    return this.insertNodeSubtitle;
3106  }
3107
3108  public getExpandAndCollapseInfo(currentNodeId: number): NodeStatus | undefined {
3109    return this.expandAndCollapseInfo.get(currentNodeId);
3110  }
3111
3112  public getLastDelayHighLightId(): number {
3113    return this.lastDelayHighLightId;
3114  }
3115
3116  public setLastDelayHighLightId(): void {
3117    this.listNode.forEach((value, index) => {
3118      if (index === this.lastDelayHighLightIndex) {
3119        this.lastDelayHighLightId = value.getNodeCurrentNodeId();
3120      }
3121    })
3122  }
3123
3124  public setLastPassId(lastPassId: number): void {
3125    this.lastPassId = lastPassId;
3126  }
3127
3128  public setLastDelayHighLightIndex(lastDelayHighLightIndex: number): void {
3129    this.lastDelayHighLightIndex = lastDelayHighLightIndex;
3130  }
3131
3132  /**
3133   * Alter the current node location to a needful position.
3134   * 1.Create an array named 'dragNodeParam' to store dragging node information.
3135   * 2.Delete the dragging node from the tree.
3136   * 3.Add the dragging node to the tree.
3137   */
3138  public alterDragNode(rearParentNodeId: number, rearCurrentNodeId: number,
3139    dragParentNodeId: number, dragCurrentNodeId: number, frontNodeInfoItem: NodeInfo): void {
3140    let dragNodeParam: DragNodeParam[] = [];
3141    let parentNodeId: number = rearParentNodeId;
3142    let currentNodeId: number = dragCurrentNodeId;
3143    let nodeParam: NodeParam = frontNodeInfoItem.getNodeInfoData();
3144    let nodeInfo: NodeInfo | null = null;
3145    let nodeInfoNode: NodeItem = frontNodeInfoItem.getNodeInfoNode();
3146    let isHighLight: boolean = false;
3147    let insertChildIndex: number = this.INITIAL_INVALID_VALUE;
3148    let currentChildIndex: number = this.INITIAL_INVALID_VALUE;
3149    let isDownFlag: boolean = this.flag === Flag.DOWN_FLAG ? true : false;
3150
3151    currentChildIndex = this.getChildIndex(dragParentNodeId, dragCurrentNodeId);
3152
3153    insertChildIndex = this.getChildIndex(rearParentNodeId, rearCurrentNodeId) + 1;
3154
3155    if (rearParentNodeId !== dragParentNodeId) {
3156      insertChildIndex = isDownFlag ? insertChildIndex + 1 : insertChildIndex;
3157    } else {
3158      if (insertChildIndex > currentChildIndex) {
3159        insertChildIndex = isDownFlag ? insertChildIndex : insertChildIndex - 1;
3160      } else {
3161        insertChildIndex = isDownFlag ? insertChildIndex + 1 : insertChildIndex;
3162      }
3163    }
3164
3165    for (let i: number = 0; i < this.listNode.length; i++) {
3166      if (this.listNode[i].getNodeCurrentNodeId() === rearCurrentNodeId) {
3167        isHighLight = this.listNode[i].getIsHighLight();
3168        if (this.flag === Flag.DOWN_FLAG && this.expandAndCollapseInfo.get(rearCurrentNodeId) === NodeStatus.EXPAND) {
3169          parentNodeId = rearCurrentNodeId;
3170          insertChildIndex = 0;
3171        } else if (this.flag === Flag.UP_FLAG && this.expandAndCollapseInfo.get(rearCurrentNodeId) ===
3172        NodeStatus.EXPAND &&
3173          this.listNode[i].getCanShowFlagLine() === false) {
3174          parentNodeId = rearCurrentNodeId;
3175          insertChildIndex = 0;
3176        } else if (isHighLight) {
3177          parentNodeId = rearCurrentNodeId;
3178          insertChildIndex = 0;
3179        }
3180        break;
3181      }
3182    }
3183
3184    let callbackParam: CallbackParam = {
3185      currentNodeId: currentNodeId,
3186      parentNodeId: parentNodeId,
3187      childIndex: insertChildIndex,
3188    }
3189
3190    /* export inner drag node Id. */
3191    this.appEventBus.emit(TreeListenType.NODE_MOVE, callbackParam);
3192
3193    /* To store dragging node information by the array named 'dragNodeParam'. */
3194    dragNodeParam.push({ parentId: parentNodeId, currentId: currentNodeId, data: nodeParam });
3195
3196    let callback: (node: NodeItem, listNode: NodeInfo[]) => boolean = (node: NodeItem, listNode: NodeInfo[]): boolean => {
3197      if (node) {
3198        parentNodeId = node.parentNodeId;
3199        currentNodeId = node.currentNodeId;
3200        for (let i: number = 0; i < listNode.length; i++) {
3201          if (listNode[i].getNodeCurrentNodeId() === currentNodeId) {
3202            nodeInfo = listNode[i];
3203            break;
3204          }
3205        }
3206        if (nodeInfo === null) {
3207          return false;
3208        }
3209        let nodeParam: NodeParam = nodeInfo.getNodeInfoData();
3210        if (parentNodeId !== dragParentNodeId) {
3211          dragNodeParam.push({ parentId: parentNodeId, currentId: currentNodeId, data: nodeParam });
3212        }
3213        return false;
3214      }
3215      return false;
3216    }
3217    this.dragTraverseNodeDF(callback, nodeInfoNode, this.listNode);
3218
3219    /* Delete the dragging node from the tree. */
3220    let removeNodeIdList: number[] = this.removeNode(dragCurrentNodeId, dragParentNodeId);
3221    if (removeNodeIdList.length === 0) {
3222      return;
3223    }
3224
3225    /**
3226     * Add the dragging node to the tree
3227     * 1.The first dragging node is added singly, because it needs to distinguish the position to insert
3228     *
3229     * Add first node.
3230     */
3231    let insertCurrentNodeId: number = rearCurrentNodeId;
3232    let isAfter: boolean = isDownFlag;
3233    if (this.expandAndCollapseInfo.get(rearCurrentNodeId) === NodeStatus.EXPAND) {
3234      isAfter = false;
3235      this.listNode.forEach((value) => {
3236        if (value.getNodeCurrentNodeId() === rearCurrentNodeId && value.getCanShowFlagLine() === false) {
3237          if (value.getNodeInfoNode().children.length) {
3238            insertCurrentNodeId = value.getNodeInfoNode().children[0].currentNodeId;
3239          } else {
3240            insertCurrentNodeId = this.INITIAL_INVALID_VALUE;
3241          }
3242        }
3243      })
3244    } else if (!this.expandAndCollapseInfo.get(rearCurrentNodeId) && isHighLight) {
3245      this.expandAndCollapseInfo.set(rearCurrentNodeId, NodeStatus.EXPAND);
3246    }
3247
3248    let addDragNodeResult: boolean =
3249      this.addDragNode(dragNodeParam[0].parentId, dragNodeParam[0].currentId, insertCurrentNodeId,
3250        isAfter, dragNodeParam[0].data);
3251
3252    if (!addDragNodeResult) {
3253      return;
3254    }
3255    /* Add remaining node. */
3256    for (let j: number = 1; j < dragNodeParam.length; j++) {
3257      let addNodeResult: boolean =
3258        this.addNode(dragNodeParam[j].parentId, dragNodeParam[j].currentId, dragNodeParam[j].data, false);
3259      if (!addNodeResult) {
3260        return;
3261      }
3262    }
3263
3264    /* Update node data and reload the array named 'listNode'. */
3265    for (let i: number = 0; i < this.listNode.length; i++) {
3266      if (this.listNode[i].getNodeCurrentNodeId() === dragParentNodeId) {
3267        if (this.listNode[i].getNodeItem().imageCollapse === null) {
3268          this.listNode[i].handleImageCollapseAfterAddNode(false);
3269          this.expandAndCollapseInfo.delete(dragParentNodeId);
3270          break;
3271        }
3272      }
3273    }
3274    let tmp: NodeInfo[] = [...this.listNode];
3275    this.reloadListNode(tmp);
3276  }
3277
3278  /**
3279   * Reload the array named 'listNode'
3280   * @param tmp
3281   */
3282  public reloadListNode(tmp: NodeInfo[]): void {
3283    let index: number = 0;
3284    let listIndex: number = 0;
3285    this.listNode.splice(0, this.listNode.length);
3286    this.loadedNodeIdAndIndexMap.clear();
3287    this.loadedListNode.splice(0, this.loadedListNode.length);
3288    this.traverseNodeDF((node: NodeItem): boolean => {
3289      let currentNodeId: number = node.currentNodeId;
3290      if (currentNodeId >= 0) {
3291        if (this.nodeIdNodeParamMap.has(currentNodeId)) {
3292          let nodeInfo: NodeInfo = new NodeInfo(node, this.nodeIdNodeParamMap.get(currentNodeId) as NodeParam);
3293          nodeInfo.addImageCollapse(node.getChildNodeInfo().isHasChildNode);
3294          this.listNode.push(nodeInfo);
3295          this.nodeIdAndNodeIndexMap.set(nodeInfo.getNodeCurrentNodeId(), listIndex++);
3296          if (this.expandAndCollapseInfo.get(currentNodeId) === NodeStatus.EXPAND) {
3297            nodeInfo.getNodeItem()
3298              .imageCollapse = CollapseImageNodeFlyweightFactory.changeImageCollapseSource(NodeStatus.EXPAND,
3299              nodeInfo.getNodeItem().imageCollapse?.isCollapse);
3300          } else if (this.expandAndCollapseInfo.get(currentNodeId) === NodeStatus.COLLAPSE) {
3301            nodeInfo.getNodeItem()
3302              .imageCollapse = CollapseImageNodeFlyweightFactory.changeImageCollapseSource(NodeStatus.COLLAPSE,
3303              nodeInfo.getNodeItem().imageCollapse?.isCollapse);
3304          }
3305
3306          for (let i: number = 0; i < tmp.length; i++) {
3307            if (tmp[i].getNodeCurrentNodeId() === nodeInfo.getNodeCurrentNodeId()) {
3308              nodeInfo.setNodeIsShow(tmp[i].getNodeIsShow());
3309              nodeInfo.setListItemHeight(tmp[i].getListItemHeight());
3310              if (nodeInfo.getNodeItem().mainTitleNode && nodeInfo.getIsShowTitle()) {
3311                nodeInfo.getNodeItem().mainTitleNode.title = tmp[i].getNodeItem().mainTitleNode?.title as string;
3312              }
3313              break;
3314            }
3315          }
3316          if (nodeInfo.getNodeIsShow()) {
3317            this.loadedNodeIdAndIndexMap.set(nodeInfo.getNodeCurrentNodeId(), index++);
3318            this.loadedListNode.push(nodeInfo);
3319          }
3320        }
3321      }
3322      return false;
3323    });
3324  }
3325
3326  public getFlagLine(): FlagLine {
3327    return this.FLAG_LINE;
3328  }
3329
3330  public getVisibility(nodeInfo: NodeInfo): Visibility {
3331    let lastShowIndex: number = this.loadedNodeIdAndIndexMap.get(nodeInfo.getNodeCurrentNodeId()) as number - 1;
3332    if (lastShowIndex > this.INITIAL_INVALID_VALUE) {
3333      let lastNodeInfo: NodeInfo | undefined = this.getData(lastShowIndex);
3334      return (nodeInfo.getCanShowFlagLine() === true && !nodeInfo.getIsHighLight() && !lastNodeInfo?.getIsHighLight()) ?
3335      Visibility.Visible : Visibility.Hidden;
3336    } else {
3337      return (nodeInfo.getCanShowFlagLine() === true && !nodeInfo.getIsHighLight()) ?
3338      Visibility.Visible : Visibility.Hidden;
3339    }
3340  }
3341
3342  public getSubTitlePara(): SubTitleStyle {
3343    return this.subTitle;
3344  }
3345
3346  public getIsFolder(nodeId: number): boolean | undefined {
3347    if (this.loadedNodeIdAndIndexMap.has(nodeId)) {
3348      return this.getData(this.loadedNodeIdAndIndexMap.get(nodeId) as number)?.getIsFolder();
3349    }
3350    return false;
3351  }
3352
3353  public getSubTitleFontColor(isHighLight: boolean): ResourceColor {
3354    return isHighLight ? this.subTitle.highLightFontColor : this.treeViewTheme.secondaryTitleFontColor;
3355  }
3356
3357  private getChildIndex(rearParentNodeId: number, rearCurrentNodeId: number): number {
3358    let insertChildIndex: number = this.INITIAL_INVALID_VALUE;
3359    if (this.nodeIdNodeItemMap.has(rearParentNodeId)) {
3360      let node: NodeItem = this.nodeIdNodeItemMap.get(rearParentNodeId) as NodeItem;
3361      if (node.getCurrentNodeId() === rearParentNodeId) {
3362        node.children.forEach((value, index) => {
3363          if (value.getCurrentNodeId() === rearCurrentNodeId) {
3364            insertChildIndex = index;
3365            return;
3366          }
3367        })
3368      }
3369    }
3370    return insertChildIndex;
3371  }
3372
3373  public setCurrentFocusNodeId(focusNodeId: number): void {
3374    this.currentFocusNodeId = focusNodeId;
3375  }
3376
3377  public getCurrentFocusNodeId(): number {
3378    return this.currentFocusNodeId;
3379  }
3380
3381  public setLastFocusNodeId(focusNodeId: number): void {
3382    this.lastFocusNodeId = focusNodeId;
3383  }
3384
3385  public getLastFocusNodeId(): number {
3386    return this.lastFocusNodeId;
3387  }
3388
3389  public getAddFocusNodeId(): number {
3390    return this.addFocusNodeId;
3391  }
3392
3393  public setFlag(flag: Flag): void {
3394    this.flag = flag;
3395  }
3396
3397  private traverseNodeDF(callback: (currentNode: NodeItem) => boolean, root: NodeItem = this._root): void {
3398    let stack: NodeItem[] = [];
3399    let found: boolean = false;
3400    stack.unshift(root);
3401    let currentNode: NodeItem = stack.shift() as NodeItem;
3402    while (!found && currentNode) {
3403      found = callback(currentNode) === true;
3404      if (!found) {
3405        stack.unshift(...currentNode.children);
3406        currentNode = stack.shift() as NodeItem;
3407      }
3408    }
3409  }
3410
3411  private traverseSectionNodeDF(callback: (currentNode: NodeItem) => boolean, root: NodeItem = this._root,
3412    startLevel?: number, endLevel?: number): void {
3413    let stack: NodeItem[] = [];
3414    let found: boolean = false;
3415    let isPassNode: boolean = false;
3416    stack.unshift(root);
3417    let currentNode: NodeItem = stack.shift() as NodeItem;
3418    while (!found && currentNode) {
3419      try {
3420        if (startLevel !== undefined && currentNode.nodeLevel < startLevel) {
3421          isPassNode = true;
3422        }
3423        if (endLevel !== undefined && currentNode.nodeLevel > endLevel) {
3424          isPassNode = true;
3425        }
3426        if (!isPassNode) {
3427          found = callback(currentNode);
3428        }
3429      } catch (err) {
3430        throw new Error('traverseSectionNodeDF function callbacks error');
3431      }
3432      if (!found) {
3433        stack.unshift(...currentNode.children);
3434        currentNode = stack.shift() as NodeItem;
3435        isPassNode = false;
3436      }
3437    }
3438  }
3439
3440  private updateParentChildNum(parentNode: NodeItem, isAdd: boolean, count: number): void {
3441    let parentNodeId: number = parentNode.parentNodeId;
3442    while (parentNodeId >= 0) {
3443      if (this.nodeIdNodeItemMap.has(parentNodeId)) {
3444        let parent: NodeItem = this.nodeIdNodeItemMap.get(parentNodeId) as NodeItem;
3445        parent.getChildNodeInfo().allChildNum =
3446          isAdd ? parent.getChildNodeInfo().allChildNum + count : parent.getChildNodeInfo().allChildNum - count;
3447        parentNodeId = parent.parentNodeId;
3448      } else {
3449        hilog.error(LOG_CODE, TAG, 'updateParentChildNum: parent node not found.');
3450        break;
3451      }
3452    }
3453  }
3454
3455  /**
3456   * find parent node id
3457   *
3458   * @param currentNodeId current node id
3459   * @returns parent node id
3460   */
3461  public findParentNodeId(currentNodeId: number): number {
3462    let current: NodeItem = new NodeItem(emptyNodeInfo);
3463    if (this.nodeIdNodeItemMap.has(currentNodeId)) {
3464      current = this.nodeIdNodeItemMap.get(currentNodeId) as NodeItem;
3465    }
3466    return current.parentNodeId;
3467  }
3468
3469  /**
3470   * add nodeItem in params
3471   *
3472   * @param parentNodeId parent node id
3473   * @param currentNodeId current node id
3474   * @param data  node param
3475   * @param initBuild whether in initialization process
3476   */
3477  public addNode(parentNodeId: number,
3478    currentNodeId: number,
3479    data: NodeParam, initBuild: boolean): boolean {
3480    if (this._root === null) {
3481      this._root = new NodeItem(emptyNodeInfo);
3482      this._root.nodeLevel = -1;
3483      this.nodeIdNodeItemMap.set(-1, this._root);
3484      this.nodeIdNodeParamMap.set(-1, emptyNodeInfo);
3485    }
3486    if (this.nodeIdNodeItemMap.has(parentNodeId)) {
3487      let parent: NodeItem = this.nodeIdNodeItemMap.get(parentNodeId) as NodeItem;
3488      let currentNode: NodeItem = new NodeItem(data);
3489      if (parent.nodeLevel > this.maxNodeLevel) {
3490        hilog.error(LOG_CODE, TAG, 'ListDataSource[addNode]: The level of the tree view cannot exceed 50.');
3491        return false;
3492      }
3493      currentNode.nodeLevel = parent.nodeLevel + 1;
3494      currentNode.parentNodeId = parentNodeId;
3495      currentNode.currentNodeId = currentNodeId;
3496      currentNode.indexOfParent = parent.children.length;
3497      data.parentNodeId = parentNodeId;
3498      data.currentNodeId = currentNodeId;
3499      parent.children.push(currentNode);
3500      parent.getChildNodeInfo().isHasChildNode = true;
3501      parent.getChildNodeInfo().childNum = parent.children.length;
3502      parent.getChildNodeInfo().allChildNum += 1;
3503      if (initBuild) {
3504        this.updateNodeIdList.push(parent.parentNodeId);
3505      } else {
3506        let updateNodeIdList: number[] = [];
3507        updateNodeIdList.push(parent.parentNodeId);
3508        delayUpdateParentChildNum(true, 1, this.nodeIdNodeItemMap, updateNodeIdList);
3509      }
3510      this.nodeIdNodeParamMap.set(currentNodeId, data);
3511      this.nodeIdNodeItemMap.set(currentNodeId, currentNode);
3512      return true;
3513    } else {
3514      hilog.error(LOG_CODE, TAG, 'ListDataSource[addNode]: Parent node not found.');
3515      return false;
3516    }
3517  }
3518
3519  private freeNodeMemory(rootNode: NodeItem, removeNodeIdList: number[]): void {
3520    let deleteNode: NodeItem[] = [];
3521    let callback = (node: NodeItem): boolean => {
3522      deleteNode.push(node);
3523      return false;
3524    };
3525    this.traverseNodeDF(callback, rootNode);
3526    deleteNode.forEach((value: NodeItem) => {
3527      removeNodeIdList.push(value.getCurrentNodeId());
3528      this.nodeIdNodeItemMap.delete(value.getCurrentNodeId());
3529      this.nodeIdNodeParamMap.delete(value.getCurrentNodeId());
3530      value = new NodeItem(emptyNodeInfo);
3531    })
3532  }
3533
3534  /**
3535   * remove node
3536   *
3537   * @param currentNodeId current node id
3538   * @param parentNodeId  parent node id
3539   * @returns node id list which is removed
3540   */
3541  public removeNode(currentNodeId: number, parentNodeId: number): number[] {
3542    if (this.nodeIdNodeItemMap.has(parentNodeId) && this.nodeIdNodeItemMap.has(currentNodeId)) {
3543      let parent: NodeItem = this.nodeIdNodeItemMap.get(parentNodeId) as NodeItem;
3544      let current: NodeItem = this.nodeIdNodeItemMap.get(currentNodeId) as NodeItem;
3545      let removeNodeIdList: number[] = [];
3546      let index: number = current.indexOfParent;
3547      let deleteNodeAllChildNum: number = 0;
3548      if (index < 0) {
3549        hilog.error(LOG_CODE, TAG, 'node does not exist.');
3550        return [];
3551      } else {
3552        deleteNodeAllChildNum = parent.children[index].getChildNodeInfo().allChildNum + 1;
3553        this.freeNodeMemory(parent.children[index], removeNodeIdList);
3554        for (let i: number = index; i < parent.children.length; i++) {
3555          parent.children[i].indexOfParent -= 1;
3556        }
3557        let node: NodeItem[] | null = parent.children.splice(index, 1);
3558        node = null;
3559        if (parent.children.length === 0) {
3560          if (this.nodeIdAndNodeIndexMap.has(parentNodeId)) {
3561            let parentIndex: number = this.nodeIdAndNodeIndexMap.get(parentNodeId) as number;
3562            this.listNode[parentIndex]?.addImageCollapse(false);
3563          }
3564        }
3565      }
3566      parent.getChildNodeInfo().childNum = parent.children.length;
3567      parent.getChildNodeInfo().allChildNum -= (deleteNodeAllChildNum);
3568      let updateNodeIdList: number[] = [];
3569      updateNodeIdList.push(parent.parentNodeId);
3570      delayUpdateParentChildNum(false, deleteNodeAllChildNum, this.nodeIdNodeItemMap, updateNodeIdList);
3571      return removeNodeIdList;
3572    } else {
3573      hilog.error(LOG_CODE, TAG, 'parent does not exist.');
3574      return [];
3575    }
3576  }
3577
3578  private getNodeInfoByNodeItem(nodeItem: NodeItem): NodeInfo {
3579    if (nodeItem?.currentNodeId === undefined) {
3580      hilog.error(LOG_CODE, TAG, 'getNodeInfoByNodeItem: currentId is undefined');
3581      return new NodeInfo(new NodeItem(emptyNodeInfo), emptyNodeInfo);
3582    }
3583    if (!this.nodeIdAndNodeIndexMap.has(nodeItem.currentNodeId)) {
3584      hilog.error(LOG_CODE, TAG, 'getNodeInfoByNodeItem: not has nodeItem.');
3585      return new NodeInfo(new NodeItem(emptyNodeInfo), emptyNodeInfo);
3586    }
3587    let index: number = this.nodeIdAndNodeIndexMap.get(nodeItem.currentNodeId) as number;
3588    return this.listNode[index];
3589  }
3590
3591  /**
3592   * get node param by node id
3593   * @param nodeId node id
3594   * @returns node param
3595   */
3596  public getNewNodeParam(nodeId: number): NodeParam {
3597    let parent: NodeItem = new NodeItem(emptyNodeInfo);
3598    if (this.nodeIdNodeItemMap.has(nodeId)) {
3599      parent = this.nodeIdNodeItemMap.get(nodeId) as NodeItem;
3600    }
3601    let newNodeParam: NodeParam = emptyNodeInfo;
3602    if (parent) {
3603      let nodeInfo: NodeInfo = this.getNodeInfoByNodeItem(parent);
3604      if (parent.children.length === 0) {
3605        if (nodeInfo.getNodeItem().imageNode !== undefined) {
3606          newNodeParam.icon = nodeInfo.getNodeItem().imageNode?.normalSource;
3607          newNodeParam.selectedIcon = nodeInfo.getNodeItem().imageNode?.selectedSource;
3608          newNodeParam.editIcon = nodeInfo.getNodeItem().imageNode?.editSource;
3609          newNodeParam.container = nodeInfo.getMenu();
3610        } else {
3611          newNodeParam.icon = undefined;
3612          newNodeParam.selectedIcon = undefined;
3613          newNodeParam.editIcon = undefined;
3614          newNodeParam.container = nodeInfo.getMenu();
3615        }
3616      } else if (parent.children.length > 0) {
3617        let childNodeInfo: NodeInfo = this.getNodeInfoByNodeItem(parent.children[0]);
3618        if (nodeInfo.getNodeItem().imageNode !== null) {
3619          newNodeParam.icon = (childNodeInfo.getNodeItem().imageNode !== undefined) ?
3620            childNodeInfo.getNodeItem().imageNode?.normalSource : undefined;
3621          newNodeParam.selectedIcon = (childNodeInfo.getNodeItem().imageNode !== undefined) ?
3622            childNodeInfo.getNodeItem().imageNode?.selectedSource : undefined;
3623          newNodeParam.editIcon = (childNodeInfo.getNodeItem().imageNode !== undefined) ?
3624            childNodeInfo.getNodeItem().imageNode?.editSource : undefined;
3625          newNodeParam.container = childNodeInfo.getMenu();
3626        } else {
3627          newNodeParam.icon = undefined;
3628          newNodeParam.selectedIcon = undefined;
3629          newNodeParam.editIcon = undefined;
3630          newNodeParam.container = childNodeInfo.getMenu();
3631        }
3632      }
3633    }
3634    return newNodeParam;
3635  }
3636
3637  /**
3638   * get child node ids by node id
3639   *
3640   * @param nodeId node id
3641   * @returns child node ids
3642   */
3643  public getClickChildId(nodeId: number): number[] {
3644    let parent: NodeItem = new NodeItem(emptyNodeInfo);
3645    if (this.nodeIdNodeItemMap.has(nodeId)) {
3646      parent = this.nodeIdNodeItemMap.get(nodeId) as NodeItem;
3647    }
3648    if (parent) {
3649      if (parent.children.length === 0) {
3650        return [];
3651      } else if (parent.children.length > 0) {
3652        let childrenNodeInfo: number[] = new Array(parent.children.length);
3653        for (let i: number = 0; i < childrenNodeInfo.length; i++) {
3654          childrenNodeInfo[i] = 0;
3655        }
3656        for (let i: number = 0; i < parent.children.length && i < childrenNodeInfo.length; i++) {
3657          childrenNodeInfo[i] = parent.children[i].currentNodeId;
3658        }
3659        return childrenNodeInfo;
3660      }
3661    }
3662    return [];
3663  }
3664
3665  /**
3666   * get child nodeInfo views by node id
3667   *
3668   * @param nodeId node id
3669   * @returns child nodeInfo views
3670   */
3671  public getClickNodeChildrenInfo(nodeId: number): NodeInfoView[] {
3672    let parent: NodeItem = new NodeItem(emptyNodeInfo);
3673    if (this.nodeIdNodeItemMap.has(nodeId)) {
3674      parent = this.nodeIdNodeItemMap.get(nodeId) as NodeItem;
3675    }
3676    if (parent) {
3677      if (parent.children.length === 0) {
3678        return [];
3679      } else if (parent.children.length > 0) {
3680        let childrenNodeInfo: NodeInfoView[] = new Array(parent.children.length);
3681        for (let i: number = 0; i < childrenNodeInfo.length; i++) {
3682          childrenNodeInfo[i] = {};
3683        }
3684        for (let i: number = 0; i < parent.children.length && i < childrenNodeInfo.length; i++) {
3685          childrenNodeInfo[i].itemId = parent.children[i].currentNodeId;
3686          let nodeInfo: NodeInfo = this.getNodeInfoByNodeItem(parent.children[i]);
3687          if (nodeInfo.getNodeItem().imageNode) {
3688            childrenNodeInfo[i].itemIcon = nodeInfo.getNodeItem().imageNode?.source;
3689          }
3690          if (nodeInfo.getNodeItem().mainTitleNode) {
3691            childrenNodeInfo[i].itemTitle = nodeInfo.getNodeItem().mainTitleNode?.title;
3692          }
3693          childrenNodeInfo[i].isFolder = nodeInfo.getIsFolder();
3694        }
3695        return childrenNodeInfo;
3696      }
3697    }
3698    return [];
3699  }
3700
3701  /**
3702   * check main title is valid
3703   *
3704   * @param title main title
3705   * @returns check result
3706   */
3707  public checkMainTitleIsValid(title: string): boolean {
3708    if (new RegExp('/[\\\/:*?"<>|]/').test(title)) {
3709      return false;
3710    }
3711    if ((new RegExp('/^[\u4e00-\u9fa5]+$/').test(title) && title.length > this.MAX_CN_LENGTH) ||
3712      (!new RegExp('/^[\u4e00-\u9fa5]+$/').test(title) && title.length > this.MAX_EN_LENGTH)) {
3713      return false;
3714    }
3715    return true;
3716  }
3717
3718  /**
3719   * DFS: Depth first traversal in drag event.
3720   *
3721   * @param callback dfs callback fuction
3722   */
3723  dragTraverseNodeDF(callback: (node: NodeItem, listNode: NodeInfo[]) => boolean,
3724    root: NodeItem = this._root, listNode: NodeInfo[]): void {
3725    let stack: NodeItem[] = [];
3726    let found: boolean = false;
3727    stack.unshift(root);
3728    let currentNode: NodeItem = stack.shift() as NodeItem;
3729    while (!found && currentNode) {
3730      found = callback(currentNode, listNode) === true;
3731      if (!found) {
3732        stack.unshift(...currentNode.children);
3733        currentNode = stack.shift() as NodeItem;
3734      }
3735    }
3736  }
3737
3738  private updateChildIndexOfParent(insertIndex: number, parent: NodeItem): void {
3739    for (let i: number = insertIndex; i < parent.children.length; i++) {
3740      parent.children[i].indexOfParent += 1;
3741    }
3742  }
3743
3744  /**
3745   * Add the first dragging node in dragging nodes
3746   * 1.the first dragging node needs to distinguish the position to insert
3747   */
3748  private addDragNode(parentNodeId: number,
3749    currentNodeId: number,
3750    insertCurrentNodeId: number,
3751    isAfter: boolean,
3752    data: NodeParam): boolean {
3753
3754    if (this._root === null) {
3755      this._root = new NodeItem(emptyNodeInfo);
3756      this._root.nodeLevel = this.INITIAL_INVALID_VALUE;
3757    }
3758
3759    if (this.nodeIdNodeItemMap.has(parentNodeId)) {
3760      let parent: NodeItem = this.nodeIdNodeItemMap.get(parentNodeId) as NodeItem;
3761      let currentNode: NodeItem = new NodeItem(data);
3762      if (parent.nodeLevel > this.maxNodeLevel) {
3763        hilog.error(LOG_CODE, TAG, 'addDragNode: The level of the tree view cannot exceed 50.');
3764        return false;
3765      }
3766      currentNode.nodeLevel = parent.nodeLevel + 1;
3767      currentNode.parentNodeId = parentNodeId;
3768      currentNode.currentNodeId = currentNodeId;
3769      data.parentNodeId = parentNodeId;
3770      data.currentNodeId = currentNodeId;
3771      let insertIndex: number = this.INITIAL_INVALID_VALUE;
3772      if (parent.children.length) {
3773        for (let i: number = 0; i < parent.children.length; i++) {
3774          if (parent.children[i].getCurrentNodeId() === insertCurrentNodeId) {
3775            insertIndex = i;
3776            break;
3777          }
3778        }
3779        if (isAfter) {
3780          currentNode.indexOfParent = insertIndex + 1;
3781          this.updateChildIndexOfParent(currentNode.indexOfParent, parent);
3782          parent.children.splice(insertIndex + 1, 0, currentNode);
3783        } else {
3784          currentNode.indexOfParent = insertIndex < 0 ? parent.children.length + insertIndex : insertIndex;
3785          this.updateChildIndexOfParent(currentNode.indexOfParent, parent);
3786          parent.children.splice(insertIndex, 0, currentNode);
3787        }
3788      } else {
3789        currentNode.indexOfParent = parent.children.length;
3790        parent.children.push(currentNode);
3791      }
3792      parent.getChildNodeInfo().isHasChildNode = true;
3793      parent.getChildNodeInfo().childNum = parent.children.length;
3794      parent.getChildNodeInfo().allChildNum += 1;
3795      this.updateParentChildNum(parent, true, 1);
3796      this.nodeIdNodeItemMap.set(currentNodeId, currentNode);
3797      this.nodeIdNodeParamMap.set(currentNodeId, data);
3798      return true;
3799    } else {
3800      hilog.error(LOG_CODE, TAG, 'addDragNode: Parent node not found.');
3801      return false;
3802    }
3803  }
3804}
3805
3806@Component
3807export struct TreeViewInner {
3808  @ObjectLink item: NodeInfo;
3809  listNodeDataSource: ListNodeDataSource = new ListNodeDataSource();
3810  @State columnWidth: number = 0;
3811  @State isFocused: boolean = false;
3812  @State index: number = -1;
3813  @State lastIndex: number = -1;
3814  @State count: number = 0;
3815  @State followingSystemFontScale: boolean = false;
3816  @State maxAppFontScale: number = 1;
3817  @Consume treeViewTheme: TreeViewTheme;
3818  @BuilderParam private listTreeViewMenu: () => void;
3819  private readonly MAX_CN_LENGTH: number = 254;
3820  private readonly MAX_EN_LENGTH: number = 255;
3821  private readonly INITIAL_INVALID_VALUE = -1;
3822  private readonly MAX_TOUCH_DOWN_COUNT = 0;
3823  private isMultiPress: boolean = false;
3824  private touchDownCount: number = this.INITIAL_INVALID_VALUE;
3825  private appEventBus: TreeListener = TreeListenerManager.getInstance().getTreeListener();
3826  private readonly itemPadding: ItemPadding = {
3827    left: $r('sys.float.ohos_id_card_margin_start'),
3828    right: $r('sys.float.ohos_id_card_margin_end'),
3829    top: $r('sys.float.ohos_id_text_margin_vertical'),
3830    bottom: $r('sys.float.padding_level0'),
3831  };
3832  private readonly textInputPadding: ItemPadding = {
3833    left: $r('sys.float.padding_level0'),
3834    right: $r('sys.float.padding_level0'),
3835    top: $r('sys.float.padding_level0'),
3836    bottom: $r('sys.float.padding_level0') };
3837  private inputFontSize: number = resourceManager.getSystemResourceManager().getNumberByName('ohos_id_text_size_body1');
3838
3839  aboutToAppear(): void {
3840    if (this.item.getNodeItem().imageNode) {
3841      this.item.imageSource = this.item.getNodeItem().imageNode?.source;
3842    }
3843    let uiContent: UIContext = this.getUIContext();
3844    this.followingSystemFontScale = uiContent.isFollowingSystemFontScale();
3845    this.maxAppFontScale = uiContent.getMaxFontScale();
3846  }
3847
3848  decideFontScale() {
3849    let uiContent: UIContext = this.getUIContext();
3850    let systemFontScale = (uiContent.getHostContext() as common.UIAbilityContext)?.config?.fontSizeScale ?? 1;
3851    if (!this.followingSystemFontScale) {
3852      return 1;
3853    }
3854    return Math.min(systemFontScale, this.maxAppFontScale, MAX_FONT_SCALE)
3855  }
3856
3857  getInputTextMaxFontSize() {
3858    let inputTextMaxFontSize = this.decideFontScale() * this.inputFontSize + 'vp';
3859    return inputTextMaxFontSize;
3860  }
3861
3862  getLeftIconColor(): ResourceColor {
3863    if (this.item.getIsModify()) {
3864      return $r('sys.color.icon_on_primary');
3865    } else if (this.item.getIsSelected()) {
3866      return this.treeViewTheme.leftIconActiveColor;
3867    } else {
3868      return this.treeViewTheme.leftIconColor;
3869    }
3870  }
3871
3872  private checkInvalidPattern(title: string): boolean {
3873    return new RegExp('/[\\\/:*?"<>|]/').test(title);
3874  }
3875
3876  private checkIsAllCN(title: string): boolean {
3877    return new RegExp('/^[\u4e00-\u9fa5]+$/').test(title);
3878  }
3879
3880  @Builder
3881  popupForShowTitle(text: string | Resource | undefined, backgroundColor: ResourceColor, fontColor: ResourceColor) {
3882    Row() {
3883      Text(text)
3884        .fontSize($r('sys.float.ohos_id_text_size_body2'))
3885        .fontWeight('regular')
3886        .fontColor(fontColor)
3887        .minFontScale(MIN_FONT_SCALE)
3888        .maxFontScale(this.decideFontScale())
3889    }
3890    .backgroundColor(backgroundColor)
3891    .border({ radius: $r('sys.float.ohos_id_elements_margin_horizontal_l') })
3892    .padding({
3893      left: $r('sys.float.ohos_id_elements_margin_horizontal_l'),
3894      right: $r('sys.float.ohos_id_elements_margin_horizontal_l'),
3895      top: $r('sys.float.ohos_id_card_margin_middle'),
3896      bottom: $r('sys.float.ohos_id_card_margin_middle'),
3897    })
3898  }
3899
3900  @Builder
3901  builder() {
3902    if (this.listTreeViewMenu) {
3903      this.listTreeViewMenu()
3904    }
3905  }
3906
3907  build() {
3908    if (this.item.getNodeIsShow()) {
3909      Stack() {
3910        Column() {
3911          Stack({ alignContent: Alignment.Bottom }) {
3912            Divider()
3913              .height(this.listNodeDataSource.getFlagLine().flagLineHeight)
3914              .color(this.listNodeDataSource.getFlagLine().flagLineColor)
3915              .visibility(this.listNodeDataSource.getVisibility(this.item))
3916              .lineCap(LineCapStyle.Round)
3917              .margin({ start: LengthMetrics.vp(this.item.getFlagLineLeftMargin()) })
3918              .focusable(true)
3919            Row({}) {
3920              if (this.item.getNodeItem().imageNode) {
3921                Row() {
3922                  Image(this.item.imageSource)
3923                    .objectFit(ImageFit.Contain)
3924                    .height(this.item.getNodeItem().imageNode?.itemHeight)
3925                    .width(this.item.getNodeItem().imageNode?.itemWidth)
3926                    .opacity(!this.item.getIsSelected() && !this.item.getIsHighLight() ?
3927                      this.item.getNodeItem().imageNode?.opacity : this.item.getNodeItem().imageNode?.noOpacity)
3928                    .focusable(this.item.getNodeItem().mainTitleNode !== null ? false : true)
3929                    .fillColor(this.getLeftIconColor())
3930                    .matchTextDirection((this.item.getNodeItem()
3931                      .imageCollapse?.collapseSource === ARROW_RIGHT || this.item.getNodeItem()
3932                      .imageCollapse?.collapseSource === ARROW_RIGHT_WITHE) ? true : false)
3933                }
3934                .focusable(true)
3935                .backgroundColor(COLOR_IMAGE_ROW)
3936                .margin({
3937                  end: getLengthMetricsByResourceOrNumber(this.item.getNodeItem().imageNode?.itemRightMargin)
3938                })
3939                .height(this.item.getNodeItem().imageNode?.itemHeight)
3940                .width(this.item.getNodeItem().imageNode?.itemWidth)
3941              }
3942              Row() {
3943                if (this.item.getNodeItem().mainTitleNode && this.item.getIsShowTitle()) {
3944                  Text(this.item.getNodeItem().mainTitleNode?.title)
3945                    .minFontScale(MIN_FONT_SCALE)
3946                    .maxFontScale(this.decideFontScale())
3947                    .maxLines(1)// max line
3948                    .fontSize(this.item.getNodeItem().mainTitleNode?.size)
3949                    .fontColor(this.item.getIsSelected() ?
3950                    this.treeViewTheme.primaryTitleActiveFontColor : this.treeViewTheme.primaryTitleFontColor)
3951                    .margin({
3952                      end: getLengthMetricsByResourceOrNumber(this.item.getNodeItem()
3953                        .mainTitleNode?.itemRightMargin)
3954                    })
3955                    .textOverflow({ overflow: TextOverflow.Ellipsis })
3956                    .fontWeight(this.item.getNodeItem().mainTitleNode?.weight)
3957                    .focusable(true)
3958                }
3959                if (this.item.getNodeItem().mainTitleNode && this.item.getNodeItem().inputText &&
3960                this.item.getIsShowInputText()) {
3961                  Row() {
3962                    TextInput({ text: this.item.getNodeItem().mainTitleNode?.title })
3963                      .height(this.item.getNodeItem().inputText?.itemHeight)
3964                      .fontSize(this.getInputTextMaxFontSize())
3965                      .fontColor(this.item.getNodeItem().inputText?.color)
3966                      .borderRadius(this.item.getNodeItem().inputText?.borderRadius)
3967                      .backgroundColor(this.item.getNodeItem().inputText?.backgroundColor)
3968                      .enterKeyType(EnterKeyType.Done)
3969                      .focusable(true)
3970                      .padding({
3971                        start: LengthMetrics.resource(this.textInputPadding.left),
3972                        end: LengthMetrics.resource(this.textInputPadding.right),
3973                        top: LengthMetrics.resource(this.textInputPadding.top),
3974                        bottom: LengthMetrics.resource(this.textInputPadding.bottom),
3975                      })
3976                      .onChange((value: string) => {
3977                        let thisIndex: number = this.listNodeDataSource.findIndex(this.item.getNodeCurrentNodeId());
3978                        let res: string = '';
3979                        let isInvalidError: boolean = false;
3980                        let isLengthError: boolean = false;
3981                        if (this.checkInvalidPattern(value)) {
3982                          for (let i: number = 0; i < value.length; i++) {
3983                            if (!this.checkInvalidPattern(value[i])) {
3984                              res += value[i];
3985                            }
3986                          }
3987                          isInvalidError = true;
3988                          this.listNodeDataSource.setPopUpInfo(PopUpType.WARNINGS,
3989                            InputError.INVALID_ERROR, true, thisIndex);
3990                        } else {
3991                          res = value;
3992                          isInvalidError = false;
3993                        }
3994                        if ((this.checkIsAllCN(res) && res.length > this.MAX_CN_LENGTH) ||
3995                          (!this.checkIsAllCN(res) && res.length > this.MAX_EN_LENGTH)) {
3996                          res = this.checkIsAllCN(res) ?
3997                          res.substr(0, this.MAX_CN_LENGTH) : res.substr(0, this.MAX_EN_LENGTH);
3998                          isLengthError = true;
3999                          this.listNodeDataSource.setPopUpInfo(PopUpType.WARNINGS,
4000                            InputError.LENGTH_ERROR, true, thisIndex);
4001                        } else {
4002                          isLengthError = false;
4003                        }
4004                        if (!isLengthError && !isInvalidError) {
4005                          this.listNodeDataSource.setMainTitleNameOnEdit(thisIndex, res);
4006                        }
4007                      })
4008                      .onSubmit((enterKey: EnterKeyType) => {
4009                        let thisIndex: number = this.listNodeDataSource.findIndex(this.item.getNodeCurrentNodeId());
4010                        this.listNodeDataSource.setPopUpInfo(
4011                          PopUpType.WARNINGS,
4012                          InputError.NONE,
4013                          false,
4014                          thisIndex
4015                        );
4016                        this.listNodeDataSource.setItemVisibilityOnEdit(thisIndex, MenuOperation.COMMIT_NODE);
4017                      })
4018                  }.backgroundColor(this.item.getNodeItem().inputText?.backgroundColor)
4019                  .borderRadius(this.item.getNodeItem().inputText?.borderRadius)
4020                  .margin({
4021                    end: getLengthMetricsByResourceOrNumber(this.item.getNodeItem()
4022                      .inputText?.itemRightMargin)
4023                  })
4024                }
4025                Blank()
4026              }
4027              .layoutWeight(1)
4028              .focusable(true)
4029
4030              if (this.listNodeDataSource.hasSubtitle(this.item.getNodeCurrentNodeId())) {
4031                Row() {
4032                  Text(this.listNodeDataSource.getSubtitle(this.item.getNodeCurrentNodeId()))
4033                    .minFontScale(MIN_FONT_SCALE)
4034                    .maxFontScale(this.decideFontScale())
4035                    .fontSize(this.listNodeDataSource.getSubTitlePara().fontSize)
4036                    .fontColor(this.item.getIsHighLight() || this.item.getIsModify() ?
4037                    $r('sys.color.ohos_id_color_primary_contrary') : this.treeViewTheme.secondaryTitleFontColor)
4038                    .fontWeight(this.listNodeDataSource.getSubTitlePara().fontWeight)
4039                }
4040                .focusable(true)
4041                .margin({
4042                  start: LengthMetrics.resource(this.listNodeDataSource.getSubTitlePara().margin.left),
4043                  end: this.item.getNodeItem().imageCollapse ?
4044                  LengthMetrics.resource($r('sys.float.padding_level0')) : LengthMetrics.resource(this.listNodeDataSource.getSubTitlePara().margin.right)
4045                })
4046              }
4047
4048              if (this.item.getNodeItem().imageCollapse) {
4049                Row() {
4050                  Image(this.item.getNodeItem().imageCollapse?.collapseSource)
4051                    .fillColor(this.item.getNodeItem().imageCollapse?.isCollapse ?
4052                    this.treeViewTheme.arrowIconColor : COLOR_IMAGE_EDIT)
4053                    .align(Alignment.End)
4054                    .objectFit(ImageFit.Contain)
4055                    .height(this.item.getNodeItem().imageCollapse?.itemHeight)
4056                    .width(this.item.getNodeItem().imageCollapse?.itemWidth)
4057                    .opacity(!this.item.getIsHighLight() ?
4058                      this.item.getNodeItem().imageCollapse?.opacity : this.item.getNodeItem().imageCollapse?.noOpacity)
4059                    .onTouch((event: TouchEvent) => {
4060                      if (event.type === TouchType.Down) {
4061                        this.listNodeDataSource.expandAndCollapseNode(
4062                          this.listNodeDataSource.findIndex(this.item.getNodeCurrentNodeId()));
4063                        this.listNodeDataSource.setCurrentFocusNodeId(this.item.getNodeCurrentNodeId());
4064                      }
4065                      event.stopPropagation();
4066                    })
4067                    .focusable(true)
4068                    .matchTextDirection((this.item.getNodeItem()
4069                      .imageCollapse?.collapseSource === ARROW_RIGHT || this.item.getNodeItem()
4070                      .imageCollapse?.collapseSource === ARROW_RIGHT_WITHE) ? true : false)
4071                }
4072                .focusable(true)
4073                .height(this.item.getNodeItem().imageCollapse?.itemHeight)
4074                .width(this.item.getNodeItem().imageCollapse?.itemWidth)
4075              }
4076            }
4077            .focusable(true)
4078            .width('100%')
4079            .gesture(
4080              TapGesture({ count: 2 })
4081                .onAction((event: GestureEvent) => {
4082                  this.listNodeDataSource.expandAndCollapseNode(
4083                    this.listNodeDataSource.findIndex(this.item.getNodeCurrentNodeId()));
4084                })
4085            )
4086            .height(this.item.getNodeHeight())
4087            .padding({ start: LengthMetrics.vp(this.item.getNodeLeftPadding()) })
4088            .bindContextMenu(this.builder, ResponseType.RightClick)
4089
4090          }.focusable(true)
4091        }
4092        .opacity(this.listNodeDataSource.getListItemOpacity(this.item))
4093        .onHover((isHover: boolean) => {
4094          if (isHover) {
4095            this.item.setNodeColor(this.treeViewTheme.itemHoverBgColor)
4096          } else {
4097            this.item.setNodeColor($r('sys.color.ohos_id_color_background_transparent'))
4098          }
4099        })
4100        .onTouch((event) => {
4101          this.count++;
4102          if (this.count > 1) {
4103            this.count--;
4104            return;
4105          }
4106
4107          this.index = this.listNodeDataSource.findIndex(this.item.getNodeCurrentNodeId())
4108          this.listNodeDataSource.setClickIndex(this.index);
4109          let currentId: number = this.item.getNodeCurrentNodeId();
4110
4111          if (event.type === TouchType.Down) {
4112            this.item.setNodeColor(this.treeViewTheme.itemPressedBgColor);
4113          }
4114
4115          else if (event.type === TouchType.Up) {
4116            if (!(typeof this.treeViewTheme.itemSelectedBgColor === 'string')) {
4117              this.item.setNodeColor(COLOR_SELECT);
4118            } else {
4119              this.item.setNodeColor(this.treeViewTheme.itemSelectedBgColor);
4120            }
4121            if (this.item.getNodeItem().imageNode !== null) {
4122              this.item.getNodeItem().imageNode?.setImageSource(InteractionStatus.SELECTED);
4123              this.listNodeDataSource.setImageSource(this.index, InteractionStatus.SELECTED);
4124              this.item.imageSource = this.item.getNodeItem().imageNode?.source;
4125            }
4126
4127            this.item.getNodeItem().mainTitleNode?.setMainTitleSelected(true);
4128            let callParam: CallbackParam = { currentNodeId: currentId };
4129            this.appEventBus.emit(TreeListenType.NODE_CLICK, callParam);
4130          }
4131
4132          if (this.listNodeDataSource.getLastIndex() !== -1 && this.index !== this.listNodeDataSource.getLastIndex()) {
4133            this.listNodeDataSource.setPopUpInfo(
4134              PopUpType.WARNINGS,
4135              InputError.NONE,
4136              false,
4137              this.listNodeDataSource.getLastIndex()
4138            );
4139            this.listNodeDataSource.setItemVisibilityOnEdit(
4140              this.listNodeDataSource.getLastIndex(),
4141              MenuOperation.COMMIT_NODE
4142            );
4143          }
4144          this.lastIndex = this.index;
4145          this.count--;
4146        })
4147        /* backgroundColor when editing and in other states. */
4148        .backgroundColor((this.item.getNodeItem().mainTitleNode && this.item.getNodeItem().inputText &&
4149        this.item.getIsShowInputText()) ? this.item.getNodeItem().inputText?.editColor : this.item.getNodeColor())
4150        .border({
4151          width: this.item.getNodeBorder().borderWidth,
4152          color: this.item.getNodeBorder().borderColor,
4153          radius: this.item.getNodeBorder().borderRadius,
4154        })
4155        .height(LIST_ITEM_HEIGHT)
4156        .focusable(true)
4157        .onMouse((event: MouseEvent) => {
4158          let thisIndex: number = this.listNodeDataSource.findIndex(this.item.getNodeCurrentNodeId());
4159          if (event.button === MouseButton.Right) {
4160            this.listNodeDataSource.handleEvent(Event.MOUSE_BUTTON_RIGHT,
4161              this.listNodeDataSource.findIndex(this.item.getNodeCurrentNodeId()));
4162            this.listTreeViewMenu = this.item.getMenu();
4163            this.listNodeDataSource.setClickIndex(thisIndex);
4164            clearTimeout(this.item.getNodeItem().mainTitleNode?.popUpTimeout);
4165          }
4166          event.stopPropagation();
4167        })
4168        .padding({ top: 0, bottom: 0 })
4169        .bindPopup(this.item.getPopUpInfo().popUpIsShow, {
4170          builder: this.popupForShowTitle(this.item.getPopUpInfo().popUpText, this.item.getPopUpInfo().popUpColor,
4171            this.item.getPopUpInfo().popUpTextColor),
4172          placement: Placement.BottomLeft,
4173          placementOnTop: false,
4174          popupColor: this.item.getPopUpInfo().popUpColor,
4175          autoCancel: true,
4176          enableArrow: this.item.getPopUpInfo().popUpEnableArrow,
4177        })
4178        .onAreaChange((oldValue: Area, newValue: Area) => {
4179          let columnWidthNum: number = Number.parseInt(newValue.width.toString());
4180          this.columnWidth = columnWidthNum;
4181        })
4182      }
4183      .stateStyles({
4184        focused: {
4185          .border({
4186            radius: $r('sys.float.ohos_id_corner_radius_clicked'),
4187            width: FLAG_NUMBER,
4188            color: this.treeViewTheme.borderFocusedColor,
4189            style: BorderStyle.Solid,
4190          })
4191        },
4192        normal: {
4193          .border({
4194            radius: $r('sys.float.ohos_id_corner_radius_clicked'),
4195            width: 0,
4196          })
4197        }
4198      })
4199    }
4200  }
4201}
4202
4203export class NodeItem {
4204  public childNodeInfo: ChildNodeInfo;
4205  public nodeLevel: number;
4206  public children: NodeItem[];
4207  public indexOfParent: number;
4208  public parentNodeId: number;
4209  public currentNodeId: number;
4210  public isFolder?: boolean;
4211
4212  constructor(nodeParam: NodeParam) {
4213    this.currentNodeId = nodeParam.currentNodeId ?? -1;
4214    this.parentNodeId = nodeParam.parentNodeId ?? -1;
4215    this.isFolder = nodeParam.isFolder;
4216    this.nodeLevel = -1;
4217    this.indexOfParent = -1;
4218    this.childNodeInfo = { isHasChildNode: false, childNum: 0, allChildNum: 0 };
4219    this.children = [];
4220  }
4221
4222  getChildNodeInfo(): ChildNodeInfo {
4223    return this.childNodeInfo;
4224  }
4225
4226  getCurrentNodeId(): number {
4227    return this.currentNodeId;
4228  }
4229
4230  getIsFolder(): boolean | undefined {
4231    return this.isFolder;
4232  }
4233}
4234
4235class NodeBaseInfo {
4236  public rightMargin: Resource | number = -1;
4237  private width: number = -1;
4238  private height: number = -1;
4239
4240  constructor() {
4241  }
4242
4243  set itemWidth(width: number) {
4244    this.width = width;
4245  }
4246
4247  get itemWidth(): number {
4248    return this.width;
4249  }
4250
4251  set itemHeight(height: number) {
4252    this.height = height;
4253  }
4254
4255  get itemHeight(): number {
4256    return this.height;
4257  }
4258
4259  set itemRightMargin(rightMargin: Resource | number) {
4260    this.rightMargin = rightMargin;
4261  }
4262
4263  get itemRightMargin(): Resource | number {
4264    return this.rightMargin;
4265  }
4266}
4267
4268export class CollapseImageNode extends NodeBaseInfo {
4269  private imageSource: Resource | string;
4270  private imageOpacity: Resource;
4271  private imageCollapseSource: Resource | string;
4272  private isImageCollapse: boolean;
4273  private collapseImageType: CollapseImageType;
4274
4275  constructor(
4276    imageSource: Resource | string,
4277    imageOpacity: Resource,
4278    itemWidth: number,
4279    itemHeight: number,
4280    itemRightMargin: Resource | number,
4281    isImageCollapse: boolean,
4282    collapseImageType: CollapseImageType
4283  ) {
4284    super();
4285    this.rightMargin = $r('sys.float.ohos_id_elements_margin_horizontal_m');
4286    this.imageSource = imageSource;
4287    this.rightMargin = itemRightMargin;
4288    this.imageOpacity = imageOpacity;
4289    this.itemWidth = itemWidth;
4290    this.itemHeight = itemHeight;
4291    this.imageCollapseSource = imageSource;
4292    this.isImageCollapse = isImageCollapse;
4293    this.collapseImageType = collapseImageType;
4294  }
4295
4296  get source(): Resource | string {
4297    return this.imageSource;
4298  }
4299
4300  get opacity(): Resource {
4301    return this.imageOpacity;
4302  }
4303
4304  get noOpacity(): number {
4305    return 1;
4306  }
4307
4308  get collapseSource(): Resource | string {
4309    return this.imageCollapseSource;
4310  }
4311
4312  get isCollapse(): boolean {
4313    return this.isImageCollapse;
4314  }
4315
4316  get type(): CollapseImageType {
4317    return this.collapseImageType;
4318  }
4319}
4320
4321class CollapseImageNodeFactory {
4322  private static instance: CollapseImageNodeFactory;
4323
4324  private constructor() {
4325  }
4326
4327  /**
4328   * CollapseImageNodeFactory singleton function
4329   *
4330   * @returns CollapseImageNodeFactory
4331   */
4332  public static getInstance(): CollapseImageNodeFactory {
4333    if (!CollapseImageNodeFactory.instance) {
4334      CollapseImageNodeFactory.instance = new CollapseImageNodeFactory();
4335    }
4336    return CollapseImageNodeFactory.instance;
4337  }
4338
4339  /**
4340   * create collapse image node by type
4341   *
4342   * @param type collapse image type
4343   * @returns collapse image node
4344   */
4345  public createCollapseImageNodeByType(type: CollapseImageType): CollapseImageNode {
4346    let imageSource: Resource | string;
4347    switch (type) {
4348      case CollapseImageType.ARROW_RIGHT_WHITE:
4349        imageSource = ARROW_RIGHT_WITHE;
4350        break;
4351      case CollapseImageType.ARROW_RIGHT:
4352        imageSource = ARROW_RIGHT;
4353        break;
4354      case CollapseImageType.ARROW_DOWN_WHITE:
4355        imageSource = ARROW_DOWN_WITHE;
4356        break;
4357      default:
4358        imageSource = ARROW_DOWN;
4359    }
4360    return new CollapseImageNode(
4361      imageSource,
4362      $r('sys.float.ohos_id_alpha_content_tertiary'),
4363      IMAGE_NODE_HEIGHT,
4364      IMAGE_NODE_WIDTH,
4365      $r('sys.float.ohos_id_text_paragraph_margin_xs'),
4366      (type === CollapseImageType.ARROW_RIGHT_WHITE || type === CollapseImageType.ARROW_DOWN_WHITE) ? false : true,
4367      type
4368    );
4369  }
4370}
4371
4372class CollapseImageNodeFlyweightFactory {
4373  private static nodeMap: Map<CollapseImageType, CollapseImageNode> = new Map<CollapseImageType, CollapseImageNode>();
4374
4375  /**
4376   * get collapse image node by type
4377   *
4378   * @param type collapse image node type
4379   * @returns  collapse image node
4380   */
4381  static getCollapseImageNodeByType(type: CollapseImageType): CollapseImageNode {
4382    let node: CollapseImageNode | undefined = CollapseImageNodeFlyweightFactory.nodeMap.get(type);
4383    if (node === undefined) {
4384      node = CollapseImageNodeFactory.getInstance().createCollapseImageNodeByType(type);
4385      CollapseImageNodeFlyweightFactory.nodeMap.set(type, node);
4386    }
4387    return node;
4388  }
4389
4390  /**
4391   * get collapse image node by interactionStatus and nodeStatus
4392   *
4393   * @param interactionStatus interaction status
4394   * @param nodeStatus node status
4395   * @param defaultType default collapse image type
4396   * @returns collapse image node
4397   */
4398  static getCollapseImageNode(interactionStatus: InteractionStatus, nodeStatus: NodeStatus | undefined,
4399    defaultType?: CollapseImageType): CollapseImageNode | undefined {
4400    if (defaultType === undefined) {
4401      return undefined;
4402    }
4403    let type: CollapseImageType = defaultType;
4404    if (interactionStatus == InteractionStatus.EDIT ||
4405      interactionStatus === InteractionStatus.DRAG_INSERT) {
4406      if (nodeStatus === NodeStatus.COLLAPSE) {
4407        type = CollapseImageType.ARROW_RIGHT_WHITE;
4408      } else {
4409        type = CollapseImageType.ARROW_DOWN_WHITE;
4410      }
4411    } else if (interactionStatus === InteractionStatus.FINISH_EDIT ||
4412      interactionStatus === InteractionStatus.FINISH_DRAG_INSERT) {
4413      if (nodeStatus === NodeStatus.COLLAPSE) {
4414        type = CollapseImageType.ARROW_RIGHT;
4415      } else {
4416        type = CollapseImageType.ARROW_DOWN;
4417      }
4418    }
4419    return CollapseImageNodeFlyweightFactory.getCollapseImageNodeByType(type);
4420  }
4421
4422  /**
4423   * change collapse image node source
4424   *
4425   * @param nodeStatus node status
4426   * @param isImageCollapse whether collapse image or white collapse image
4427   * @returns collapse image node
4428   */
4429  static changeImageCollapseSource(nodeStatus: NodeStatus, isImageCollapse?: boolean): CollapseImageNode | undefined {
4430    if (isImageCollapse === undefined) {
4431      return undefined;
4432    }
4433    let type: CollapseImageType;
4434    if (!isImageCollapse) {
4435      if (nodeStatus === NodeStatus.COLLAPSE) {
4436        type = CollapseImageType.ARROW_RIGHT_WHITE;
4437      } else {
4438        type = CollapseImageType.ARROW_DOWN_WHITE;
4439      }
4440    } else {
4441      if (nodeStatus === NodeStatus.COLLAPSE) {
4442        type = CollapseImageType.ARROW_RIGHT;
4443      } else {
4444        type = CollapseImageType.ARROW_DOWN;
4445      }
4446    }
4447    return CollapseImageNodeFlyweightFactory.getCollapseImageNodeByType(type);
4448  }
4449}
4450
4451export class ImageNode extends NodeBaseInfo {
4452  private imageSource: Resource | string;
4453  private imageNormalSource: Resource | string;
4454  private imageSelectedSource: Resource | string;
4455  private imageEditSource: Resource | string;
4456  private imageOpacity: Resource;
4457  private currentInteractionStatus: InteractionStatus;
4458  private imageCollapseSource: Resource | string;
4459  private imageCollapseDownSource: Resource | string;
4460  private isImageCollapse: boolean;
4461  private imageCollapseRightSource: Resource | string;
4462
4463  constructor(
4464    imageSource: Resource | string,
4465    imageOpacity: Resource,
4466    itemWidth: number,
4467    itemHeight: number,
4468    itemSelectedIcon?: Resource | string,
4469    itemEditIcon?: Resource | string,
4470  ) {
4471    super();
4472    this.rightMargin = $r('sys.float.ohos_id_elements_margin_horizontal_m');
4473    this.imageSource = imageSource;
4474    this.imageNormalSource = imageSource;
4475    if (itemSelectedIcon !== undefined) {
4476      this.imageSelectedSource = itemSelectedIcon;
4477    } else {
4478      this.imageSelectedSource = this.imageNormalSource;
4479    }
4480    if (itemEditIcon !== undefined) {
4481      this.imageEditSource = itemEditIcon;
4482    } else {
4483      this.imageEditSource = this.imageNormalSource;
4484    }
4485    this.imageOpacity = imageOpacity;
4486    this.itemWidth = itemWidth;
4487    this.itemHeight = itemHeight;
4488    this.imageCollapseSource = imageSource;
4489    this.imageCollapseDownSource = ARROW_DOWN;
4490    this.imageCollapseRightSource = ARROW_RIGHT;
4491    this.isImageCollapse = true;
4492    this.currentInteractionStatus = InteractionStatus.NORMAL;
4493  }
4494
4495  get source(): Resource | string {
4496    return this.imageSource;
4497  }
4498
4499  get normalSource(): Resource | string {
4500    return this.imageNormalSource;
4501  }
4502
4503  get selectedSource(): Resource | string {
4504    return this.imageSelectedSource;
4505  }
4506
4507  get editSource(): Resource | string {
4508    return this.imageEditSource;
4509  }
4510
4511  get opacity(): Resource {
4512    return this.imageOpacity;
4513  }
4514
4515  get noOpacity(): number {
4516    return 1;
4517  }
4518
4519  get collapseSource(): Resource | string {
4520    return this.imageCollapseSource;
4521  }
4522
4523  get isCollapse(): boolean {
4524    return this.isImageCollapse;
4525  }
4526
4527  changeImageCollapseSource(nodeStatus: NodeStatus): void {
4528    if (nodeStatus === NodeStatus.EXPAND) {
4529      this.imageCollapseSource = this.imageCollapseDownSource;
4530    } else if (nodeStatus === NodeStatus.COLLAPSE) {
4531      this.imageCollapseSource = this.imageCollapseRightSource;
4532    }
4533  }
4534
4535  setImageCollapseSource(interactionStatus: InteractionStatus, nodeStatus: NodeStatus | undefined): void {
4536    if (interactionStatus === InteractionStatus.EDIT || interactionStatus === InteractionStatus.DRAG_INSERT) {
4537      this.imageCollapseDownSource = ARROW_DOWN_WITHE;
4538      this.imageCollapseRightSource = ARROW_RIGHT_WITHE;
4539      this.isImageCollapse = false;
4540    } else if (interactionStatus === InteractionStatus.FINISH_EDIT ||
4541      interactionStatus === InteractionStatus.FINISH_DRAG_INSERT) {
4542      this.imageCollapseDownSource = ARROW_DOWN
4543      this.imageCollapseRightSource = ARROW_RIGHT
4544      this.isImageCollapse = true;
4545    }
4546    this.imageCollapseSource = (nodeStatus === NodeStatus.COLLAPSE) ?
4547    this.imageCollapseRightSource : this.imageCollapseDownSource;
4548  }
4549
4550  setImageSource(interactionStatus: InteractionStatus): void {
4551    switch (interactionStatus) {
4552      case InteractionStatus.NORMAL:
4553        this.imageSource = this.imageNormalSource;
4554        this.currentInteractionStatus = interactionStatus;
4555        break;
4556      case InteractionStatus.SELECTED:
4557        if (this.currentInteractionStatus !== InteractionStatus.EDIT) {
4558          this.imageSource = this.imageSelectedSource;
4559          this.currentInteractionStatus = interactionStatus;
4560        }
4561        break;
4562      case InteractionStatus.EDIT:
4563        this.imageSource = this.imageEditSource;
4564        this.currentInteractionStatus = interactionStatus;
4565        break;
4566      case InteractionStatus.FINISH_EDIT:
4567        this.imageSource = this.imageSelectedSource;
4568        this.currentInteractionStatus = interactionStatus;
4569        break;
4570      case InteractionStatus.DRAG_INSERT:
4571        this.imageSource = this.imageEditSource;
4572        this.currentInteractionStatus = interactionStatus;
4573        break;
4574      case InteractionStatus.FINISH_DRAG_INSERT:
4575        this.imageSource = this.imageNormalSource;
4576        this.currentInteractionStatus = interactionStatus;
4577        break;
4578      default:
4579        break;
4580    }
4581  }
4582}
4583
4584class MainTitleNode extends NodeBaseInfo {
4585  private mainTitleName: ResourceStr;
4586  public mainTitleSetting: TextSetting;
4587  private showPopUpTimeout: number;
4588  private treeViewTheme: TreeViewTheme = TreeViewTheme.getInstance();
4589
4590  constructor(mainTitleName: ResourceStr) {
4591    super();
4592    this.mainTitleName = mainTitleName;
4593    this.itemWidth = ITEM_WIDTH;
4594    this.itemHeight = ITEM_HEIGHT;
4595    this.rightMargin = $r('sys.float.ohos_id_text_paragraph_margin_xs');
4596    this.mainTitleSetting = {
4597      fontColor: this.treeViewTheme.primaryTitleFontColor,
4598      fontSize: $r('sys.float.ohos_id_text_size_body1'),
4599      fontWeight: FontWeight.Normal,
4600    };
4601    this.showPopUpTimeout = 0;
4602  }
4603
4604  setMainTitleSelected(isSelected: boolean): void {
4605    if (isSelected) {
4606      this.mainTitleSetting = {
4607        fontColor: this.treeViewTheme.primaryTitleActiveFontColor,
4608        fontSize: $r('sys.float.ohos_id_text_size_body1'),
4609        fontWeight: FontWeight.Regular,
4610      };
4611    } else {
4612      this.mainTitleSetting = {
4613        fontColor: this.treeViewTheme.primaryTitleFontColor,
4614        fontSize: $r('sys.float.ohos_id_text_size_body1'),
4615        fontWeight: FontWeight.Normal,
4616      };
4617    }
4618  }
4619
4620  set title(text: ResourceStr) {
4621    this.mainTitleName = text;
4622  }
4623
4624  get title(): ResourceStr {
4625    return this.mainTitleName;
4626  }
4627
4628  set popUpTimeout(showPopUpTimeout: number) {
4629    this.showPopUpTimeout = showPopUpTimeout;
4630  }
4631
4632  get popUpTimeout(): number {
4633    return this.showPopUpTimeout;
4634  }
4635
4636  get color(): ResourceColor {
4637    return this.mainTitleSetting.fontColor;
4638  }
4639
4640  get size(): Resource {
4641    return this.mainTitleSetting.fontSize;
4642  }
4643
4644  get weight(): FontWeight {
4645    return this.mainTitleSetting.fontWeight;
4646  }
4647
4648  setMainTitleHighLight(isHighLight: boolean): void {
4649    if (isHighLight) {
4650      this.mainTitleSetting = {
4651        fontColor: this.treeViewTheme.primaryTitleActiveFontColor,
4652        fontSize: $r('sys.float.ohos_id_text_size_body1'),
4653        fontWeight: FontWeight.Regular,
4654      };
4655    } else {
4656      this.mainTitleSetting = {
4657        fontColor: this.treeViewTheme.primaryTitleFontColor,
4658        fontSize: $r('sys.float.ohos_id_text_size_body1'),
4659        fontWeight: FontWeight.Normal,
4660      };
4661    }
4662  }
4663}
4664
4665export class InputText extends NodeBaseInfo {
4666  private inputTextSetting: TextSetting;
4667  private status?: Status = undefined;
4668  private statusColor: Resource = $r('sys.color.ohos_id_color_background');
4669  private editItemColor: Resource = $r('sys.color.ohos_id_color_emphasize');
4670  private radius: Resource = $r('sys.float.ohos_id_corner_radius_default_xs');
4671  private treeViewTheme: TreeViewTheme = TreeViewTheme.getInstance();
4672
4673  constructor() {
4674    super();
4675    this.itemWidth = ITEM_WIDTH;
4676    this.itemHeight = ITEM_HEIGHT_INPUT;
4677    this.rightMargin = $r('sys.float.ohos_id_text_paragraph_margin_xs');
4678    this.inputTextSetting = {
4679      fontColor: this.treeViewTheme.primaryTitleFontColor,
4680      fontSize: $r('sys.float.ohos_id_text_size_body1'),
4681      fontWeight: FontWeight.Normal,
4682    };
4683  }
4684
4685  get color(): ResourceColor {
4686    return this.inputTextSetting.fontColor;
4687  }
4688
4689  get size(): Resource {
4690    return this.inputTextSetting.fontSize;
4691  }
4692
4693  get weight(): FontWeight {
4694    return this.inputTextSetting.fontWeight;
4695  }
4696
4697  get borderRadius(): Resource {
4698    return this.radius;
4699  }
4700
4701  get backgroundColor(): Resource {
4702    return this.statusColor;
4703  }
4704
4705  get editColor(): Resource {
4706    return this.editItemColor;
4707  }
4708
4709  get textInputStatusColor(): Status | undefined {
4710    return this.status;
4711  }
4712}
4713
4714/**
4715 * get LengthMetrics
4716 *
4717 * @param Resource | number type
4718 * @returns LengthMetrics
4719 */
4720function getLengthMetricsByResourceOrNumber(resourceOrNumber: Resource | number): LengthMetrics {
4721  if (!resourceOrNumber) {
4722    return LengthMetrics.vp(0);
4723  } else if (typeof resourceOrNumber === 'number') {
4724    return LengthMetrics.vp(resourceOrNumber);
4725  } else {
4726    return LengthMetrics.resource(resourceOrNumber);
4727  }
4728}