From 520b966f90332ef42b571d5de84e5702a6a1b28a Mon Sep 17 00:00:00 2001 From: Maxime Jacquemin <maxime.jacquemin@cea.fr> Date: Thu, 31 Mar 2022 13:59:53 +0200 Subject: [PATCH] [ivette] Get ride of the SelectedProbeInfo component --- ivette/src/frama-c/plugins/eva/style.css | 79 ++------ ivette/src/frama-c/plugins/eva/valuetable.tsx | 173 +++++------------- 2 files changed, 62 insertions(+), 190 deletions(-) diff --git a/ivette/src/frama-c/plugins/eva/style.css b/ivette/src/frama-c/plugins/eva/style.css index b1f806a19cd..6fab09886b9 100644 --- a/ivette/src/frama-c/plugins/eva/style.css +++ b/ivette/src/frama-c/plugins/eva/style.css @@ -1,61 +1,3 @@ -/* -------------------------------------------------------------------------- */ -/* --- Probe Panel --- */ -/* -------------------------------------------------------------------------- */ - -.eva-probeinfo { - min-height: 32px; - padding-left: 6px; - padding-top: 2px; - padding-bottom: 4px; - width: 100%; - background: var(--background-profound); - display: flex; -} - -.eva-probeinfo-label { - flex: 0 1 auto; - min-width: 22px; - text-align: left; -} - -.eva-probeinfo-code { - flex: 0 1 auto; - height: fit-content; - background: var(--background-intense); - min-width: 120px; - width: auto; - border: thin solid var(--border); - padding-left: 6px; - padding-right: 6px; - border-radius: 4px; - overflow: hidden; -} - -.eva-probeinfo-stmt { - flex: 0 0 auto; - margin-left: 2px; - margin-right: 0px; - margin-top: 2px; -} - -.eva-probeinfo-button { - flex: 0 0 auto; - margin: 1px; - min-width: 16px; -} - -.eva-probeinfo-state { - margin-top: 2px; - margin-bottom: 2px; -} - -.eva-probeinfo-code-text { - padding: 3px; - white-space: pre; - overflow: hidden; - text-overflow: ellipsis; -} - /* -------------------------------------------------------------------------- */ /* --- Call Stack Info --- */ /* -------------------------------------------------------------------------- */ @@ -222,8 +164,15 @@ gap: 0.3em; } -.eva-italic { - font-style: italic; +.eva-header-buttons-container { + position: absolute; + right: 1px; + top: 0px; + display: flex; +} + +.eva-header-button { + padding: 2px 2px 3px 2px; } /* -------------------------------------------------------------------------- */ @@ -310,9 +259,17 @@ tr:first-of-type > .eva-table-callsite-box { } /* -------------------------------------------------------------------------- */ -/* --- Selected Element Background --- */ +/* --- Miscellaneous --- */ /* -------------------------------------------------------------------------- */ +.eva-button { + margin: 0px; +} + +.eva-italic { + font-style: italic; +} + .eva-focused { background: var(--code-select); } diff --git a/ivette/src/frama-c/plugins/eva/valuetable.tsx b/ivette/src/frama-c/plugins/eva/valuetable.tsx index 174f8d693b9..581b6a088a4 100644 --- a/ivette/src/frama-c/plugins/eva/valuetable.tsx +++ b/ivette/src/frama-c/plugins/eva/valuetable.tsx @@ -28,13 +28,12 @@ import * as States from 'frama-c/states'; import * as Server from 'frama-c/server'; import * as Ast from 'frama-c/kernel/api/ast'; import * as Values from 'frama-c/plugins/eva/api/values'; -import ControlPoint from 'frama-c/plugins/eva/ControlPoint'; import { classes } from 'dome/misc/utils'; import { Icon } from 'dome/controls/icons'; +import { Inset } from 'dome/frame/toolbars'; import { Cell, Code } from 'dome/controls/labels'; import { IconButton } from 'dome/controls/buttons'; -import { Inset, Button, ButtonGroup } from 'dome/frame/toolbars'; import { Filler, Hpack, Hfill, Vpack, Vfill } from 'dome/layout/boxes'; @@ -44,8 +43,6 @@ import { Filler, Hpack, Hfill, Vpack, Vfill } from 'dome/layout/boxes'; /* -------------------------------------------------------------------------- */ type Request<A, B> = (a: A) => Promise<B>; -type StateToDisplay = { before: boolean; after: boolean } -const defaultState = { before: true, after: true }; type Alarm = [ 'True' | 'False' | 'Unknown', string ] function getAlarmStatus(alarms: Alarm[] | undefined): string { @@ -278,7 +275,7 @@ async function StackInfos(props: StackInfosProps): Promise<JSX.Element> { <Hfill /> <IconButton icon='CROSS' - className='eva-probeinfo-button' + className='eva-button' onClick={close} /> </div> @@ -289,83 +286,6 @@ async function StackInfos(props: StackInfosProps): Promise<JSX.Element> { -/* -------------------------------------------------------------------------- */ -/* --- Selected Probe Component --- */ -/* -------------------------------------------------------------------------- */ -/* --- Display informations on the currently selected probe, along with --- */ -/* --- buttons to pin, remove and change the displayed columns, i.e. if --- */ -/* --- we display the values before and/or after the statement. --- */ -/* -------------------------------------------------------------------------- */ - -interface ProbeInfosProps { - probe?: Probe; - status?: MarkerStatus; - removeProbe: (probe: Probe) => void; - setLocPin: (loc: Location, pin: boolean) => void; - state: StateToDisplay; - setState: (state: StateToDisplay) => void; -} - -function SelectedProbeInfos(props: ProbeInfosProps): JSX.Element { - const { probe, status, setLocPin, removeProbe, state, setState } = props; - const code = probe?.code; - const stmt = probe?.stmt; - const target = probe?.target; - const visible = code !== undefined; - const pinned = status ? isPinnedMarker(status) : false; - return ( - <Hpack className="eva-probeinfo"> - <div - className="eva-probeinfo-code" - style={{ visibility: visible ? 'visible' : 'hidden' }} - > - <div className='eva-probeinfo-code-text dome-text-cell'>{code}</div> - </div> - <Code><Stmt stmt={stmt} marker={target} /></Code> - <IconButton - icon='PIN' - className="eva-probeinfo-button" - title='Pin the probe' - selected={pinned} - visible={visible} - onClick={() => { if (probe) setLocPin(probe, !pinned); }} - /> - <IconButton - icon="CIRC.CLOSE" - className="eva-probeinfo-button" - title="Discard the probe" - onClick={() => { if (probe) removeProbe(probe); }} - visible={visible} - /> - <Filler /> - <ButtonGroup className='eva-probeinfo-state'> - <Button - value='Before' - selected={state.before} - title='Show values before statement effects' - visible={visible} - onClick={() => setState({before: !state.before, after: state.after})} - > - <ControlPoint kind={'before'}/> - </Button> - <Button - value='After' - selected={state.after} - title='Show values after statement effects' - visible={visible} - onClick={() => setState({before: state.before, after: !state.after})} - > - <ControlPoint kind={'after'}/> - </Button> - </ButtonGroup> - </Hpack> - ); -} - -/* -------------------------------------------------------------------------- */ - - - /* -------------------------------------------------------------------------- */ /* --- Probe Header Component --- */ /* -------------------------------------------------------------------------- */ @@ -376,7 +296,6 @@ function SelectedProbeInfos(props: ProbeInfosProps): JSX.Element { interface ProbeHeaderProps { probe: Probe; status: MarkerStatus; - state: StateToDisplay; pinProbe: (pin: boolean) => void; selectProbe: () => void; removeProbe: () => void; @@ -385,30 +304,27 @@ interface ProbeHeaderProps { } function ProbeHeader(props: ProbeHeaderProps): JSX.Element { - const { probe, status, state, setSelection, locEvt } = props; + const { probe, status, setSelection, locEvt } = props; const { code = '(error)', stmt, target, fct } = probe; const color = classes(MarkerStatusClass(status), 'eva-table-header-sticky'); const { selectProbe, removeProbe, pinProbe } = props; - const { before, after } = state; - - // Computing the number of columns.. - let span = 0; - if ((before || after) && !probe.effects && !probe.condition) span += 2; - if (before && (probe.effects || probe.condition)) span += 2; - if (after && probe.effects) span += 2; - if (after && probe.condition) span += 4; - if (span === 0) return <></>; + const span = 2 + (probe.effects ? 2 : 0) + (probe.condition ? 4 : 0); + const buttonClass = classes('eva-button', 'eva-header-button'); // When the location is selected, we scroll the header into view, making it // appears wherever it was. const ref = React.createRef<HTMLTableCellElement>(); locEvt.on((l) => { if (l === probe) ref.current?.scrollIntoView(); }); + const isPinned = isPinnedMarker(status); + const pinText = isPinned ? 'Unpin' : 'Pin'; const loc: States.SelectionActions = { location: { fct, marker: target} }; const onClick = (): void => { setSelection(loc); selectProbe(); }; - const onDoubleClick = (): void => pinProbe(!isPinnedMarker(status)); + const onDoubleClick = (): void => pinProbe(!isPinned); const onContextMenu = (): void => { const items: Dome.PopupMenuItem[] = []; + const pinLabel = `${pinText} column for ${code}`; + items.push({ label: pinLabel, onClick: onDoubleClick }); const removeLabel = `Remove column for ${code}`; items.push({ label: removeLabel, onClick: removeProbe }); Dome.popupMenu(items); @@ -425,6 +341,21 @@ function ProbeHeader(props: ProbeHeaderProps): JSX.Element { > <span className='dome-text-cell'>{code}</span> <Stmt stmt={stmt} marker={target} short={true}/> + <div className='eva-header-buttons-container'> + <IconButton + icon='PIN' + className={buttonClass} + title={`${pinText} the probe`} + selected={isPinned} + onClick={onDoubleClick} + /> + <IconButton + icon="CIRC.CLOSE" + className={buttonClass} + title="Discard the probe" + onClick={() => removeProbe()} + /> + </div> </th> ); } @@ -442,12 +373,10 @@ function ProbeHeader(props: ProbeHeaderProps): JSX.Element { interface ProbeDescrProps { probe: Probe; - state: StateToDisplay; } function ProbeDescr(props: ProbeDescrProps): JSX.Element[] { - const { probe, state } = props; - const { before, after } = state; + const { probe } = props; const valuesClass = classes('eva-table-values', 'eva-table-values-center'); const tableClass = classes('eva-table-descrs', 'eva-table-descr-sticky'); const cls = classes(valuesClass, tableClass); @@ -457,13 +386,13 @@ function ProbeDescr(props: ProbeDescrProps): JSX.Element[] { function push(title: string, children: JSX.Element | string): void { elements.push(<td {...infos} title={title}>{children}</td>); } - if ((before || after) && !probe.effects && !probe.condition) + if (!probe.effects && !probe.condition) push('Values at the statement', '-'); - if (before && (probe.effects || probe.condition)) + if (probe.effects || probe.condition) push(title('before'), 'Before'); - if (after && probe.effects) + if (probe.effects) push(title('after'), 'After'); - if (after && probe.condition) { + if (probe.condition) { const pushCondition = (s: string): void => { const t = `Values after the condition, in the ${s.toLowerCase()} branch`; const child = @@ -497,15 +426,13 @@ interface ProbeValuesProps { probe: Probe; summary: Evaluation; status: MarkerStatus; - state: StateToDisplay; addLoc: (loc: Location) => void; isSelectedCallstack: (c: callstack) => boolean; summaryOnly: boolean; } function ProbeValues(props: ProbeValuesProps): Request<callstack, JSX.Element> { - const { probe, summary, state, addLoc, summaryOnly } = props; - const { before, after } = state; + const { probe, summary, addLoc, summaryOnly } = props; const summaryStatus = getAlarmStatus(summary.vBefore?.alarms); // Building common parts @@ -557,16 +484,16 @@ function ProbeValues(props: ProbeValuesProps): Request<callstack, JSX.Element> { ); } const elements: JSX.Element[] = []; - if (before && after && probe.effects && _.isEqual(vBefore, vAfter)) + if (probe.effects && _.isEqual(vBefore, vAfter)) elements.push(td(vBefore, 3)); else { - if ((before || after) && !probe.effects && !probe.condition) + if (!probe.effects && !probe.condition) elements.push(td(vBefore)); - if (before && (probe.effects || probe.condition)) + if (probe.effects || probe.condition) elements.push(td(vBefore)); - if (after && probe.effects) + if (probe.effects) elements.push(td(vAfter)); - if (after && probe.condition) + if (probe.condition) elements.push(td(vThen), td(vElse)); } return <>{React.Children.toArray(elements)}</>; @@ -626,7 +553,6 @@ async function CallsiteCell(props: CallsiteCellProps): Promise<JSX.Element> { interface FunctionProps { fct: string; markers: Map<Ast.marker, MarkerStatus>; - state: StateToDisplay; close: () => void; getProbe: Request<Location, Probe>; pinProbe: (probe: Probe, pin: boolean) => void; @@ -650,13 +576,12 @@ interface FunctionProps { const PageSize = 99; async function FunctionSection(props: FunctionProps): Promise<JSX.Element> { - const { fct, state, folded, isSelectedCallstack, locEvt } = props; + const { fct, folded, isSelectedCallstack, locEvt } = props; const { byCallstacks, setSelection, getCallsites } = props; const { addLoc, getCallstacks: getCS } = props; const { setFolded, setByCallstacks, close } = props; const { startingCallstack, changeStartingCallstack } = props; - const { before, after } = state; - const displayTable = folded || !(before || after) ? 'none' : 'block'; + const displayTable = folded ? 'none' : 'block'; type RowHandler = React.MouseEventHandler<HTMLTableRowElement>; const onClick: (c: callstack) => RowHandler = (c) => (event) => { const elt = document.elementFromPoint(event.clientX, event.clientY); @@ -686,14 +611,14 @@ async function FunctionSection(props: FunctionProps): Promise<JSX.Element> { const selectProbe = (): void => props.selectProbe(d.probe); const removeProbe = (): void => props.removeProbe(d.probe); const fcts = { selectProbe, pinProbe, removeProbe, setSelection }; - return ProbeHeader({ ...d, state, ...fcts, locEvt }); + return ProbeHeader({ ...d, ...fcts, locEvt }); })); /* Computes the columns descriptions */ - const descrs = data.map((d) => ProbeDescr({ ...d, state })).flat(); + const descrs = data.map((d) => ProbeDescr(d)).flat(); /* Computes the summary values */ - const miscs = { state, addLoc, isSelectedCallstack, summaryOnly }; + const miscs = { addLoc, isSelectedCallstack, summaryOnly }; const builders = data.map((d: Data) => ProbeValues({ ...d, ...miscs })); const summary = await Promise.all(builders.map((b) => b('Summary'))); const summCall = await CallsiteCell({ callstack: 'Summary', getCallsites }); @@ -748,7 +673,7 @@ async function FunctionSection(props: FunctionProps): Promise<JSX.Element> { </div> <IconButton icon="ITEMS.LIST" - className="eva-probeinfo-button" + className="eva-button" selected={byCallstacks} disabled={summaryOnly} title="Details by callstack" @@ -757,7 +682,7 @@ async function FunctionSection(props: FunctionProps): Promise<JSX.Element> { <Inset /> <IconButton icon="CROSS" - className="eva-probeinfo-button" + className="eva-button" title="Close" onClick={close} /> @@ -965,7 +890,6 @@ function EvaTable(): JSX.Element { /* Component state */ const [ selection, select ] = States.useSelection(); - const [ state, setState ] = React.useState<StateToDisplay>(defaultState); const [ cs, setCS ] = React.useState<callstack>('Summary'); const [ fcts ] = React.useState(new FunctionsManager()); const [ focus, setFocus ] = React.useState<Probe | undefined>(undefined); @@ -1054,7 +978,6 @@ function EvaTable(): JSX.Element { return { fct, markers: infos.markers(focus), - state, close: () => { fcts.delete(fct); setTic(tac + 1); }, pinProbe: setLocPin, getProbe, @@ -1078,7 +1001,7 @@ function EvaTable(): JSX.Element { return Promise.all(ps.map(FunctionSection)); }, [ cs, fcts, focus, tac, getCallsites, setLocPin, - getCallstacks, getProbe, remove, select, state, locEvt ]); + getCallstacks, getProbe, remove, select, locEvt ]); const { result: functions } = Dome.usePromise(functionsPromise); /* Builds the alarms component. As for the function sections, it is an @@ -1102,14 +1025,6 @@ function EvaTable(): JSX.Element { return ( <> <Ivette.TitleBar /> - <SelectedProbeInfos - probe={focus} - status={fcts.status(focus)} - setLocPin={setLocPin} - removeProbe={remove} - state={state} - setState={setState} - /> <div className='eva-functions-section'> {React.Children.toArray(functions)} </div> -- GitLab