package net.certware.evidence.hugin.view; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Set; import net.certware.core.ICertWareConstants; import net.certware.core.ui.handlers.LinkEditor; import net.certware.core.ui.help.IHelpContext; import net.certware.core.ui.listeners.ActiveEditorListener; import net.certware.core.ui.log.CertWareLog; import net.certware.core.ui.resources.FileFinder; import net.certware.core.ui.resources.FileOpener; import net.certware.core.ui.views.ICertWareView; import net.certware.evidence.hugin.view.help.ContextProvider; import net.certware.evidence.hugin.view.preferences.PreferenceConstants; import net.certware.evidence.hugin.view.tree.VariableNode; import net.certware.evidence.hugin.view.tree.VariableNodeState; import net.certware.evidence.hugin.view.util.ReadModelFile; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.help.IContextProvider; import org.eclipse.jface.dialogs.IMessageProvider; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ColumnViewerEditor; import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent; import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy; import org.eclipse.jface.viewers.FocusCellOwnerDrawHighlighter; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.TreeViewerColumn; import org.eclipse.jface.viewers.TreeViewerEditor; import org.eclipse.jface.viewers.TreeViewerFocusCellManager; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.jface.viewers.ViewerSorter; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.ui.IMemento; import org.eclipse.ui.IPartListener2; import org.eclipse.ui.ISaveablePart2; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.IViewSite; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.forms.HyperlinkSettings; import org.eclipse.ui.forms.events.ExpansionAdapter; import org.eclipse.ui.forms.events.ExpansionEvent; import org.eclipse.ui.forms.events.HyperlinkAdapter; import org.eclipse.ui.forms.events.HyperlinkEvent; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.Hyperlink; import org.eclipse.ui.forms.widgets.ScrolledForm; import org.eclipse.ui.forms.widgets.Section; import org.eclipse.ui.forms.widgets.TableWrapData; import org.eclipse.ui.forms.widgets.TableWrapLayout; import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.help.IWorkbenchHelpSystem; import org.eclipse.ui.part.ViewPart; import org.eclipse.ui.services.IEvaluationService; import edu.ucla.belief.BeliefNetwork; import edu.ucla.belief.io.hugin.HuginNode; import edu.ucla.belief.sensitivity.SensitivityEngine; /** * Provides a tree view of a network model. * @author mrb * @since 1.2.1 */ public class ViewTree extends ViewPart implements ICertWareConstants, ICertWareView, ISelectionListener, ISaveablePart2, IResourceChangeListener, IAdaptable, IHelpContext { /** the forms toolkit, borrowed from plugin's shared instance */ private FormToolkit toolkit; /** the top-level scrolled form installed as the view control */ private ScrolledForm form; /** memento for view part */ protected IMemento memento = null; /** whether the view and editor are linked */ public boolean isLinkingEditor = false; /** selected network model */ private BeliefNetwork selectedNetwork = null; /** selected file */ private IFile selectedFile = null; /** latest editor selection */ private ISelection latestSelection = null; /** our listener for editor changes */ private IPartListener2 activeEditorListener = null; /** context provider for help system adapter */ static ContextProvider contextprovider = null; /** tree viewer */ private TreeViewer treeViewer; /** context heading form section */ private Section context; /** sensitivity settings */ private Section sensitivity; /** analysis results form section */ private Section results; /** table items form section */ private Section items; /** network name link */ private Hyperlink networkHyperlink; /** discrete item filter */ private MenuItem itemDiscreteFilterMenuItem; /** continuous item filter */ private MenuItem itemContinuousFilterMenuItem; /** decision item filter */ private MenuItem itemDecisionFilterMenuItem; /** utility item filter */ private MenuItem itemUtilityFilterMenuItem; /** whether the model is dirty */ protected boolean dirty = false; /** sensitivity client composite */ private Composite sensitivityClient; /** results client composite for dynamic update */ private Composite resultsClient; /** network name prefix */ private static final String NETWORK_LABEL = "Network: "; /** network name tool tip */ private static final String NETWORK_TOOL_TIP = "Network model name"; /** memento for section expanded */ private static final String MEMENTO_SECTION_CONTEXT = "memento.context"; //$NON-NLS-1$ /** memento for section expanded */ private static final String MEMENTO_SECTION_RESULTS = "memento.results"; //$NON-NLS-1$ /** memento for section expanded */ private static final String MEMENTO_SECTION_SENSITIVITY = "memento.sensitivity"; //$NON-NLS-1$ /** memento for section expanded */ private static final String MEMENTO_SECTION_ITEMS = "memento.items"; //$NON-NLS-1$ /** memento for file selection */ private static final String MEMENTO_FILE = "memento.file"; //$NON-NLS-1$ /** memento for filter state */ private static final String MEMENTO_FILTER_DISCRETE = "memento.filter.discrete"; //$NON-NLS-1$ /** memento for filter state */ private static final String MEMENTO_FILTER_CONTINUOUS = "memento.filter.continuous"; //$NON-NLS-1$ /** memento for filter state */ private static final String MEMENTO_FILTER_DECISION = "memento.filter.decision"; //$NON-NLS-1$ /** memento for filter state */ private static final String MEMENTO_FILTER_UTILITY = "memento.filter.utility"; //$NON-NLS-1$ /** number of columns */ private static final int COLUMN_COUNT = 2; /** sensitivity operator */ private String sensitivityOperator = SensitivityEngine.OPERATOR_GTE; /** sensitivity threshold text field */ private Text thresholdText; /** * Initializes the part. * Captures the selected file memento. * @param site IViewSite * @param memento IMemento * @throws PartInitException * @see org.eclipse.ui.IViewPart#init(IViewSite, IMemento) */ @Override public void init(final IViewSite site, IMemento memento) throws PartInitException { super.init(site, memento); this.memento = memento; // can be null } /** * Saves the view state. * The selected file and messages file are saved as a memento by file name. * @param memento memento to save * @see org.eclipse.ui.IViewPart#saveState(IMemento) */ @Override public void saveState(IMemento memento) { super.saveState(memento); memento.putString(MEMENTO_FILE, selectedFile == null ? null : selectedFile.getName()); memento.putBoolean(MEMENTO_SECTION_CONTEXT, context.isExpanded() ); memento.putBoolean(MEMENTO_SECTION_SENSITIVITY, sensitivity.isExpanded() ); memento.putBoolean(MEMENTO_SECTION_RESULTS, results.isExpanded() ); memento.putBoolean(MEMENTO_SECTION_ITEMS, items.isExpanded() ); memento.putBoolean(MEMENTO_FILTER_DISCRETE, itemDiscreteFilterMenuItem.getSelection()); memento.putBoolean(MEMENTO_FILTER_CONTINUOUS, itemContinuousFilterMenuItem.getSelection()); memento.putBoolean(MEMENTO_FILTER_DECISION, itemDecisionFilterMenuItem.getSelection()); memento.putBoolean(MEMENTO_FILTER_UTILITY, itemUtilityFilterMenuItem.getSelection()); } /** * Set the context IDs for help system. * Wait to call until site has been established. * @param control Control */ private void setHelpContextIDs(Control control) { IWorkbenchHelpSystem helpSystem = getSite().getWorkbenchWindow().getWorkbench().getHelpSystem(); helpSystem.setHelp(control, IHelpContext.SAMIAM_ANALYSIS_VIEW); } /** * Get the tree viewer. * Normally used for refreshing content from handlers. * @return tree viewer */ public TreeViewer getTreeViewer() { return treeViewer; } /** * Selection listener to sort columns. * @param tvc table column * @param sorter sorter to apply */ private void addSelectionSorter(final TreeViewerColumn tvc, final TreeSorter sorter) { tvc.getColumn().addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { sorter.setColumn(tvc.getColumn().getText()); treeViewer.refresh(true,true); TreeColumn c = (TreeColumn)e.getSource(); setColumnImages(sorter,c); treeViewer.getControl().redraw(); form.layout(true); } }); } /** * Set the column header images according to sort direction. * @param sorter tree sorter * @param sc selected column to assign image */ private void setColumnImages(TreeSorter sorter, TreeColumn sc) { // clear all current images for ( int c = 0; c < treeViewer.getTree().getColumnCount(); c++ ) { TreeColumn tc = treeViewer.getTree().getColumn(c); tc.setImage(null); } // set the selected column's image according to sort direction sc.setImage(Activator.getDefault().getImageRegistry().get( sorter.getDirection() == SWT.UP ? Activator.ASCENDING_IMAGE : Activator.DESCENDING_IMAGE )); } /** * Creates and handles a column header menu to change column visibility. * Item is enabled if column is resizable. * Item is initially selected if its width is greater than zero. * @param parent header menu * @param column column for menu choice */ private void createMenuItem(Menu parent, final TreeViewerColumn treeViewerColumn) { final MenuItem itemName = new MenuItem(parent, SWT.CHECK); final TreeColumn column = treeViewerColumn.getColumn(); itemName.setText(column.getText()); itemName.setEnabled( column.getResizable() ); itemName.setSelection( column.getWidth() > 0 ); itemName.addListener(SWT.Selection, new Listener() { public void handleEvent(Event event) { if (itemName.getSelection()) { column.setWidth(100); column.setResizable(true); } else { column.setWidth(0); column.setResizable(false); } } }); } /** * Refreshes the state object and its siblings in the tree view. * @param state state to refresh */ public void refreshViewerState(VariableNodeState state) { VariableNode parent = state.getContainer(); for ( VariableNodeState child : parent.states ) { treeViewer.refresh(child); // forces image change } } /** * Forces a re-evaluation of the properties for contribution states. * Evaluates the {@code isSelected} property. */ public void refreshViewProperties() { String property = "net.certware.evidence.hugin.isSelected"; //$NON-NLS-1$ IEvaluationService es = (IEvaluationService) PlatformUI.getWorkbench().getService(IEvaluationService.class); es.requestEvaluation(property); } /** * Layout the form after computations. */ public void layout() { Display.getDefault().asyncExec(new Runnable(){ public void run() { // form.reflow(true); results.getClient().pack(true); results.getParent().layout(true); } }); } /** * Create the view content using the forms widgets. */ @Override public void createPartControl(Composite parent) { TableWrapData twd; toolkit = Activator.getDefault().getFormToolkit(parent.getDisplay()); toolkit.getHyperlinkGroup().setHyperlinkUnderlineMode(HyperlinkSettings.UNDERLINE_HOVER); form = toolkit.createScrolledForm(parent); form.setText("Network Analysis"); form.setToolTipText("Select a Hugin network model"); toolkit.decorateFormHeading(form.getForm()); form.setImage(Activator.getDefault().getImageRegistry().getDescriptor(Activator.NETWORK_IMAGE).createImage()); clearMessage(); // layout TableWrapLayout layout = new TableWrapLayout(); layout.numColumns = 2; form.getBody().setLayout(layout); int sectionStyle = Section.TITLE_BAR | Section.TWISTIE | Section.EXPANDED; // Section.DESCRIPTION // context section context = toolkit.createSection(form.getBody(), sectionStyle); twd = new TableWrapData(TableWrapData.FILL); twd.colspan = 2; context.setLayoutData(twd); context.setText("Network Context"); context.setToolTipText("Network identification content"); context.addExpansionListener(new ExpansionAdapter() { @Override public void expansionStateChanged(ExpansionEvent e) { form.layout(true); form.reflow(true); } }); Composite contextClient = toolkit.createComposite(context); contextClient.setLayout(new GridLayout()); // network name networkHyperlink = toolkit.createHyperlink(contextClient, NETWORK_LABEL, SWT.WRAP); networkHyperlink.setEnabled(false); networkHyperlink.setToolTipText(NETWORK_TOOL_TIP); networkHyperlink.setFont(JFaceResources.getDialogFont()); networkHyperlink.addHyperlinkListener(new HyperlinkAdapter() { @Override public void linkActivated(HyperlinkEvent e) { FileOpener.findResourceEditor((IFile)e.getHref()); // Href contains the IFile from selection } }); context.setClient(contextClient); // sensitivity section sensitivity = toolkit.createSection(form.getBody(), sectionStyle); twd = new TableWrapData(TableWrapData.FILL); twd.colspan = 2; sensitivity.setLayoutData(twd); sensitivity.setText("Sentivity Analysis Settings"); sensitivity.setToolTipText("Calculation parameters for next analysis"); sensitivity.addExpansionListener(new ExpansionAdapter() { @Override public void expansionStateChanged(ExpansionEvent e) { form.layout(true); form.reflow(true); } }); sensitivityClient = toolkit.createComposite(sensitivity); sensitivityClient.setLayout(new GridLayout(2,false)); // sensitivity comparison Label operatorLabel = toolkit.createLabel(sensitivityClient, "Comparison Operator"); operatorLabel.setFont(JFaceResources.getDialogFont()); operatorLabel.setLayoutData(new GridData(SWT.BEGINNING,SWT.CENTER,false,false)); Composite operatorGroup = toolkit.createComposite(sensitivityClient); operatorGroup.setLayoutData(new GridData(SWT.BEGINNING,SWT.FILL,true,false)); operatorGroup.setLayout(new GridLayout(3,true)); Button operatorButtonEquals = toolkit.createButton(operatorGroup, "==",SWT.RADIO); operatorButtonEquals.setLayoutData(new GridData(SWT.BEGINNING,SWT.CENTER,false,false)); operatorButtonEquals.addSelectionListener(new SelectionAdapter() { public void widgetSelected(final SelectionEvent se) { se.display.asyncExec(new Runnable(){ @Override public void run() { Button b = (Button)se.widget; if ( b.getSelection() ) { sensitivityOperator = SensitivityEngine.OPERATOR_EQUALS; } }});}}); Button operatorButtonLessthan = toolkit.createButton(operatorGroup, "<=",SWT.RADIO); operatorButtonLessthan.setLayoutData(new GridData(SWT.BEGINNING,SWT.CENTER,false,false)); operatorButtonLessthan.addSelectionListener(new SelectionAdapter() { public void widgetSelected(final SelectionEvent se) { se.display.asyncExec(new Runnable(){ @Override public void run() { Button b = (Button)se.widget; if ( b.getSelection() ) { sensitivityOperator = SensitivityEngine.OPERATOR_LTE; } }});}}); Button operatorButtonGreaterthan = toolkit.createButton(operatorGroup, ">=",SWT.RADIO); operatorButtonGreaterthan.setLayoutData(new GridData(SWT.BEGINNING,SWT.CENTER,false,false)); operatorButtonGreaterthan.setSelection(true); // should align with field default operatorButtonGreaterthan.addSelectionListener(new SelectionAdapter() { public void widgetSelected(final SelectionEvent se) { se.display.asyncExec(new Runnable(){ @Override public void run() { Button b = (Button)se.widget; if ( b.getSelection() ) { sensitivityOperator = SensitivityEngine.OPERATOR_GTE; } }});}}); // sensitivity threshold Label thresholdLabel = toolkit.createLabel(sensitivityClient, "Comparison Threshold"); thresholdLabel.setFont(JFaceResources.getDialogFont()); thresholdLabel.setLayoutData(new GridData(SWT.BEGINNING,SWT.CENTER,false,false)); thresholdText = toolkit.createText(sensitivityClient, "0.8"); thresholdText.setFont(JFaceResources.getTextFont()); thresholdText.setLayoutData(new GridData(SWT.BEGINNING,SWT.CENTER,false,false)); thresholdText.setEditable(true); thresholdText.setMessage("Enter threshold value"); thresholdText.setTextLimit(8); thresholdText.setToolTipText("Enter threshold decimal value (0-1)"); thresholdText.addKeyListener(new KeyListener(){ /** * Allow only numbers and a period, along with traversals. * Idea is to help ensure parsing as double succeeds later when fetched. */ @Override public void keyPressed(KeyEvent ke) { ke.doit = true; if ( ke.character == '.') { // disallow more than one decimal point if ( thresholdText.getText().indexOf('.') >= 0 ) { ke.doit = false; } return; } if ( Character.isLetter( ke.character )) { ke.doit = false; } } @Override public void keyReleased(KeyEvent e) { }}); /* thresholdText.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent me) { String t = thresholdText.getText(); if ( t == null || t == "" ) { return; } try { Double.parseDouble(t); thresholdText.setMessage(""); } catch( NumberFormatException nfe ) { thresholdText.setMessage("Value must be decimal value"); } }}); */ sensitivity.setClient(sensitivityClient); sensitivity.setExpanded(false); // results section results = toolkit.createSection(form.getBody(), sectionStyle); twd = new TableWrapData(TableWrapData.FILL); twd.colspan = 2; results.setLayoutData(twd); results.setText("Analysis Results"); results.setToolTipText("Results from latest analysis"); results.addExpansionListener(new ExpansionAdapter() { @Override public void expansionStateChanged(ExpansionEvent e) { form.layout(true); form.reflow(true); } }); resultsClient = toolkit.createComposite(results); resultsClient.setLayout(new GridLayout(2,false)); results.setClient(resultsClient); // items section items = toolkit.createSection(form.getBody(), sectionStyle ); twd = new TableWrapData(TableWrapData.FILL_GRAB); twd.colspan = 2; twd.maxHeight = 500; items.setLayoutData(twd); items.addExpansionListener(new ExpansionAdapter() { @Override public void expansionStateChanged(ExpansionEvent e) { form.layout(true); form.reflow(true); } }); items.setText("Structure Items"); items.setToolTipText("Network nodes"); Composite treeClient = toolkit.createComposite(items); treeClient.setLayout(new FillLayout()); // create the table treeViewer = new TreeViewer(treeClient, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.FULL_SELECTION); treeViewer.getTree().setLinesVisible(true); treeViewer.getTree().setHeaderVisible(true); treeViewer.addSelectionChangedListener(new ISelectionChangedListener(){ @Override public void selectionChanged(SelectionChangedEvent event) { ISelection selection = event.getSelection(); if ( selection instanceof IStructuredSelection ) { IStructuredSelection iss = (IStructuredSelection)selection; Object first = iss.getFirstElement(); if ( first instanceof VariableNode ) { VariableNode n = (VariableNode)first; n.setSelected( ! n.isSelected() ); // toggle treeViewer.refresh(n); // forces image change } if ( first instanceof VariableNodeState ) { VariableNodeState s = (VariableNodeState)first; s.clearSiblings(); // clear other states, mutually exclusive s.setSelected( ! s.isSelected() ); // toggle // update images in the container set refreshViewerState(s); } // force refresh of menu item conditional tests refreshViewProperties(); } }}); GridData gridData = new GridData(GridData.FILL_BOTH); gridData.grabExcessVerticalSpace = true; gridData.horizontalSpan = 3; final Menu headerMenu = new Menu(treeViewer.getControl()); // focus manager TreeViewerFocusCellManager focusCellManager = new TreeViewerFocusCellManager(treeViewer, new FocusCellOwnerDrawHighlighter(treeViewer)); ColumnViewerEditorActivationStrategy actSupport = new ColumnViewerEditorActivationStrategy(treeViewer) { protected boolean isEditorActivationEvent(ColumnViewerEditorActivationEvent event) { return event.eventType == ColumnViewerEditorActivationEvent.TRAVERSAL || event.eventType == ColumnViewerEditorActivationEvent.MOUSE_DOUBLE_CLICK_SELECTION || (event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && event.keyCode == SWT.CR) || event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC; } }; TreeViewerEditor.create(treeViewer, focusCellManager, actSupport, ColumnViewerEditor.TABBING_HORIZONTAL | ColumnViewerEditor.TABBING_MOVE_TO_ROW_NEIGHBOR | ColumnViewerEditor.TABBING_VERTICAL | ColumnViewerEditor.KEYBOARD_ACTIVATION); // final TextCellEditor textCellEditor = new TextCellEditor(treeViewer.getTree()); // column 0 TreeViewerColumn idColumn = new TreeViewerColumn(treeViewer, SWT.NONE); idColumn.getColumn().setWidth(200); idColumn.getColumn().setMoveable(true); idColumn.getColumn().setResizable(true); idColumn.getColumn().setText("Nodes/Values"); createMenuItem(headerMenu,idColumn); idColumn.setLabelProvider(new ColumnLabelProvider() { /** * Get the column cell image. * If item is selected, return a distinguishing image. */ public Image getImage(Object element) { if ( element instanceof VariableNode ) { if ( ((VariableNode)element).isSelected() ) { return Activator.getDefault().getImageRegistry().get(Activator.SELECTED_NODE_IMAGE); } return Activator.getDefault().getImageRegistry().get(Activator.UNSELECTED_NODE_IMAGE); } if ( element instanceof VariableNodeState ) { if ( ((VariableNodeState)element).isSelected() ) { return Activator.getDefault().getImageRegistry().get(Activator.SELECTED_NODE_IMAGE); } return Activator.getDefault().getImageRegistry().get(Activator.UNSELECTED_NODE_IMAGE); } return null; } /** * Get the column cell text. */ public String getText(Object element) { if ( element instanceof VariableNode ) { HuginNode hn = ((VariableNode)element).getNode(); String text = String.format("%s: %s",hn.getID(),hn.getLabel()); return text; } if ( element instanceof VariableNodeState ) { return ((VariableNodeState)element).getStateName(); } return "c0"; //$NON-NLS-1$ } }); /* idColumn.setEditingSupport(new EditingSupport(treeViewer) { protected boolean canEdit(Object element) { return false; } protected CellEditor getCellEditor(Object element) { return textCellEditor; } protected Object getValue(Object element) { return ((MyModel) element).counter + ""; } protected void setValue(Object element, Object value) { ((MyModel) element).counter = Integer .parseInt(value.toString()); treeViewer.update(element, null); } }); */ // column 1 TreeViewerColumn typeColumn = new TreeViewerColumn(treeViewer, SWT.NONE); typeColumn.getColumn().setWidth(200); typeColumn.getColumn().setMoveable(true); typeColumn.getColumn().setText("Type"); createMenuItem(headerMenu,typeColumn); typeColumn.setLabelProvider(new ColumnLabelProvider() { public String getText(Object element) { if ( element instanceof VariableNode ) { HuginNode hn = ((VariableNode)element).getNode(); int type = hn.getValueType(); switch( type ) { case HuginNode.DISCRETE: return "Discrete"; case HuginNode.CONTINUOUS: return "Continuous"; case HuginNode.DECISION: return "Decision"; case HuginNode.UTILITY: return "Utility"; default: return Integer.toString(type); } } return ""; //$NON-NLS-1$ } }); /* typeColumn.setEditingSupport(new EditingSupport(treeViewer) { protected boolean canEdit(Object element) { return false; } protected CellEditor getCellEditor(Object element) { return textCellEditor; } protected Object getValue(Object element) { return ((MyModel) element).counter + ""; } protected void setValue(Object element, Object value) { ((MyModel) element).counter = Integer.parseInt(value.toString()); treeViewer.update(element, null); } }); */ // column 2 TreeViewerColumn marginalColumn = new TreeViewerColumn(treeViewer, SWT.NONE); marginalColumn.getColumn().setWidth(200); marginalColumn.getColumn().setMoveable(true); marginalColumn.getColumn().setText("Marginal"); createMenuItem(headerMenu,marginalColumn); marginalColumn.setLabelProvider(new ColumnLabelProvider() { public String getText(Object element) { if ( element instanceof VariableNodeState ) { VariableNodeState vns = (VariableNodeState)element; return Double.toString(vns.getMarginal()); } return ""; //$NON-NLS-1$ } }); // table sorting on columns TreeSorter sorter = new TreeSorter(treeViewer); treeViewer.setSorter(sorter); addSelectionSorter(idColumn, sorter); addSelectionSorter(typeColumn, sorter); addSelectionSorter(marginalColumn, sorter); setColumnImages(sorter, idColumn.getColumn()); // content treeViewer.setContentProvider(new NetworkContentProvider()); treeViewer.setInput(selectedNetwork); // add filters to the header menu createMenuSeparator(headerMenu); ViewerFilter discreteFilter = new ResultFilter(0); ViewerFilter continuousFilter = new ResultFilter(1); ViewerFilter decisionFilter = new ResultFilter(2); ViewerFilter utilityFilter = new ResultFilter(3); itemDiscreteFilterMenuItem = createMenuFilterItem(headerMenu,"Hide Discrete",discreteFilter,false); itemContinuousFilterMenuItem = createMenuFilterItem(headerMenu,"Hide Continuous",continuousFilter,false); itemDecisionFilterMenuItem = createMenuFilterItem(headerMenu,"Hide Decision",decisionFilter,false); itemUtilityFilterMenuItem = createMenuFilterItem(headerMenu,"Hide Utility",utilityFilter,false); items.setClient(treeClient); // create the help context id for the viewer's control PlatformUI.getWorkbench().getHelpSystem().setHelp(form, IHelpContext.SAMIAM_ANALYSIS_VIEW); // add the selection listener // add the resource listener // add editor listener getSite().getWorkbenchWindow().getSelectionService().addSelectionListener(this); ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE ); activeEditorListener = new ActiveEditorListener(this); getSite().getPage().addPartListener(activeEditorListener); // sync with toggle commands setLinkingEditor( LinkEditor.getState() ); // update with memento if available if ( memento != null ) { // file name String fileName = memento.getString(MEMENTO_FILE); if ( fileName != null ) { // && getLinkingEditor() == false ) { selectedFile = FileFinder.findResourceByName(fileName); setSelectedFile(selectedFile); getViewSite().getActionBars().getToolBarManager().markDirty(); } // sections expanded context.setExpanded( memento.getBoolean(MEMENTO_SECTION_CONTEXT) ); items.setExpanded( memento.getBoolean(MEMENTO_SECTION_ITEMS) ); sensitivity.setExpanded( memento.getBoolean( MEMENTO_SECTION_SENSITIVITY)); // filter states // set the menu item toggle and add the filter if ( memento.getBoolean(MEMENTO_FILTER_DISCRETE) ) { itemDiscreteFilterMenuItem.setSelection( true ); treeViewer.addFilter(discreteFilter); } if ( memento.getBoolean(MEMENTO_FILTER_CONTINUOUS) ) { itemContinuousFilterMenuItem.setSelection( true ); treeViewer.addFilter(continuousFilter); } if ( memento.getBoolean(MEMENTO_FILTER_DECISION) ) { itemDecisionFilterMenuItem.setSelection( true ); treeViewer.addFilter(decisionFilter); } if ( memento.getBoolean(MEMENTO_FILTER_UTILITY) ) { itemUtilityFilterMenuItem.setSelection( true ); treeViewer.addFilter(utilityFilter); } } setHelpContextIDs(parent); form.layout(); } /** * Clears the results client by disposing of its children. */ public void clearResults() { Display.getDefault().asyncExec(new Runnable(){ public void run() { for ( Control c : resultsClient.getChildren() ) { c.dispose(); } results.layout(true); } }); } /** * Adds a results client entry of one label and one value. * Presumes a grid layout with two columns. * @param label row label, first column * @param value row value, second column */ public void addResult(final String label, final String value) { Display.getDefault().asyncExec(new Runnable(){ public void run() { Label l1 = toolkit.createLabel(resultsClient, label); l1.setLayoutData(new GridData(SWT.BEGINNING,SWT.CENTER,false,false,1,1)); Label l2 = toolkit.createLabel(resultsClient, value); l2.setLayoutData(new GridData(SWT.BEGINNING,SWT.CENTER,false,false,1,1)); } }); } /** * Adds all results client entries of one label and one value. * Presumes a grid layout with two columns. * We use a {@code LinkedHashMap} here so results appear in same order as loaded. * @param rows map of labels and values */ public void addResult(final LinkedHashMap<String,String> rows) { Display.getDefault().asyncExec(new Runnable(){ public void run() { for ( Control c : resultsClient.getChildren() ) { c.dispose(); } Set<String> keys = rows.keySet(); for ( Object s : keys.toArray() ) { String label = (String)s; String value = (String)rows.get(s); Label l1 = toolkit.createLabel(resultsClient, label); l1.setLayoutData(new GridData(SWT.BEGINNING,SWT.CENTER,false,false,1,1)); Label l2 = toolkit.createLabel(resultsClient, value); l2.setLayoutData(new GridData(SWT.BEGINNING,SWT.CENTER,false,false,1,1)); } results.layout(true); form.reflow(true); }}); } /** * Sensitivity analysis operator selection. * @return operator, one of values from {@code SensitivityEngine} */ public String getSensitivityOperator() { return sensitivityOperator; } /** * Sensitivity analysis threshold value. * Writes to widget message if parsing fails. * @return value, if parsed from text string as double */ public double getSensitivityThreshold() { double rv = 0.0; String sv = thresholdText.getText(); if ( sv != null && sv != "" ) { try { rv = Double.parseDouble(sv); } catch( NumberFormatException nfe ) { thresholdText.setMessage("Threshold value invalid"); } } return rv; } /** * Creates a menu item separator for the column header. * @param parent column header menu */ private void createMenuSeparator(Menu parent) { new MenuItem(parent, SWT.SEPARATOR ); } /** * Creates a menu item for adding or removing a filter from the column header menu. * Item is enabled and its selection depends on argument. * @param parent column header menu * @param label filter name label * @param filter viewer filter for addition or removal * @param selection current selection value * @return filter menu item */ private MenuItem createMenuFilterItem(Menu parent, String label, final ViewerFilter filter, boolean selection) { final MenuItem itemName = new MenuItem(parent, SWT.CHECK); itemName.setText(label); itemName.setEnabled( true ); itemName.setSelection( selection ); itemName.addListener(SWT.Selection, new Listener() { public void handleEvent(Event event) { if (itemName.getSelection()) { treeViewer.addFilter(filter); } else { treeViewer.removeFilter(filter); } } }); return itemName; } /** * Return the selected file. * @return selected file, or null */ public IFile getSelectedFile() { return selectedFile; } /** * Sets the selected network. * @param bn network to assign to viewer content */ protected void setSelectedNetwork(BeliefNetwork bn) { this.selectedNetwork = bn; } /** * Sets the form information message. * @param message message for form header */ public void setInfoMessage(final String message) { Display.getDefault().asyncExec(new Runnable(){ public void run() { form.setMessage(message, IMessageProvider.INFORMATION); }}); } /** * Sets the form warning message. * @param message message for form header */ public void setWarningMessage(final String message) { Display.getDefault().asyncExec(new Runnable(){ public void run() { form.setMessage(message, IMessageProvider.WARNING); }}); } /** * Sets the form error message. * @param message message for form header */ public void setErrorMessage(final String message) { Display.getDefault().asyncExec(new Runnable(){ public void run() { form.setMessage(message, IMessageProvider.ERROR); }}); } /** * Clears the form information message. */ public void clearMessage() { Display.getDefault().asyncExec(new Runnable(){ public void run() { form.setMessage(null, IMessageProvider.NONE); //$NON-NLS-1$ }}); } /** * Sets the selected file. * @param f selected file * @return */ public boolean setSelectedFile(final IFile ifile) { if ( ifile == null ) return false; // if the file is the right type, open it and update the view String extension = ifile.getFileExtension(); if (extension != null && ICertWareConstants.NET_EXTENSION.equals(extension)) { try { /* // load the network file by parsing the DSL NetworkIO.BeliefNetworkIOListener bnil = new NetworkIO.BeliefNetworkIOListener() { @Override public void handleSyntaxErrors(String[] errors, FileType filetype) { CertWareLog.logWarning(String.format("%s %s","Syntax error reading file",ifile.getName())); for ( String s : errors ) { CertWareLog.logWarning(s); } } @Override public void handleProgress(ProgressMonitorable readTask, Estimate estimate) { } @Override public void handleNewBeliefNetwork(BeliefNetwork bn, File f) { // threadNetwork = bn; setSelectedNetwork(bn); } @Override public void handleCancelation() { } @Override public void handleBeliefNetworkIOError(String msg) { CertWareLog.logWarning(String.format("%s %s",msg,ifile.getName())); } @Override public ThreadGroup getThreadGroup() { return null; } }; */ //Thread thread = NetworkIO.readHuginNet(ifile.getContents(),ifile.getName(),bnil); //thread.run(); // this version handles influence diagram nodes better than Hugin network read // BeliefNetwork bn = NetworkIO.read( ifile.getFullPath().toFile() ); BeliefNetwork bn = ReadModelFile.readNetwork(ifile); setSelectedNetwork(bn); if ( getSelectedNetwork() != null ) { selectedFile = ifile; // selectedNetwork = threadNetwork; updateView(); } else { CertWareLog.logWarning(String.format("%s %s","Unsupported network file selected",ifile.getName())); } } catch( Exception exception ) { CertWareLog.logError(String.format("%s %s","Network null loading",ifile), exception); selectedFile = null; return false; } return true; } // if return false; } /** * Return the selected model. * @return selected model, or null */ public BeliefNetwork getSelectedNetwork() { return selectedNetwork; } /** * Set the form focus. */ @Override public void setFocus() { form.setFocus(); } /** * Run the save handler. * @param progress monitor, unused */ @Override public void doSave(IProgressMonitor monitor) { String commandId = "net.certware.evidence.hugin.view.save"; //$NON-NLS-1$ try { IHandlerService handlerService = (IHandlerService) getSite().getService(IHandlerService.class); handlerService.executeCommand(commandId, null); setDirty(false); } catch( Exception e ) { CertWareLog.logError("Saving network model",e); } } /** * Save as not supported, no changes to model made here. Copy the resource instead. */ @Override public void doSaveAs() { // not needed } /** * Returns the dirty flag. * @return dirty flag */ @Override public boolean isDirty() { return dirty; } /** * Sets the dirty flag * @param d dirty flag new value */ public void setDirty(boolean d) { dirty = d; } /** * Whether save as allowed. * @return always returns false */ @Override public boolean isSaveAsAllowed() { return false; } /** * Whether save on close is needed. * @return defers to isDirty() */ @Override public boolean isSaveOnCloseNeeded() { return isDirty(); } /** * Responds to resource changed events. * Listens to removed and changed events. * If removed, clears the view. * If changed, updates the display with previous selection and new content. * @param event resource change event */ @Override public void resourceChanged(IResourceChangeEvent event) { final IPreferenceStore ps = Activator.getDefault().getPreferenceStore(); try { event.getDelta().accept(new IResourceDeltaVisitor() { public boolean visit(IResourceDelta delta) throws CoreException { if ( delta.getKind() == IResourceDelta.REMOVED ) { // selected results file if ( selectedFile != null ) if ( delta.getResource() instanceof IFile && selectedFile.equals(delta.getResource()) ) { latestSelection = null; selectionChanged(null,latestSelection); // refresh the display with no selection clearView(); } } // removed else if ( delta.getKind() == IResourceDelta.CHANGED ) { if ( selectedFile != null ) if ( ps.getBoolean( PreferenceConstants.P_NETWORK_VIEW_REFRESH_ON_RESOURCE_CHANGE ) ) if ( delta.getResource() instanceof IFile && selectedFile.equals(delta.getResource()) ) { selectionChanged(null,latestSelection); // refresh the display } } // changed return true; } }); } catch( CoreException ce ) { CertWareLog.logWarning(String.format("%s: %s","Exception refreshing selected network file",ce.getMessage())); } } /** * Updates the network table content. */ protected void updateNetworkTable() { if ( selectedNetwork == null ) return; treeViewer.setInput( selectedNetwork ); treeViewer.refresh(); treeViewer.getControl().pack(true); } /** * Clears the contents of the view. * Used primarily when the associate resource becomes unavailable. */ protected void clearView() { if ( networkHyperlink == null ) return; context.getDisplay().asyncExec(new Runnable(){ public void run() { networkHyperlink.setText(NETWORK_LABEL + "<none>"); networkHyperlink.setEnabled(false); treeViewer.getTree().setItemCount(0); treeViewer.refresh(); form.reflow(true); } }); } /** * Update the view. */ protected void updateView() { if ( selectedNetwork == null ) return; networkHyperlink.getDisplay().asyncExec(new Runnable() { public void run() { try { // update the context section // always redraw the strings to erase any previous content // the form composite requires re-packing to reflect the new boundaries networkHyperlink.setText(NETWORK_LABEL + selectedFile.getName()); networkHyperlink.setHref( selectedFile ); networkHyperlink.setEnabled(true); networkHyperlink.pack(true); updateNetworkTable(); // refresh the form layout form.reflow(true); } catch( SWTException e ) { // ignore disposed cases, pass along others if ( e.code != SWT.ERROR_DEVICE_DISPOSED ) throw e; } } }); } /** * Prompt to save on close. * @return default prompt for workbench */ @Override public int promptToSaveOnClose() { // use the workbench default prompt return ISaveablePart2.DEFAULT; } /** * Process a selection change. * @param part workbench part * @param selection selection to process */ @SuppressWarnings("unchecked") @Override public void selectionChanged(IWorkbenchPart part, ISelection selection) { // skip selection if not structured if ( ! (selection instanceof IStructuredSelection )) return; // skip repeat selections if ( latestSelection == selection ) { return; } IStructuredSelection iss = (IStructuredSelection)selection; // selecting from the explorer if ( iss.getFirstElement() instanceof IFile ) { IFile newSelection = (IFile)iss.getFirstElement(); if ( newSelection.getFileExtension().endsWith(ICertWareConstants.NET_EXTENSION) ) { if ( setSelectedFile(newSelection) ) { latestSelection = selection; } } return; } // selecting from our node tree for ( Iterator<Object> i = iss.iterator(); i.hasNext(); ) { Object o = i.next(); if ( o instanceof VariableNode ) { ((VariableNode)o).setSelected(true); continue; } if ( o instanceof VariableNodeState ) { VariableNodeState vns = (VariableNodeState)o; vns.setSelected(true); continue; } } } /** * Dispose of listeners and other resources. * The plugin takes care of the forms toolkit. * @see org.eclipse.ui.IWorkbenchPart#dispose() */ @Override public void dispose() { getSite().getWorkbenchWindow().getSelectionService().removeSelectionListener(this); if ( activeEditorListener != null ) getSite().getPage().removePartListener(activeEditorListener); ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); super.dispose(); } /** * Return the latest selection, an {@code IStructuredSelection}. * @return latest selection */ public ISelection getSelection() { return this.latestSelection; } /** * Returns whether the view is linking the editor. * @return true if linking editor */ public boolean getLinkingEditor() { return isLinkingEditor; } /** * Sets the linking editor state. * @param le true if the view should listen to the active editor */ public void setLinkingEditor(boolean le) { isLinkingEditor = le; } /** * Adapter to identify context provider. * @param adapter context provider type desired * @return context provider * @see org.eclipse.core.runtime.IAdaptable#getAdapter(Class) */ @Override public Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) { if (IContextProvider.class.equals(adapter)) { return new ContextProvider(this, IHelpContext.SAMIAM_ANALYSIS_VIEW, (IStructuredSelection) getSelection()); } return super.getAdapter(adapter); } @Override public String getDefaultExtension() { return ICertWareConstants.NET_EXTENSION; } /** * Whether the tree content has selected variable items. * @return true if any variable selected */ public boolean hasVariableSelections() { if ( treeViewer != null ) { NetworkContentProvider ncp = (NetworkContentProvider)treeViewer.getContentProvider(); if ( ncp != null ) { for ( VariableNode v : ncp.roots ) { if ( v.isSelected() ) { return true; } } } } return false; } /** * Whether settings for sensitivity calculations are selected. * @return true if settings sufficient, false otherwise */ public boolean hasSensitivitySelections() { return true; } /** * Whether the tree content has selected variable state items. * @return true if any variable selected */ public boolean hasVariableStateSelections() { if ( treeViewer != null ) { NetworkContentProvider ncp = (NetworkContentProvider)treeViewer.getContentProvider(); if ( ncp != null ) { for ( VariableNode v : ncp.roots ) { for ( VariableNodeState s : v.states ) { if ( s.isSelected() ) { return true; } } } } } return false; } /** * Returns the list of variable nodes from the tree viewer. * @return list of variable nodes or null if viewer or content provider not established */ public List<VariableNode> getVariableNodes() { if ( treeViewer != null ) { NetworkContentProvider ncp = (NetworkContentProvider)treeViewer.getContentProvider(); if ( ncp != null ) { return ncp.roots; } } return null; } /** * Tree network content provider. * @author mrb */ private class NetworkContentProvider implements ITreeContentProvider { /** the master model behind the tree */ private BeliefNetwork input = null; /** the top-level items in the viewer are the network variables */ ArrayList<VariableNode> roots = new ArrayList<VariableNode>(); /** * Translates the belief network node list into our local array for display. * @param inputElement the selected resource, presumed to be a {@code BeliefNetwork} * @return array of {@code MyNode} elements, or empty object list */ public Object[] getElements(Object inputElement) { if ( inputElement instanceof BeliefNetwork ) { BeliefNetwork bn = (BeliefNetwork)inputElement; roots.clear(); for ( Object o : bn.topologicalOrder().toArray() ) { roots.add(new VariableNode((HuginNode)o)); } return roots.toArray(); } return new Object[0]; } /** * Dispose of the content provider. Unused. */ public void dispose() { } /** * Model input changed. Saves the reference to the new input model. * @param viewer tree viewer, unused * @param oldInput old input model, unused * @param newInput new input model, reference saved, presumed to be a {@code BeliefNetwork} item */ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { if ( newInput instanceof BeliefNetwork ) { input = (BeliefNetwork)newInput; } else { // System.err.println("tcp input changed invalid input " + newInput); } } /** * Get the children of the given parent element. * @param parentElement parent element reference * @return {@getElements} if {@code BeliefNetwork}, list of {@code MyState} if {@code MyNode}, empty array otherwise */ @Override public Object[] getChildren(Object parentElement) { if ( parentElement instanceof BeliefNetwork ) { return getElements(parentElement); } if ( parentElement instanceof VariableNode ) { VariableNode n = (VariableNode)parentElement; return n.getStates(); } if ( parentElement instanceof VariableNodeState ) { return new Object[0]; } return new Object[0]; } /** * Get the parent of the given element. * @param element tree element reference * @return {@code MyNode} if element is {@code MyState}, {@code input} if {@code MyNode}, null if null */ public Object getParent(Object element) { if (element == null) { return null; } // find the parent by linear search if ( element instanceof VariableNodeState ) { for ( VariableNode n : roots ) { if ( n.states.contains((VariableNodeState)element)) { return n; } } } // node returns the network parent return input; } /** * Whether the given element has children * @param element element expecting {@code BeliefNetwork} or {@code MyNode} * @return true if element has array children, false otherwise */ public boolean hasChildren(Object element) { if ( element instanceof BeliefNetwork ) { return roots.size() > 0; } if ( element instanceof VariableNode ) { VariableNode n = (VariableNode)element; return n.states.size() > 0; } return false; } } /** * A viewer sorter for the <code>ViewTree</code> viewer model. * @author mrb */ static public class TreeSorter extends ViewerSorter { /** which column number to sort */ private int propertyIndex = 0; /** direction of sort, up is ascending */ private int direction = SWT.UP; /** table viewer reference */ private final TreeViewer treeViewer; /** * Constructor sets first column sort ascending. * @param tableViewer reference to table viewer */ public TreeSorter(TreeViewer tv) { this.propertyIndex = 0; this.treeViewer = tv; direction = SWT.UP; } /** * Sets the sort column given the column name. * Calls <code>setColumn(int)</code> with the associated column number. * @param columnName column name */ public void setColumn(String columnName) { for ( int i = 0; i < COLUMN_COUNT ; i++ ) { if ( treeViewer.getTree().getColumn(i).getText().equalsIgnoreCase(columnName)) { setColumn(i); return; } } } /** * Gets the current sort direction. * Up direction is ascending, down direction is descending. * @return one of <code>SWT.UP</code> or <code>SWT.DOWN</code> */ public int getDirection() { return direction; } /** * Sets the current sort direction. * Does not re-sort. * Up direction is ascending, down direction is descending. * @param d direction, one of SWT.UP or SWT.DOWN */ public void setDirection(int d) { direction = d; } /** * Sets the sort column given the column number. * If the column number is already set, reverses direction. * @param column column number */ public void setColumn(int column) { if (column == this.propertyIndex) { // same column as last sort; toggle the direction direction = direction == SWT.UP ? SWT.DOWN : SWT.UP; } else { // new sort column; do an ascending sort this.propertyIndex = column; direction = SWT.UP; } } /** * Compares objects for sorting. * Uses currently selected property column. * Presumes using the <code>NetworkModel</code> object model. * @param viewer line value model viewer * @param e1 object compare one, a line value model object * @param e2 object compare two, a line value model object * @return 1 for e1>e2, -1 for e1<e2, 0 for e1=e2 */ @Override public int compare(Viewer viewer, Object e1, Object e2) { if ( e1 instanceof VariableNodeState ) { VariableNodeState s1 = (VariableNodeState)e1; VariableNodeState s2 = (VariableNodeState)e2; if ( propertyIndex == 2 ) { return s1.getMarginal() > s2.getMarginal() ? 1 : -1; } return s1.getStateName().compareTo(s2.getStateName()); } VariableNode p1 = (VariableNode)e1; VariableNode p2 = (VariableNode)e2; int rc = 0; switch (propertyIndex) { case 0: // id rc = p1.getNode().getID().compareTo(p2.getNode().getID()); break; case 1: // value type int v1 = p1.getNode().getValueType(); int v2 = p2.getNode().getValueType(); if ( v1 == v2 ) rc = 0; else rc = v1 > v2 ? 1 : -1; break; default: rc = 0; } // if descending order, flip the direction if (direction == SWT.DOWN) { rc = -rc; } return rc; } } /** * Provides a viewer filter for node type choice values. */ static public class ResultFilter extends ViewerFilter { /** choice code from result value type enumeration */ int type; /** * Result filter creates viewer filter and sets its type. * The type is expected to be one of the {@code HuginNode} node type constants. * @param type */ public ResultFilter(int type) { super(); this.type = type; } /** * Applies the filter on the selection. * Matches the model's result field value to the value type from the constructor. * @return false if selected line model matches choice, true otherwise */ @Override public boolean select(Viewer viewer, Object parentElement, Object element) { VariableNode p = (VariableNode) element; if ( p == null ) return true; if ( p.getNode().getValueType() == type ) return false; return true; } } }