/** * This file is part of Archiv-Editor. * * The software Archiv-Editor serves as a client user interface for working with * the Person Data Repository. See: pdr.bbaw.de * * The software Archiv-Editor was developed at the Berlin-Brandenburg Academy * of Sciences and Humanities, Jägerstr. 22/23, D-10117 Berlin. * www.bbaw.de * * Copyright (C) 2010-2013 Berlin-Brandenburg Academy * of Sciences and Humanities * * The software Archiv-Editor was developed by @author: Christoph Plutte. * * Archiv-Editor is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Archiv-Editor is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Archiv-Editor. * If not, see <http://www.gnu.org/licenses/lgpl-3.0.html>. */ package org.bbaw.pdr.ae.export.swt.preview; import java.util.Collections; import java.util.HashSet; import java.util.Vector; import org.bbaw.pdr.ae.common.AEConstants; import org.bbaw.pdr.ae.common.CommonActivator; import org.bbaw.pdr.ae.export.logic.PdrObjectsPreviewStructure; import org.bbaw.pdr.ae.export.logic.StructNode; import org.bbaw.pdr.ae.export.pluggable.AeExportCoreProvider; import org.bbaw.pdr.ae.export.swt.IPdrWidgetStructure; import org.bbaw.pdr.ae.model.Aspect; import org.bbaw.pdr.ae.model.PdrObject; import org.bbaw.pdr.ae.model.Person; import org.bbaw.pdr.ae.model.ReferenceMods; import org.bbaw.pdr.ae.view.control.PDRObjectsProvider; import org.eclipse.core.runtime.ILog; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTreeViewer; import org.eclipse.jface.viewers.IBaseLabelProvider; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ITreeViewerListener; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreeExpansionEvent; import org.eclipse.jface.wizard.IWizardContainer; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Tree; import org.osgi.framework.FrameworkUtil; //TODO: doc /** * Important: Must be the only child of parent composite! (due to including * TreeColumnLayout) * <p><b>Also important:</b> CheckboxTreeViewer is actually not supposed to be * subclassed. <i>"It is designed to be instantiated with a pre-existing SWT tree control".</i> * Are we gonna get away with this...?</p> * @author jhoeper * @see {@link CheckboxTreeViewer}, {@link IPdrWidgetStructure} * */ public class PdrSelectionFilterPreview extends CheckboxTreeViewer implements IPdrWidgetStructure { /** * This Listener takes care of which tree items are being * selected and in what way a new selection is different from the one * before. * @author jhoeper * @see PdrPreviewCheckStateListener */ private class PdrPreviewSelectionListener implements ISelectionChangedListener { private StructuredSelection previous, current; public PdrPreviewSelectionListener() { addSelectionChangedListener(this); previous = null; current = new StructuredSelection(); } @Override public void selectionChanged(SelectionChangedEvent event) { StructuredSelection arg = (StructuredSelection)event.getSelection(); previous = current; current = arg; } /** * Returns a presumption on whether it seems like a multi-item selection * put together recently has collapsed even more recently due to a user * interaction that probably was performed with the intention of keeping * said selection. * <p>The most likely use case to require this is that a user has * selected multiple tree items and wants to change the <code>check * state</code> value of every selected item simultaneously. Any action * leading to an updated item check state will also result in a new * single item selection, so {@link PdrPreviewCheckStateListener} * probably will {@link #rewind()} the selection history in order to * operate on every of the intentionally selected items.</p> * @return <code>true</code> if the current selection seems to have * replaced a larger one */ public boolean rewindable() { return (current.size() == 1) && (previous.size() > 1) && (previous.toList().contains(current.getFirstElement())); } /** * Backs up one step in selection history by restoring the very * selection that has been replaced the most recently. */ public void rewind() { setSelection(previous); } } /** * <p><i>Note: * Problems might arise from the current implementation, as it always calls * {@link PdrSelectionFilterPreview#setChecked(Object, boolean)}, which * itself delegates node-wise consistency maintenance to * {@link PdrSelectionFilterPreview#setChecked(Object, boolean, boolean)}, * from which recursive method {@link PdrSelectionFilterPreview#expandAndSelectDescendants(StructNode, boolean)} * is called to traverse the affected subtree down to its leaves. This actually should * not be done recursively. TODO: Is it possible to rather expand the concerned * subtree entirely and iterate over the exposed items to archieve consistency? * </i></p> * * This listener keeps the hierarchic checking logic of the preview tree * up to date. Whenever the check state of an item changes, the check states * of related items are updated according to the following rules: * <ul><li>If the item whose check state was changed is expandable, assign * its check state to all its direct children</li> * <li>If the item is representing an {@link Aspect}, and its check state * has been set to <code>true</code>, make sure the parent gets checked * as well in order to prevent <code>Aspect</code> items from ending up * being selected for export with no {@link Person} alongside itself</li> * <li>If multiple items are selected at once, have them all being of the * check state that was changed</li></ul> * <p>The last rule is enforced by asking {@link * PdrPreviewSelectionListener} if the user's interaction that resulted in * an updated check state might have destroyed a recently set up multi-item * selection. If so, we assume that the user did actually intend to apply * the outcome of their interaction to each item formerly assembled by the * multi-item selection. To make this implicitly expected result come true, * we {@link PdrPreviewSelectionListener#rewind()} the most recent selection * history and proceed as specified above.</p> * <p>Both <code>PdrPreviewCheckStateListener</code> and {@link * PdrPreviewSelectionListener} auto-activate themselves as soon as they * are being instantiated by {@link PdrSelectionFilterPreview}</p> * * @author jhoeper * @see PdrPreviewSelectionListener */ private class PdrPreviewCheckStateListener implements ICheckStateListener { public PdrPreviewCheckStateListener() { addCheckStateListener(this); } public void checkStateChanged(CheckStateChangedEvent event) { HashSet<StructNode> affects = new HashSet<StructNode>(); // if there is more than one item selected, assign the same // checked status to all of them // TODO: more than one? if (selectionListener.rewindable()) { selectionListener.rewind(); StructuredSelection selectedItems = (StructuredSelection)getSelection(); for (Object obj : selectedItems.toArray()) //setChecked(obj, event.getChecked()); affects.addAll(previewStructure.setSelected(obj, event.getChecked())); } else { Object obj = event.getElement(); affects.addAll(previewStructure.setSelected(obj, event.getChecked())); } /*if (affects.size()>0) { System.out.println(" new check state: "+event.getChecked()); System.out.println(" affected relatives: "); for (StructNode a : affects) System.out.println(" "+a.getLabel()+" -> "+a.isSelected()); }*/ update(affects.toArray(new Object[affects.size()]), null); // setChecked(obj, event.getChecked()); // TODO: how expensive is this operation and how can it be improved? validateSelection(); } } /** * Handles changes in expansion state * @author jhoeper * */ private class PdrPreviewExpansionListener implements ITreeViewerListener { public PdrPreviewExpansionListener() { addTreeListener(this); } @Override public void treeCollapsed(TreeExpansionEvent event) { StructNode node = (StructNode)event.getElement(); node.setExpanded(false); } @Override public void treeExpanded(TreeExpansionEvent event) { StructNode node = (StructNode)event.getElement(); node.setExpanded(true); } } //TODO Maybe extend widget in a way that export to multiple files at once is possible /* In order to allow for choosing to export multiple pdr persons in multiple files * at once, this class would then have to inform the core export plugin, which crafts * the PdrObject selection representation used by extending plugins, about * that choice, possibly via the local export provider. */ private ILog log = AEConstants.ILOGGER; // PdrSelectionPreview private fields private Tree tree; //ITreeContentProvider contentProvider; IBaseLabelProvider labelProvider; PdrPreviewSelectionListener selectionListener; PdrPreviewCheckStateListener checkStateListener; PdrPreviewExpansionListener expansionListener; private boolean isvalid; private String message; private WizardPage wizardPage; private PdrObjectsPreviewStructure previewStructure; /** * Creates a PDR Selection Preview on a newly-created tree under the given * parent. * <p>Explicitly intended to be used in a {@link GridLayout} environment. * That means that the <code>parent</code> composite must provide such a * layout.</p> * <p>Standard layout data is applied using {@link SWT#FILL} and * grabbing excess space flags both horizontally and vertically. <br/> * Layout data may however be customized by using {@link #setLayoutData(int, * int, boolean, boolean, int, int)} if specification row or column span * is desired.</p> * @param parent the parent Control with {@link GridLayout} */ public PdrSelectionFilterPreview(WizardPage page, Composite parent) { super(parent, SWT.BORDER | SWT.CHECK | SWT.MULTI | SWT.V_SCROLL); this.wizardPage = page; tree = getTree(); PdrSelectionPreviewColumns.createColumns(this, parent); // workaround to prevent eclipse window builder from crashing if (FrameworkUtil.getBundle(getClass()) != null) { // TODO: this resets the contents of our central pdr objetcs provider // during the pdr objects preview struct constructor. // we might however want to instantiate multiple previews // for the same structure instance/pdr objects provider // FIXME: den jedesmal zu erzeugen ist ja wohl auch quatsch. dabei wird jedesmal auch // der objectsprovider zurückgesetyt und bekommt neue eingaben aus der fassade. man muß sich auch mal entscheiden! previewStructure = AeExportCoreProvider.getInstance().createPdrObjectsTree(); setContentProvider(previewStructure); setCheckStateProvider(previewStructure); } setLayoutData(SWT.FILL, SWT.FILL, true, true); isvalid = true; setInput(previewStructure.getElements(null)); // select all for (StructNode root : previewStructure.getElements()) { //FIXME //setChecked(root, true, false); /*for (StructNode chld : root.getChildren()) { collapseToLevel(chld, ALL_LEVELS); chld.setExpanded(false); }*/ } } /** * Sets a custom layout data for this tree viewer. Calling this method * does the same as calling {@link Control#setLayoutData(Object)}. * <p>In addition, minimum width and height are set as 150 and 100 pixels * respectively, {@link Tree#setLinesVisible(boolean)} is set <code>true * </code> and an {@link ICheckStateListener} is initialized for triggering * shallow recursive checking of subtrees whenever a expandable node is * checked and checking of the nodes of which at least one child node * has been checked.</p> * <p>Span numbers are optional. {@link #setLayoutData(int, int, boolean, * boolean)} may be used as well.</p> * @param horizontalAlignment {@link SWT} style for horizontal alignment * @param verticalAlignment {@link SWT} style for vertical alignment * @param grabExcessHorizontalSpace boolean space grabbing flag horizontal * @param grabExcessVerticalSpace space grabbing flag vertical * @param horizontalSpan number of columns to be used in gridlayout * @param verticalSpan number of rows to be used in gridlayout */ public void setLayoutData(int horizontalAlignment, int verticalAlignment, boolean grabExcessHorizontalSpace, boolean grabExcessVerticalSpace, int horizontalSpan, int verticalSpan) { GridData gd = new GridData(horizontalAlignment, verticalAlignment, grabExcessHorizontalSpace, grabExcessVerticalSpace, horizontalSpan, verticalSpan); gd.minimumHeight = 100; gd.minimumWidth = 150; tree.setLayoutData(gd); tree.setLinesVisible(true); tree.setHeaderVisible(true); // http://www.vogella.com/articles/EclipseJFaceTable/article.html // treecolumnlayout: http://workorhobby.blogspot.de/2012/02/eclipse-jface-virtual-treeviewer.html this.tree.pack(); checkStateListener = new PdrPreviewCheckStateListener(); selectionListener = new PdrPreviewSelectionListener(); expansionListener = new PdrPreviewExpansionListener(); } /** * The same thing as {@link #setLayoutData(int, int, boolean, boolean, * int, int)} except for that no column and row spans are used. * @param horizontalAlignment {@link SWT} style for horizontal alignment * @param verticalAlignment {@link SWT} style for vertical alignment * @param grabExcessHorizontalSpace boolean space grabbing flag horizontal * @param grabExcessVerticalSpace space grabbing flag vertical * @see #setLayoutData(int, int, boolean, boolean, int, int) */ public void setLayoutData(int horizontalAlignment, int verticalAlignment, boolean grabExcessHorizontalSpace, boolean grabExcessVerticalSpace) { setLayoutData(horizontalAlignment, verticalAlignment, grabExcessHorizontalSpace, grabExcessVerticalSpace, 1, 1); } /** * Sets the input data for this tree viewer as an array of {@link StructNode} * elements. Each and every object is being 'checked' by default. * @param input An array of {@link StructNode}s */ public void setInput(Object[] input) { super.setInput(input); /*for (Object obj : input) if (obj instanceof StructNode) setChecked(obj, ((StructNode)obj).isSelected());*/ } /** * Validates current selection. */ public void validateSelection() { // find out if resulting selection is ready to be exported // TODO: find more sophisticated requirements for valid selection // check if at least one Pdr Person is checked in preview // otherwise, preview contents are marked invalid PdrObject[] newSelection = getSelectedObjects(); if (newSelection.length > 0) { for (PdrObject pobj : newSelection) if (pobj instanceof Person) { setValid(); return; } setInvalid("Please select at least one Person."); //TODO nl } else setInvalid("Please select some content to export."); //TODO nl } /** * returns an array of all objects represented by tree items whose check * state is true, no matter if they are persons or aspects * @return */ public PdrObject[] getSelectedObjects() { StructNode[] selection = previewStructure.getSelected(); Vector<PdrObject> objects = new Vector<PdrObject>(); // for all selection leaf nodes, walk up towards its root and // collect every PdrObject on the way. for (StructNode leaf : selection) { Vector<StructNode> path = new Vector<StructNode>(); path.add(leaf); // collect ancestors while (path.lastElement().getParent() != null) path.add(path.lastElement().getParent()); Collections.reverse(path); // filter out objects that are not PdrObject instances for (StructNode node : path) if (node.getContent() instanceof PdrObject) objects.add((PdrObject) node.getContent()); } return objects.toArray(new PdrObject[objects.size()]); } /** * Returns wheather the widget's contents are valid in a way that for * instance export wizards can continue given its state. * @return */ @Override public boolean isValid() { log.log(new Status(isvalid ? IStatus.INFO : IStatus.OK, CommonActivator.PLUGIN_ID, "Pdr objects previewer is "+(isvalid ? "valid." : "not valid!"))); return isvalid; } /** * Mark this widget invalid. * @param message Complementary error message. */ private void setInvalid(String message) { if (isvalid) if (message != null && !message.equals(this.message)){ log.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID, "Mark previewer as invalid")); isvalid = false; this.message = message; IWizardContainer wizardContainer = this.wizardPage.getWizard().getContainer(); wizardContainer.updateButtons(); wizardContainer.updateMessage(); } } /** * Mark this widget valid */ private void setValid() { log.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID, "Preview selection is valid.")); if (!isvalid) { isvalid = true; this.message = null; IWizardContainer wizardContainer = this.wizardPage.getWizard().getContainer(); wizardContainer.updateButtons(); wizardContainer.updateMessage(); } } @Override public String getMessage() { return message; } public void updateColumnLabel(String label) { PdrSelectionPreviewColumns.setColumnLabel(label); } /** * Jeder Aufruf setzt den komplette Baum zurück. Der muß jedesmal neu gebaut werden. * Dafür wird jedesmal auch der zentrale {@link PDRObjectsProvider} angerufen, der gar nicht * weiß wie ihm geschieht, weil er ständig von Hinz und Kunz neu instanziiert oder mit neuen * Inputs malträtiert wird. Ob der dann bei so ner Aktion wie hier überhaupt in nem * startklaren Zustand ist, weiß kein Mensch. (Vor allem nervt PdrObjectsPreview rum. Wenigetsnd * bei jedem angelegten Wizard mit ner Preview wird der provider komplett eingestampft) */ @Deprecated public void update(){ previewStructure.buildTree(); this.refresh(); /*for (StructNode node : previewStructure.getElements()) setChecked(node, true);*/ } /** * Causes the internal representations of those {@link PdrObject}s that * have been retrieved from the database the most recently to update their * sorting criteria and to repopulate. Attempts to backup and reconstruct * the selections made in the GUI. * @param compId index number of the attribute by which the contents are * desired to be ordered. * @return index number of the applied sorting method plus a flag indicating * if the contents have been put in ascending or descending order at bit number 3. */ public int sortBy(int compId){ // backup current selected nodes, nodes flagged as expanded StructNode[] selBck = previewStructure.getSelected(); StructNode[] expBck = previewStructure.getExpandedNodes(); // re-sort, re-populate, overwrite structures // first, disconnect check state listener this.removeCheckStateListener(checkStateListener); // then give tree entire rebirth int res = previewStructure.sortBy(compId); // then update tree viewer input FIXME: what for??? //setInput(previewStructure.getElements(null)); // ############ RESTORE SELECTION STATE FROM BACKUP ############## // from the backed up nodes, filter only those that //// represent Aspects, as the entire selection can be //// inferred on this basis // unfold entire tree for (StructNode root : previewStructure.getElements(null)) expandToLevel(root, ALL_LEVELS); // for each of the backed up aspects, retrieve from the // re-populated tree structure those nodes that represent // the same aspect // and try to find out which of them supposedly is to be // considered the backup node's alter ego: // + if parent node of both the backup node and the candidate // represent the same object // + if category of root node (group) remains System.out.println("Backup selection state to restore:"); System.out.println("===================================================="); System.out.println(" "+selBck.length+" nodes"); /* for (StructNode exp : selBck) System.out.println(exp.getLabel() + " ("+exp.getRootCategory()+")"); System.out.println("----------------------------------------------------");*/ previewStructure.deselectAll(); // FIXME: the objects provider has been modified and now caches its known // FIXME: person/orderinghead top layer, which means that even on re-sort and // FIXME: thus invoked tree rebuild, the provided person, oh, and aspect objects // FIXME: remain the same! // FIXME: hence, it should be way easier to find those objects that were // FIXME: the selected and expanded ones in the replaced tree! // FIXME: right now, sorting does not work, expansion backup is ignored. Vector<StructNode> aliases = new Vector<StructNode>(); for (StructNode sel : selBck) { // find possible incarnations of vanished tree selection nodes Vector<StructNode> candidates = previewStructure.getNodesFor(sel.getContent()); String cat = sel.getRootCategory(); // test candidates for (StructNode cnd : candidates) // same parent? // same group? // then select matching reborn candidate if (cnd.isSiblingOf(sel) && (cat == null || cat.equals(cnd.getRootCategory()))) //aliases.add(cnd); cnd.setSelected(true); } // select every of the identified Aliases // FIXME: this also checks all child references by default! // TODO: or does it? //for (StructNode als : aliases) //setCheckStates(als, true); //als.setSelected(true); // ######## RESTORE EXPANSION STATE FROM BACKUP ############### //since the entire tree is still expanded, we simply process every // maximum-depth nodes flagged as expanded in the backup structure // and try to collapse what can be considered their children in // the re-built tree System.out.println("Backup expansion state to restore:"); System.out.println("===================================================="); System.out.println(" "+expBck.length+" nodes"); /*for (StructNode exp : expBck) System.out.println(exp.getLabel() + " ("+exp.getRootCategory()+")"); System.out.println("----------------------------------------------------");*/ aliases = new Vector<StructNode>(); for (StructNode exp : expBck) { Vector<StructNode> candidates = previewStructure.getNodesFor(exp.getContent()); String cat = exp.getRootCategory(); // requirements for a node holding the same content as the query node // to be considered identical: // + same parent (can be null) // + same tree category for (StructNode cand : candidates) if (cand.isSiblingOf(exp)) if (cat == null || cand.getRootCategory().equals(cat)) aliases.add(cand); } // restore pre-sorting expansion state by expanding nodes assumingly // identical to backup // (inversely, since right now, the entire tree is expanded) // restore expansion situation for (StructNode als : aliases) { log.log(new Status(IStatus.INFO, CommonActivator.PLUGIN_ID, "Expand node: "+als.getLabel()+" ("+als.getRootCategory()+")")); als.setExpanded(true); collapseToLevel(als, ALL_LEVELS); expandToLevel(als, 1); } // collapse to valid state // FIXME: traverse through all nodes, not only roots! for (StructNode rtn : previewStructure.getElements(null)) if (!rtn.isExpanded()) collapseToLevel(rtn, ALL_LEVELS); this.refresh(); // re-connect check state listener when tree viewer is completely repopulated this.addCheckStateListener(checkStateListener); return res; } /* @Override public boolean setChecked(Object element, boolean state) { return setChecked(element, state, true); // FIXME: what about superclass? }*/ /** * Set the the checkbox state of a given element. Also * checks <strike>parent elements up to root level and</strike> child elements * if necessary. {@link ReferenceMods} elements checkbox states * depend on the aspect elements they belong to. * @param element * @param state * @return */ private boolean setChecked(Object element, boolean state, boolean expand) { //TODO: this is actually really bad. We handle tree logic in the //representation layer. If anybody finds out, we are totally embarassed. //This checkbox state handling/node expanding stuff should be moved //to StructNode or at least PreviewStructure entirely. //TODO on the other hand, the expanded flag is clearly representation. //also, it might be good to keep viewer checkbox states and tree node //selection flags seperated in order to maintain tree consistency even //if viewer is inconsistency for some reason. //TODO decision StructNode node = (StructNode)element; StructNode parent = node.getParent(); //System.out.println("Check node "+ node.label+" > "+ state); // if reference selection state changes within aspect node, update aspect // selection state, s.t. either every reference within aspect node is // deselected or every reference is selected, equivalent to aspect selection // state if (node.getContent() instanceof ReferenceMods) if (parent != null && parent.getContent() instanceof Aspect) { setChecked(parent, state, true); //System.out.println(" -> Check parent aspect node"+" > "+state); } boolean ret; // if expand flag is passed, update selection flags recursively if (expand) ret = expandAndSelectDescendants(node, state); else // if not, just this node ret = setCheckStates(element, state); return ret; } /** * Calls superclass method {@link CheckboxTreeViewer#setChecked(Object, boolean)}, * updates check flag of internal representation, and checks all parent nodes. * @param element * @param state * @return */ private boolean setCheckStates(Object element, boolean state) { if (element instanceof StructNode) { StructNode node = ((StructNode)element); node.setSelected(state); if (state) { boolean ok = true; // FIXME: references are currently not loaded into the model // whenever a reference gets selected, find it in // reference roots and select it there as well (references need to // be exported as objects just like persons) // Wow, this is really bad practice!!! // FIXME: but currently inactive if (node.getContent() instanceof ReferenceMods) // FIXME: doesn't find any matching nodes. for (StructNode ref : previewStructure.getNodesFor(node.getContent())) if (ref.getRootCategory().equals("grouped.reference")) ok &= setCheckStates(ref, state); // select all ancestors of this node as well in CheckboxTreeViewer terms while (node != null) { ok &= super.setChecked(node, state); node = node.getParent(); } return ok; } } return super.setChecked(element, state); } /** * Expands an object in the tree (unless it is an Aspect) and updates its checkbox state * according to the given parameter.<br/> * This is not like an alias for {@link #expandToLevel(Object, int)}, but more like * recursively applying a selection state to nodes in a subtree, and expand them * under certain conditions. * @param node * @param check */ boolean expandAndSelectDescendants(StructNode node, boolean check){ // tracking if any changes are made check-wise boolean change = setCheckStates(node, check); if (isExpandable(node)) { // save current expansion state boolean expanded = getExpandedState(node); // unfold this node -> uncover direct child nodes if (!expanded) { expandToLevel(node, 1); node.setExpanded(true); } // if desired, mark all children as selected // in any case, walk down links to expandable children // and continue there for (StructNode chld : node.getChildren()) change |= expandAndSelectDescendants(chld, check); // if during this call a node got expanded that hadn't been // until now, re-collapse this node only at one of these conditions: // * node holds Aspect (don't show references) // * node wasn't to be selected (don't show boring unselected ancestors) // * selection state of subtree didn't change (nothing happened at all) if (!expanded && (node.getContent() instanceof Aspect || !check || !change)) { node.setExpanded(false); collapseToLevel(node, CheckboxTreeViewer.ALL_LEVELS); } } return change; } @Override public boolean getExpandedState(Object elementOrTreePath) { if (elementOrTreePath instanceof StructNode) return ((StructNode)elementOrTreePath).isExpanded(); return super.getExpandedState(elementOrTreePath); } @Override public void performFinish() { } }