From 99fc3dd38df90d210fd3cf016d08af66df591334 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr>
Date: Thu, 30 Nov 2023 16:15:52 +0100
Subject: [PATCH] [ivette/wp] goal & list view

---
 ivette/src/frama-c/plugins/wp/goals.tsx | 37 +++++++++------
 ivette/src/frama-c/plugins/wp/index.tsx | 60 +++++++++++++++----------
 ivette/src/frama-c/states.ts            |  2 +
 3 files changed, 62 insertions(+), 37 deletions(-)

diff --git a/ivette/src/frama-c/plugins/wp/goals.tsx b/ivette/src/frama-c/plugins/wp/goals.tsx
index 87d94e61690..de0453e1c9c 100644
--- a/ivette/src/frama-c/plugins/wp/goals.tsx
+++ b/ivette/src/frama-c/plugins/wp/goals.tsx
@@ -96,19 +96,27 @@ function filterGoal(
 export interface GoalTableProps {
   scope: Ast.decl | undefined;
   failed: boolean;
-  onFilter: (goals: number, total: number) => void;
+  display: boolean;
+  current: WP.goal;
+  setCurrent: (goal: WP.goal) => void;
+  setGoals: (goals: number) => void;
+  setTotal: (total: number) => void;
 }
 
 export function GoalTable(props: GoalTableProps): JSX.Element {
-  const { scope, failed, onFilter } = props;
-  const model = States.useSyncArrayModel(WP.goals);
-  const [wpoSelection, setWpoSelection] = React.useState(WP.goalDefault);
-
-  const onWpoSelection = React.useCallback(
+  const {
+    display, scope, failed,
+    current, setCurrent,
+    setGoals, setTotal,
+  } = props;
+  const { model } = States.useSyncArrayProxy(WP.goals);
+  const goals = model.getRowCount();
+  const total = model.getTotalRowCount();
+  const onSelection = React.useCallback(
     ({ wpo, property }: WP.goalsData) => {
       States.setSelected(property);
-      setWpoSelection(wpo);
-    }, []);
+      setCurrent(wpo);
+    }, [setCurrent]);
 
   React.useEffect(() => {
     if (failed || !!scope) {
@@ -116,17 +124,18 @@ export function GoalTable(props: GoalTableProps): JSX.Element {
     } else {
       model.setFilter();
     }
-    const goals = model.getRowCount();
-    const total = model.getTotalRowCount();
-    onFilter(goals, total);
-  }, [model, scope, failed, onFilter]);
+  }, [model, scope, failed]);
+
+  React.useEffect(() => setGoals(goals), [goals, setGoals]);
+  React.useEffect(() => setTotal(total), [total, setTotal]);
 
   return (
     <Table
       model={model}
+      display={display}
       settings='wp.goals'
-      onSelection={onWpoSelection}
-      selection={wpoSelection}
+      selection={current}
+      onSelection={onSelection}
     >
       <Column id='scope' label='Scope'
               width={150}
diff --git a/ivette/src/frama-c/plugins/wp/index.tsx b/ivette/src/frama-c/plugins/wp/index.tsx
index bc8223ad414..943e2471d1b 100644
--- a/ivette/src/frama-c/plugins/wp/index.tsx
+++ b/ivette/src/frama-c/plugins/wp/index.tsx
@@ -41,32 +41,46 @@ import './style.css';
 /* -------------------------------------------------------------------------- */
 
 function WPGoals(): JSX.Element {
-  const [scoped, flipScoped] = Dome.useFlipSettings('frama-c.wp.goals.scoped');
-  const [failed, flipFailed] = Dome.useFlipSettings('frama-c.wp.goals.failed');
+  const [scoped, flipScoped] = Dome.useFlipSettings('frama-c.wp.goals.scoped',false);
+  const [failed, flipFailed] = Dome.useFlipSettings('frama-c.wp.goals.failed',true);
+  const [list, flipListView] = Dome.useFlipSettings('frama-c.wp.goals.list',true);
+  const [current, setCurrent] = React.useState(WP.goalDefault);
   const scope = States.useCurrentScope();
   const [goals, setGoals] = React.useState(0);
   const [total, setTotal] = React.useState(0);
-  const onFilter = React.useCallback((goals, total) => {
-    setGoals(goals);
-    setTotal(total);
-  }, [setGoals, setTotal]);
-  const current = scoped ? scope : undefined;
-    return (
-      <>
-        <Ivette.TitleBar>
-          <Label display={goals < total}>
-            {goals} / {total}
-          </Label>
-          <Inset />
-          <IconButton icon='COMPONENT' title='Current Scope Only'
-                      enabled={!!current}
-                      selected={scoped} onClick={flipScoped} />
-          <IconButton icon='CIRC.QUESTION' title='Unresolved Goals Only'
-                      selected={failed} onClick={flipFailed} />
-        </Ivette.TitleBar>
-        <GoalTable scope={scope} failed={failed} onFilter={onFilter} />
-      </>
-    );
+  const hasGoals = total > 0;
+  return (
+    <>
+      <Ivette.TitleBar>
+        <Label display={goals < total}>
+          {goals} / {total}
+        </Label>
+        <Inset />
+        <IconButton icon='CURSOR' title='Current Scope Only'
+                    enabled={hasGoals}
+                    selected={scoped}
+                    onClick={flipScoped} />
+        <IconButton icon='CIRC.QUESTION' title='Unresolved Goals Only'
+                    enabled={hasGoals}
+                    selected={failed}
+                    onClick={flipFailed} />
+        <IconButton icon='ITEMS.LIST'
+                    title='Goals List / Current Goal Details'
+                    enabled={hasGoals}
+                    selected={list}
+                    onClick={flipListView} />
+      </Ivette.TitleBar>
+      <GoalTable
+        display={list}
+        scope={scope}
+        failed={failed}
+        current={current}
+        setCurrent={setCurrent}
+        setGoals={setGoals}
+        setTotal={setTotal}
+      />
+    </>
+  );
 }
 
 Ivette.registerComponent({
diff --git a/ivette/src/frama-c/states.ts b/ivette/src/frama-c/states.ts
index 7e1ee641c1d..abb59114db2 100644
--- a/ivette/src/frama-c/states.ts
+++ b/ivette/src/frama-c/states.ts
@@ -409,6 +409,7 @@ export function reloadArray<K, A>(arr: Array<K, A>): void {
 
 /** Access to Synchronized Array elements. */
 export interface ArrayProxy<K, A> {
+  model: CompactModel<K,A>;
   length: number;
   getData(elt: K | undefined): (A | undefined);
   forEach(fn: (row: A, elt: K) => void): void;
@@ -429,6 +430,7 @@ function arrayProxy<K, A>(
   _stamp: number,
 ): ArrayProxy<K, A> {
   return {
+    model,
     length: model.length(),
     getData: (elt) => elt ? model.getData(elt) : undefined,
     forEach: (fn) => model.forEach((r) => fn(r, model.getkey(r))),
-- 
GitLab