From: Tuukka Lehtonen Date: Wed, 14 Jun 2017 16:45:36 +0000 (+0300) Subject: Improved PDF diagram export wizard user experience X-Git-Tag: v1.31.0~320 X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=commitdiff_plain;h=83b58da86c173e771f7083778799b79b2fb152c8 Improved PDF diagram export wizard user experience * Page number addition is now an option in the wizard page * The diagram selection tree no longer automatically expands itself fully when any node is (un)checked. Automatic expansion is only done up to the second node level only. * The diagram filter now works with a 500ms quiet time delay which means writing in the filter shouldn't slow down the UI like it used to. * Initial selection is more properly taken into account when initializing the wizard page. The containing model/index root is always sought based on the selection and the diagrams contained by the selected resource are then initially selected for that model in the diagram list. * The Select/Deselect Visible buttons have been removed and Expand and Collapse buttons have been added instead for expanding and collapsing the selected tree nodes. * The Select All and Deselect All buttons now work differently. If any filter is applied to the current tree view, only the diagrams shown in the tree are selected/deselected when either button is pressed. If no filter is applied, all diagrams will be selected/deselected. refs #7297 Change-Id: I078a80528ba91c337f9921422d08a75c95cad45f --- diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/NodeTree.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/NodeTree.java new file mode 100644 index 000000000..a17aafb4a --- /dev/null +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/NodeTree.java @@ -0,0 +1,501 @@ +/******************************************************************************* + * Copyright (c) 2017 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Semantum Oy - #7297 + *******************************************************************************/ +package org.simantics.modeling.ui.pdf; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.jface.viewers.CellLabelProvider; +import org.eclipse.jface.viewers.CheckStateChangedEvent; +import org.eclipse.jface.viewers.CheckboxTreeViewer; +import org.eclipse.jface.viewers.ICheckStateListener; +import org.eclipse.jface.viewers.ICheckStateProvider; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TreePath; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.TreeItem; +import org.simantics.browsing.ui.common.views.DefaultFilterStrategy; +import org.simantics.browsing.ui.common.views.IFilterStrategy; +import org.simantics.modeling.requests.CollectionResult; +import org.simantics.modeling.requests.Node; +import org.simantics.utils.strings.AlphanumComparator; +import org.simantics.utils.ui.ISelectionUtils; + +/** + * A tree of nodes intended for usable listing and selecting diagrams. + * + * @author Tuukka Lehtonen + * @since 1.30.0 + */ +public class NodeTree extends Composite { + + /** + * This exists to make {@link NodeCheckStateProvider} faster + */ + private static class CheckStateCache { + Map isChecked = new HashMap<>(); + Map isGrayed = new HashMap<>(); + + public void invalidate(Node n) { + for (; n != null; n = n.getParent()) { + isChecked.remove(n); + isGrayed.remove(n); + } + } + public void invalidate() { + isChecked.clear(); + isGrayed.clear(); + } + } + + protected Display display; + + protected LocalResourceManager resourceManager; + + protected Color noDiagramColor; + + protected IFilterStrategy filterStrategy = new DefaultFilterStrategy(); + + protected Text filter; + + protected Matcher matcher = null; + + protected CheckboxTreeViewer tree; + + /** + * The tree paths that were expanded last time no filter was defined. Will + * be nullified after the expanded paths have been returned when + * {@link #matcher} turns null. + */ + protected TreePath[] noFilterExpandedPaths; + + protected Set selectedNodes; + + protected CheckStateCache checkStateCache = new CheckStateCache(); + + protected Runnable selectionChangeListener; + + protected CollectionResult nodes; + + public NodeTree(Composite parent, Set selectedNodes) { + super(parent, 0); + + this.display = getDisplay(); + this.selectedNodes = selectedNodes; + + resourceManager = new LocalResourceManager(JFaceResources.getResources(), this); + noDiagramColor = getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY); + + GridLayoutFactory.fillDefaults().spacing(20, 10).numColumns(3).applyTo(this); + + createFilter(this); + createTree(this); + createButtons(this); + } + + public void setSelectionChangeListener(Runnable r) { + this.selectionChangeListener = r; + } + + public void setInput(CollectionResult nodes) { + this.nodes = nodes; + tree.setInput(nodes); + resetFilterString(filter.getText()); + } + + private Runnable resetFilter = () -> resetFilterString(filter.getText()); + + private void createFilter(Composite parent) { + Label filterLabel = new Label(parent, SWT.NONE); + filterLabel.setText("Fi<er:"); + GridDataFactory.fillDefaults().span(1, 1).applyTo(filterLabel); + filter = new Text(parent, SWT.BORDER); + GridDataFactory.fillDefaults().span(2, 1).applyTo(filter); + filter.addModifyListener(e -> display.timerExec(500, resetFilter)); + } + + private void createTree(Composite parent) { + tree = new CheckboxTreeViewer(parent, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION); + tree.setUseHashlookup(true); + GridDataFactory.fillDefaults().grab(true, true).span(3, 1).applyTo(tree.getControl()); + tree.getControl().setToolTipText("Selects the diagrams to include in the exported document."); + tree.setAutoExpandLevel(2); + tree.addCheckStateListener(new CheckStateListener()); + tree.setContentProvider(new NodeTreeContentProvider()); + tree.setLabelProvider(new NodeLabelProvider()); + tree.setCheckStateProvider(new NodeCheckStateProvider()); + tree.setComparator(new ViewerComparator(AlphanumComparator.CASE_INSENSITIVE_COMPARATOR)); + tree.setFilters(new ViewerFilter[] { new NodeFilter() }); + } + + private void createButtons(Composite parent) { + Composite bar = new Composite(parent, SWT.NONE); + GridDataFactory.fillDefaults().grab(true, false).span(3, 1).applyTo(bar); + bar.setLayout(new RowLayout()); + Button selectAll = new Button(bar, SWT.PUSH); + selectAll.setText("Select &All"); + selectAll.setToolTipText("Select All Visible Diagrams"); + selectAll.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + selectedNodes.addAll(filter.getText().isEmpty() ? nodes.breadthFirstFlatten(CollectionResult.DIAGRAM_RESOURCE_FILTER) : getVisibleNodes()); + refreshTree(true); + fireChangeListener(); + scheduleFocusTree(); + } + }); + Button clearSelection = new Button(bar, SWT.PUSH); + clearSelection.setText("&Deselect All"); + clearSelection.setToolTipText("Deselect All Visible Diagrams"); + clearSelection.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + if (filter.getText().isEmpty()) + selectedNodes.clear(); + else + selectedNodes.removeAll(getVisibleNodes()); + refreshTree(true); + fireChangeListener(); + scheduleFocusTree(); + } + }); + Button expand = new Button(bar, SWT.PUSH); + expand.setText("&Expand"); + expand.setToolTipText("Fully Expand Selected Nodes or All Nodes"); + expand.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + IStructuredSelection ss = tree.getStructuredSelection(); + if (ss.isEmpty()) + tree.expandAll(); + else + for (Object n : ss.toList()) + tree.expandToLevel(n, TreeViewer.ALL_LEVELS); + scheduleFocusTree(); + } + }); + Button collapse = new Button(bar, SWT.PUSH); + collapse.setText("&Collapse"); + collapse.setToolTipText("Collapse Selected Nodes or All Nodes"); + collapse.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + IStructuredSelection ss = tree.getStructuredSelection(); + if (ss.isEmpty()) + tree.collapseAll(); + else + for (Object n : ss.toList()) + tree.collapseToLevel(n, TreeViewer.ALL_LEVELS); + scheduleFocusTree(); + } + }); + } + + protected void fireChangeListener() { + if (selectionChangeListener != null) + selectionChangeListener.run(); + } + + protected void scheduleFocusTree() { + display.asyncExec(() -> { + if (!tree.getTree().isDisposed() && !tree.getTree().isFocusControl()) + tree.getTree().setFocus(); + }); + } + + private Collection getVisibleNodes() { + Collection result = new ArrayList<>(); + + Deque todo = new ArrayDeque<>(); + for (TreeItem ti : tree.getTree().getItems()) { + todo.add(ti); + } + + while (!todo.isEmpty()) { + TreeItem item = todo.removeLast(); + Node node = (Node) item.getData(); + if (node != null) + result.add(node); + + for (TreeItem child : item.getItems()) { + todo.add(child); + } + } + + return result; + } + + private void resetFilterString(String filterString) { + TreePath[] restoreExpansions = null; + String patternString = filterStrategy.toPatternString(filterString); + if (patternString == null) { + if (matcher != null) { + // Filter has been removed + restoreExpansions = noFilterExpandedPaths; + noFilterExpandedPaths = null; + } + matcher = null; + } else { + if (matcher == null) { + // Filter has been defined after not being previously defined + noFilterExpandedPaths = tree.getExpandedTreePaths(); + } + matcher = Pattern.compile(patternString).matcher(""); + } + refreshTree(false); + if (restoreExpansions != null) + tree.setExpandedTreePaths(restoreExpansions); + else + tree.expandAll(); + } + + protected static boolean hasDiagram(Node n) { + return n.getDiagramResource() != null; + } + + protected static boolean hasDiagramDeep(Node n) { + if (hasDiagram(n)) + return true; + for (Node c : n.getChildren()) + if (hasDiagramDeep(c)) + return true; + return false; + } + + protected boolean isSomethingSelected(Node node) { + if (selectedNodes.contains(node)) + return true; + + Collection children = node.getChildren(); + if (!children.isEmpty()) { + for (Node child : children) { + if (!hasDiagramDeep(child)) + continue; + if (isSomethingSelected(child)) + return true; + } + } + return false; + } + + protected boolean isFullySelected(Node node) { + if (selectedNodes.contains(node)) + return true; + + int selectedCount = 0; + boolean allSelected = true; + Collection children = node.getChildren(); + if (!children.isEmpty()) { + for (Node child : children) { + if (!hasDiagramDeep(child)) + continue; + boolean selected = isFullySelected(child); + allSelected &= selected; + selectedCount += selected ? 1 : 0; + //System.out.println("\tisFullySelected: test child: " + child + " : " + selected + " => " + allSelected); + if (!selected) + break; + } + } + //System.out.println("isFullySelected(" + node + "): " + allSelected + ", " + selectedCount); + return allSelected && selectedCount > 0; + } + + protected boolean isPartiallySelected(Node node) { + return !selectedNodes.contains(node) && isSomethingSelected(node) && !isFullySelected(node); + } + + protected void refreshTree(boolean invalidateCheckStateCache) { + if (invalidateCheckStateCache) + checkStateCache.invalidate(); + tree.refresh(); + } + + public void refreshTree() { + refreshTree(true); + } + + public boolean addOrRemoveSelection(Node node, boolean add) { + boolean changed = false; + if (hasDiagram(node)) { + if (add) + changed = selectedNodes.add(node); + else + changed = selectedNodes.remove(node); + if (changed) + checkStateCache.invalidate(node); + } + return changed; + } + + public boolean addOrRemoveSelectionRec(Node node, boolean add) { + boolean changed = false; + changed |= addOrRemoveSelection(node, add); + for (Node child : node.getChildren()) + changed |= addOrRemoveSelectionRec(child, add); + return changed; + } + + private static class NodeTreeContentProvider implements ITreeContentProvider { + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + @Override + public void dispose() { + } + @Override + public Object[] getElements(Object inputElement) { + if (inputElement instanceof CollectionResult) + return ((CollectionResult) inputElement).roots.toArray(); + return new Object[0]; + } + @Override + public boolean hasChildren(Object element) { + Node n = (Node) element; + Collection children = n.getChildren(); + if (children.isEmpty()) + return false; + for (Node c : children) + if (hasDiagramDeep(c)) + return true; + return false; + + } + @Override + public Object getParent(Object element) { + return ((Node) element).getParent(); + } + @Override + public Object[] getChildren(Object parentElement) { + Node n = (Node) parentElement; + List result = new ArrayList<>( n.getChildren().size() ); + for (Node c : n.getChildren()) + if (hasDiagramDeep(c)) + result.add(c); + return result.toArray(); + } + } + + private class NodeLabelProvider extends CellLabelProvider { + @Override + public void update(ViewerCell cell) { + Object e = cell.getElement(); + if (e instanceof Node) { + Node n = (Node) e; + String name = DiagramPrinter.formDiagramName(n, false); + cell.setText(name); + + if (n.getDiagramResource() == null) + cell.setForeground(noDiagramColor); + else + cell.setForeground(null); + } else { + cell.setText("invalid input: " + e.getClass().getSimpleName()); + } + } + } + + private class CheckStateListener implements ICheckStateListener { + @Override + public void checkStateChanged(CheckStateChangedEvent event) { + final boolean checked = event.getChecked(); + Node checkedNode = (Node) event.getElement(); + + Set nodes = new HashSet<>(); + Set selection = ISelectionUtils.filterSetSelection(tree.getSelection(), Node.class); + if (selection.contains(checkedNode)) + nodes.addAll(selection); + else + tree.setSelection(StructuredSelection.EMPTY); + nodes.add(checkedNode); + + for (Node node : nodes) + addOrRemoveSelectionRec(node, checked); + + tree.refresh(); + fireChangeListener(); + } + } + + private class NodeCheckStateProvider implements ICheckStateProvider { + @Override + public boolean isChecked(Object element) { + Node n = (Node) element; + Boolean cache = checkStateCache.isChecked.get(n); + if (cache != null) + return cache; + boolean checked = isSomethingSelected(n); + checkStateCache.isChecked.put(n, checked); + return checked; + } + @Override + public boolean isGrayed(Object element) { + Node n = (Node) element; + Boolean cache = checkStateCache.isGrayed.get(n); + if (cache != null) + return cache; + boolean grayed = n.getDiagramResource() == null && isPartiallySelected(n); + checkStateCache.isGrayed.put(n, grayed); + return grayed; + } + } + + private class NodeFilter extends ViewerFilter { + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + if (matcher == null) + return true; + + Node node = (Node) element; + boolean matches = matcher.reset(node.getName().toLowerCase()).matches(); + if (matches) + return true; + + // If any children are in sight, show this element. + for (Node child : node.getChildren()) + if (select(viewer, element, child)) + return true; + + return false; + } + } + +} \ No newline at end of file diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFDiagramExportWizard.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFDiagramExportWizard.java index f0c9cddb3..461e98c10 100644 --- a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFDiagramExportWizard.java +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFDiagramExportWizard.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * Copyright (c) 2007, 2017 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation + * Semantum Oy - initial selection handling improvements *******************************************************************************/ package org.simantics.modeling.ui.pdf; @@ -17,16 +18,15 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Deque; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.TreeSet; -import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.preference.IPersistentPreferenceStore; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.viewers.IFilter; @@ -36,15 +36,16 @@ import org.eclipse.ui.IExportWizard; import org.eclipse.ui.IMemento; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.preferences.ScopedPreferenceStore; +import org.simantics.NameLabelMode; +import org.simantics.NameLabelUtil; import org.simantics.Simantics; -import org.simantics.browsing.ui.graph.impl.request.GetName; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.common.NamedResource; import org.simantics.db.common.request.ObjectsWithType; +import org.simantics.db.common.request.PossibleIndexRoot; import org.simantics.db.common.request.ReadRequest; import org.simantics.db.exception.DatabaseException; -import org.simantics.db.layer0.request.ActiveModels; import org.simantics.db.management.ISessionContext; import org.simantics.layer0.Layer0; import org.simantics.modeling.requests.Node; @@ -56,12 +57,17 @@ import org.simantics.simulation.ontology.SimulationResource; import org.simantics.ui.SimanticsUI; import org.simantics.ui.utils.ResourceAdaptionUtils; import org.simantics.utils.FileUtils; +import org.simantics.utils.strings.AlphanumComparator; import org.simantics.utils.ui.ErrorLogger; import org.simantics.utils.ui.ExceptionUtils; import org.simantics.utils.ui.workbench.StringMemento; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class PDFDiagramExportWizard extends Wizard implements IExportWizard { + private static final Logger LOGGER = LoggerFactory.getLogger(PDFDiagramExportWizard.class); + private static final int MAX_RECENT_EXPORT_PATHS = 10; private static final String TAG_PATH = "path"; @@ -70,9 +76,9 @@ public class PDFDiagramExportWizard extends Wizard implements IExportWizard { Deque recentExportPaths; boolean zoomToFit; - boolean attachTG, attachWiki; + boolean attachTG, attachWiki, addPageNumbers; - PDFExportPlan exportPlan; + PDFExportPlan exportPlan; private boolean readPreferences() { IPreferenceStore store = new ScopedPreferenceStore(InstanceScope.INSTANCE, Activator.PLUGIN_ID); @@ -82,6 +88,7 @@ public class PDFDiagramExportWizard extends Wizard implements IExportWizard { zoomToFit = store.getBoolean(Preferences.DIAGRAM_EXPORT_PDF_ZOOM_TO_FIT); attachTG = store.getBoolean(Preferences.DIAGRAM_EXPORT_PDF_ATTACH_TG); attachWiki = store.getBoolean(Preferences.DIAGRAM_EXPORT_PDF_ATTACH_WIKI); + addPageNumbers = store.getBoolean(Preferences.DIAGRAM_EXPORT_PDF_ADD_PAGE_NUMBERS); return true; } @@ -93,13 +100,14 @@ public class PDFDiagramExportWizard extends Wizard implements IExportWizard { store.putValue(Preferences.DIAGRAM_EXPORT_PDF_ZOOM_TO_FIT, String.valueOf(zoomToFit)); store.putValue(Preferences.DIAGRAM_EXPORT_PDF_ATTACH_TG, String.valueOf(attachTG)); store.putValue(Preferences.DIAGRAM_EXPORT_PDF_ATTACH_WIKI, String.valueOf(attachWiki)); + store.putValue(Preferences.DIAGRAM_EXPORT_PDF_ADD_PAGE_NUMBERS, String.valueOf(addPageNumbers)); if (store.needsSaving()) store.save(); } private Deque decodePaths(String recentPathsPref) { - Deque result = new LinkedList(); + Deque result = new LinkedList<>(); try { StringMemento sm = new StringMemento(recentPathsPref); for (IMemento m : sm.getChildren(TAG_PATH)) { @@ -137,7 +145,7 @@ public class PDFDiagramExportWizard extends Wizard implements IExportWizard { } private NamedResource toNamedResource(ReadGraph graph, Resource r) throws DatabaseException { - String name = graph.syncRequest(new GetName(r)); + String name = NameLabelUtil.modalName(graph, r, NameLabelMode.NAME_AND_LABEL); return new NamedResource(name, r); } @@ -154,54 +162,45 @@ public class PDFDiagramExportWizard extends Wizard implements IExportWizard { exportPlan = new PDFExportPlan(ctx, recentExportPaths); exportPlan.project = project; - final Object selectedObject = selection.getFirstElement(); + exportPlan.initialSelection = selection; exportPlan.fitContentToPageMargins = zoomToFit; exportPlan.attachTG = attachTG; exportPlan.attachWiki = attachWiki; + exportPlan.addPageNumbers = addPageNumbers; // Get all model names try { exportPlan.sessionContext.getSession().syncRequest(new ReadRequest() { @Override public void run(ReadGraph graph) throws DatabaseException { - Resource selection = ResourceAdaptionUtils.toSingleResource(selectedObject); - if (selection != null) { - //exportModel.selection = new NamedResource(name + " (input selection)", selection); - exportPlan.selection = toNamedResource(graph, selection); - exportPlan.selectableModels.add(exportPlan.selection); - } else { - for (Resource activeModel : graph.syncRequest(new ActiveModels(exportPlan.project.get()))) { - selection = activeModel; - exportPlan.selection = toNamedResource(graph, activeModel); - exportPlan.selectableModels.add( exportPlan.selection ); - break; - } - } + Set processed = new HashSet<>(); + List models = new ArrayList<>(); - List models = new ArrayList(); - Collection ontologies = Simantics.applySCL("Simantics/SharedOntologies", "traverseSharedOntologies", graph, graph.getRootLibrary()); - for (Resource model : ontologies) { - if (model.equals(selection)) - continue; - models.add( toNamedResource(graph, model) ); + for (Resource root : ontologies) { + if (processed.add(root)) + models.add( toNamedResource(graph, root) ); } - + for (Resource model : graph.syncRequest(new ObjectsWithType(exportPlan.project.get(), Layer0.getInstance(graph).ConsistsOf, SimulationResource.getInstance(graph).Model))) { - if (model.equals(selection)) - continue; - models.add( toNamedResource(graph, model) ); + if (processed.add(model)) + models.add( toNamedResource(graph, model) ); } - Collections.sort(models); + Collections.sort(models, AlphanumComparator.CASE_INSENSITIVE_COMPARATOR); exportPlan.selectableModels.addAll(models); - if (selection == null && !exportPlan.selectableModels.isEmpty()) { + + Resource selected = ResourceAdaptionUtils.toSingleResource(selection.getFirstElement()); + Resource indexRoot = selected != null ? graph.sync(new PossibleIndexRoot(selected)) : null; + if (indexRoot != null) + exportPlan.initialModelSelection = exportPlan.selection = toNamedResource(graph, indexRoot); + + if (exportPlan.selection == null && !exportPlan.selectableModels.isEmpty()) exportPlan.selection = exportPlan.selectableModels.get(0); - } } }); } catch (DatabaseException e) { - e.printStackTrace(); + LOGGER.error("Failed to initialize diagram PDF export wizard input data.", e); } } @@ -224,7 +223,7 @@ public class PDFDiagramExportWizard extends Wizard implements IExportWizard { recentExportPaths.addFirst(exportPlan.exportLocation.getAbsolutePath()); // Remove duplicates - Set dups = new TreeSet(String.CASE_INSENSITIVE_ORDER); + Set dups = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); for (Iterator it = recentExportPaths.iterator(); it.hasNext();) { String path = it.next(); if (!dups.add(path)) { @@ -238,6 +237,7 @@ public class PDFDiagramExportWizard extends Wizard implements IExportWizard { zoomToFit = exportPlan.fitContentToPageMargins; attachTG = exportPlan.attachTG; attachWiki = exportPlan.attachWiki; + addPageNumbers = exportPlan.addPageNumbers; writePreferences(); } catch (IOException e) { @@ -256,16 +256,13 @@ public class PDFDiagramExportWizard extends Wizard implements IExportWizard { long start = System.currentTimeMillis(); try { - getContainer().run(true, true, new IRunnableWithProgress() { - @Override - public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { - try { - DiagramPrinter.printToPdf(monitor, exportPlan, exportPlan.exportLocation.toString(), exportPlan.selectedNodes); - } catch (PdfException e) { - throw new InvocationTargetException(e); - } finally { - monitor.done(); - } + getContainer().run(true, true, monitor -> { + try { + DiagramPrinter.printToPdf(monitor, exportPlan, exportPlan.exportLocation.toString(), exportPlan.selectedNodes); + } catch (PdfException e) { + throw new InvocationTargetException(e); + } finally { + monitor.done(); } }); } catch (InvocationTargetException e) { @@ -276,7 +273,7 @@ public class PDFDiagramExportWizard extends Wizard implements IExportWizard { return false; } long end = System.currentTimeMillis(); - System.out.println("PDF export took " + ((end - start) * 1e-3) + " seconds."); + LOGGER.info("PDF export took " + ((end - start) * 1e-3) + " seconds."); return true; } diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFExportPage.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFExportPage.java index 7ef2efb94..fb2f8b0c6 100644 --- a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFExportPage.java +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFExportPage.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * Copyright (c) 2007, 2017 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,145 +8,71 @@ * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation + * Semantum Oy - #7297 *******************************************************************************/ package org.simantics.modeling.ui.pdf; import java.io.File; import java.lang.reflect.InvocationTargetException; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; import java.util.HashSet; -import java.util.List; import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.eclipse.core.runtime.IPath; -import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.jface.layout.GridDataFactory; -import org.eclipse.jface.operation.IRunnableWithProgress; -import org.eclipse.jface.resource.JFaceResources; -import org.eclipse.jface.resource.LocalResourceManager; -import org.eclipse.jface.viewers.CellLabelProvider; -import org.eclipse.jface.viewers.CheckStateChangedEvent; -import org.eclipse.jface.viewers.CheckboxTreeViewer; -import org.eclipse.jface.viewers.ICheckStateListener; -import org.eclipse.jface.viewers.ICheckStateProvider; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.jface.viewers.ViewerCell; -import org.eclipse.jface.viewers.ViewerComparator; -import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CCombo; -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.graphics.Color; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; -import org.eclipse.swt.widgets.TreeItem; -import org.simantics.browsing.ui.common.views.DefaultFilterStrategy; -import org.simantics.browsing.ui.common.views.IFilterStrategy; import org.simantics.databoard.Bindings; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.common.NamedResource; import org.simantics.db.common.request.ReadRequest; import org.simantics.db.exception.DatabaseException; +import org.simantics.modeling.requests.CollectionResult; import org.simantics.modeling.requests.Node; +import org.simantics.modeling.requests.Nodes; +import org.simantics.ui.utils.ResourceAdaptionUtils; import org.simantics.utils.FileUtils; -import org.simantics.utils.strings.AlphanumComparator; -import org.simantics.utils.ui.ISelectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class PDFExportPage extends WizardPage { - protected Display display; + private static final Logger LOGGER = LoggerFactory.getLogger(PDFExportPage.class); - protected PDFExportPlan exportModel; + protected Display display; - protected IFilterStrategy filterStrategy = new DefaultFilterStrategy(); + protected PDFExportPlan exportModel; protected Combo modelSelector; protected SelectionListener modelSelectorListener; - protected Text filter; - - protected Matcher matcher = null; - - protected CheckboxTreeViewer tree; + protected NodeTree nodeTree; protected CCombo exportLocation; protected ModifyListener exportLocationListener; protected Set selectedNodes; - - protected LocalResourceManager resourceManager; - protected Color noDiagramColor; - protected Label toFileLabel; + protected Label toFileLabel; protected boolean exportLocationTouchedByUser = false; - ICheckStateProvider checkStateProvider = new ICheckStateProvider() { - @Override - public boolean isChecked(Object element) { - Node node = (Node) element; - - // Primarily checked if any children are selected. - Collection children = node.getChildren(); - if (!children.isEmpty()) { - for (Node child : node.getChildren()) - if (isChecked(child)) - return true; - - // No children are checked, not checked. - return false; - } - - // Otherwise checked only if selected. - return selectedNodes.contains(node); - } - @Override - public boolean isGrayed(Object element) { - Node node = (Node) element; - - // Grayed if there are children but not all of them are selected. - Collection children = node.getChildren(); - if (!children.isEmpty()) { - for (Node child : children) - if (!selectedNodes.contains(child)) - return true; - } - - // Grayed if the node itself contains no diagram. - if (node.getDiagramResource() == null) - return true; - - // Otherwise never grayed. - return false; - } - }; - protected PDFExportPage(PDFExportPlan model) { super("Export Diagrams to PDF", "Define Exported Items", null); this.exportModel = model; @@ -165,14 +91,6 @@ public class PDFExportPage extends WizardPage { layout.numColumns = 3; container.setLayout(layout); } - resourceManager = new LocalResourceManager(JFaceResources.getResources()); - container.addDisposeListener(new DisposeListener() { - @Override - public void widgetDisposed(DisposeEvent e) { - resourceManager.dispose(); - } - }); - noDiagramColor = container.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY); Label modelSelectorLabel = new Label(container, SWT.NONE); modelSelectorLabel.setText("Model Selector:"); @@ -196,198 +114,9 @@ public class PDFExportPage extends WizardPage { modelSelector.addSelectionListener(modelSelectorListener); -// Label label = new Label(container, SWT.NONE); -// label.setText("Diagrams to Export:"); -// GridDataFactory.fillDefaults().span(3, 1).applyTo(label); - - Label filterLabel = new Label(container, SWT.NONE); - filterLabel.setText("Fi<er:"); - GridDataFactory.fillDefaults().span(1, 1).applyTo(filterLabel); - filter = new Text(container, SWT.BORDER); - GridDataFactory.fillDefaults().span(2, 1).applyTo(filter); - filter.addModifyListener(new ModifyListener() { - @Override - public void modifyText(ModifyEvent e) { - resetFilterString(filter.getText()); - } - }); - - tree = new CheckboxTreeViewer(container, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION); - { - tree.setUseHashlookup(true); - GridDataFactory.fillDefaults().grab(true, true).span(3, 1).applyTo(tree.getControl()); - tree.getControl().setToolTipText("Selects the diagram to include in the exported document."); - tree.setAutoExpandLevel(TreeViewer.ALL_LEVELS); - tree.addCheckStateListener(new ICheckStateListener(){ - void addOrRemoveSelection(Node node, boolean add) { - if (add) - selectedNodes.add(node); - else - selectedNodes.remove(node); - } - void addOrRemoveSelectionRec(Node node, boolean add) { - addOrRemoveSelection(node, add); - for (Node child : node.getChildren()) - addOrRemoveSelectionRec(child, add); - } - @Override - public void checkStateChanged(CheckStateChangedEvent event) { - final boolean checked = event.getChecked(); - Node checkedNode = (Node) event.getElement(); - - Set nodes = new HashSet(); - Set selection = ISelectionUtils.filterSetSelection(tree.getSelection(), Node.class); - if (selection.contains(checkedNode)) - nodes.addAll(selection); - else - tree.setSelection(StructuredSelection.EMPTY); - nodes.add(checkedNode); - - for (Node node : nodes) { - addOrRemoveSelectionRec(node, checked); - -// tree.setSubtreeChecked(node, checked); -// The checked node is always either checked or not checked, never grayed. -// tree.setGrayed(node, checkStateProvider.isGrayed(node)); - -// Node parent = node.getParent(); -// if (parent != null) { -// tree.setChecked(parent, checkStateProvider.isChecked(parent)); -// tree.setGrayed(parent, checkStateProvider.isGrayed(parent)); -// } - } - - refreshAndExpandTree(); - validatePage(); - } - }); - - tree.setContentProvider(new ITreeContentProvider(){ - @Override - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - @Override - public void dispose() { - } - @Override - public Object[] getElements(Object inputElement) { - return exportModel.nodes.roots.toArray(); - } - @Override - public boolean hasChildren(Object element) { - Node n = (Node) element; - if (n.getChildren().isEmpty()) return false; - for (Node c : n.getChildren()) if (hasDiagram(c)) return true; - return false; - - } - @Override - public Object getParent(Object element) { - Node n = (Node) element; - return n.getParent(); - } - @Override - public Object[] getChildren(Object parentElement) { - Node n = (Node) parentElement; - List result = new ArrayList( n.getChildren().size() ); - for (Node c : n.getChildren()) - if (hasDiagram(c)) - result.add(c); - return result.toArray(); - } - - boolean hasDiagram(Node n) - { - if (n.getDiagramResource()!=null) return true; - for (Node c : n.getChildren()) if (hasDiagram(c)) return true; - return false; - } - }); - tree.setLabelProvider(new CellLabelProvider() { - @Override - public void update(ViewerCell cell) { - Object e = cell.getElement(); - if (e instanceof Node) { - Node n = (Node) e; - String name = DiagramPrinter.formDiagramName(n, false); - cell.setText(name); - - if (n.getDiagramResource() == null) - cell.setForeground(noDiagramColor); - else - cell.setForeground(null); - } else { - cell.setText("invalid input: " + e.getClass().getSimpleName()); - } - } - }); - tree.setComparator(new ViewerComparator(AlphanumComparator.CASE_INSENSITIVE_COMPARATOR)); - tree.setFilters(new ViewerFilter[] { - new ViewerFilter() { - @Override - public boolean select(Viewer viewer, Object parentElement, Object element) { - if (matcher == null) - return true; - - Node node = (Node) element; - // If any children are in sight, show this element. - for (Node child : node.getChildren()) { - if (select(viewer, element, child)) - return true; - } - - return matcher.reset(node.getName().toLowerCase()).matches(); - } - } - }); - tree.setCheckStateProvider(checkStateProvider); - } - - Composite bar = new Composite(container, SWT.NONE); - GridDataFactory.fillDefaults().grab(true, false).span(3, 1).applyTo(bar); - bar.setLayout(new RowLayout()); - Button selectAll = new Button(bar, SWT.PUSH); - selectAll.setText("Select &All"); - selectAll.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - selectedNodes.addAll(exportModel.nodes.breadthFirstFlatten()); - for (Node root : exportModel.nodes.roots) - tree.setSubtreeChecked(root, true); - validatePage(); - } - }); - Button clearSelection = new Button(bar, SWT.PUSH); - clearSelection.setText("&Clear Selection"); - clearSelection.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - selectedNodes.clear(); - for (Node root : exportModel.nodes.roots) - tree.setSubtreeChecked(root, false); - validatePage(); - } - }); - Button selectVisible = new Button(bar, SWT.PUSH); - selectVisible.setText("&Select Visible"); - selectVisible.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - selectedNodes.addAll(getVisibleNodes()); - refreshAndExpandTree(); - validatePage(); - } - }); - Button deselectVisible = new Button(bar, SWT.PUSH); - deselectVisible.setText("&Deselect Visible"); - deselectVisible.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - selectedNodes.removeAll(getVisibleNodes()); - refreshAndExpandTree(); - validatePage(); - } - }); + nodeTree = new NodeTree(container, selectedNodes); + GridDataFactory.fillDefaults().grab(true, true).span(3, 1).applyTo(nodeTree); + nodeTree.setSelectionChangeListener(this::validatePage); toFileLabel = new Label(container, SWT.NONE); toFileLabel.setText("&To file:"); @@ -474,7 +203,7 @@ public class PDFExportPage extends WizardPage { } }); */ - + final Button attachWikiButton = new Button(container, SWT.CHECK); GridDataFactory.fillDefaults().grab(true, false).span(3, 1).applyTo( attachWikiButton ); attachWikiButton.setText("Attach &Wiki page"); @@ -485,7 +214,18 @@ public class PDFExportPage extends WizardPage { exportModel.attachWiki = attachWikiButton.getSelection(); } }); - + + final Button addPageNumbers = new Button(container, SWT.CHECK); + GridDataFactory.fillDefaults().grab(true, false).span(3, 1).applyTo( addPageNumbers ); + addPageNumbers.setText("Add page &numbers"); + addPageNumbers.setSelection(exportModel.addPageNumbers); + addPageNumbers.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + exportModel.addPageNumbers = addPageNumbers.getSelection(); + } + }); + setControl(container); validatePage(); @@ -493,21 +233,14 @@ public class PDFExportPage extends WizardPage { } private void scheduleInitializeData(final NamedResource modelSelection) { - display.asyncExec(new Runnable() { - @Override - public void run() { - if (filter.isDisposed()) - return; - - try { + display.asyncExec(() -> { + try { + if (!nodeTree.isDisposed()) initializeData(modelSelection); - } catch (DatabaseException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.getTargetException().printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } + } catch (DatabaseException | InterruptedException e) { + LOGGER.error("Input data initialization failed.", e); + } catch (InvocationTargetException e) { + LOGGER.error("Input data initialization failed.", e.getTargetException()); } }); } @@ -527,43 +260,9 @@ public class PDFExportPage extends WizardPage { exportLocation.addModifyListener(exportLocationListener); } - private Collection getVisibleNodes() { - Collection result = new ArrayList(); - - Deque todo = new ArrayDeque(); - for (TreeItem ti : tree.getTree().getItems()) { - todo.add(ti); - } - - while (!todo.isEmpty()) { - TreeItem item = todo.removeLast(); - Node node = (Node) item.getData(); - result.add(node); - - for (TreeItem child : item.getItems()) { - todo.add(child); - } - } - - return result; - } - - private void resetFilterString(String filterString) { - String patternString = filterStrategy.toPatternString(filterString); - if (patternString == null) { - matcher = null; - } else { - matcher = Pattern.compile(patternString).matcher(""); - } - refreshAndExpandTree(); - } - - private void refreshAndExpandTree() { - tree.refresh(); - tree.expandAll(); - } - private void initializeData(final NamedResource modelSelection) throws DatabaseException, InvocationTargetException, InterruptedException { + Set toBeSelected = new HashSet<>(); + if (modelSelection != null) { // Process input selection to find the model/state selected by default. @@ -573,29 +272,54 @@ public class PDFExportPage extends WizardPage { // !PROFILE long time = System.nanoTime(); - getWizard().getContainer().run(true, true, new IRunnableWithProgress() { - @Override - public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { - try { - final SubMonitor mon = SubMonitor.convert(monitor, "Searching for exportable diagrams...", 100); - exportModel.sessionContext.getSession().syncRequest(new ReadRequest() { - @Override - public void run(ReadGraph graph) throws DatabaseException { - exportModel.nodes = DiagramPrinter.browse(mon.newChild(100), graph, new Resource[] { modelSelection.getResource() }); + getWizard().getContainer().run(true, true, monitor -> { + try { + SubMonitor mon = SubMonitor.convert(monitor, "Searching for exportable diagrams...", 100); + exportModel.sessionContext.getSession().syncRequest(new ReadRequest() { + @Override + public void run(ReadGraph graph) throws DatabaseException { + CollectionResult coll = exportModel.nodes = DiagramPrinter.browse(mon.newChild(100), graph, new Resource[] { modelSelection.getResource() }); + + // Decide initial selection based on exportModel.initialSelection + if (modelSelection.equals(exportModel.initialModelSelection)) { + Set selectedResources = new HashSet<>(); + for (Object o : exportModel.initialSelection.toList()) { + Resource r = ResourceAdaptionUtils.toSingleResource(o); + if (r != null) + selectedResources.add(r); + } + coll.walkTree(node -> { + if (node.getDiagramResource() != null) { + if (Nodes.parentIsInSet(toBeSelected, node)) + toBeSelected.add(node); + else + for (Resource r : node.getDefiningResources()) + if (selectedResources.contains(r)) + toBeSelected.add(node); + } + return true; + }); } - }); - } catch (DatabaseException e) { - throw new InvocationTargetException(e); - } finally { - monitor.done(); - } + + // Filter out any excess nodes from the tree. + exportModel.nodes = coll = coll.withRoots(Nodes.depthFirstFilter(Nodes.DIAGRAM_RESOURCE_PREDICATE, coll.roots)); + + // Select all if initial selection doesn't dictate anything. + if (toBeSelected.isEmpty()) + toBeSelected.addAll(coll.breadthFirstFlatten(CollectionResult.DIAGRAM_RESOURCE_FILTER)); + } + }); + } catch (DatabaseException e) { + throw new InvocationTargetException(e); + } finally { + monitor.done(); } }); // !PROFILE long endTime = System.nanoTime(); if (exportModel.nodes != null) - System.out.println("Found " + exportModel.nodes.diagrams.size() + " diagrams in " + ((endTime - time)*1e-9) + " seconds."); + LOGGER.info("Found " + exportModel.nodes.diagrams.size() + " diagrams in " + ((endTime - time)*1e-9) + " seconds."); } // Browsing was canceled by user. @@ -604,15 +328,10 @@ public class PDFExportPage extends WizardPage { // Setup selected states, select everything by default. selectedNodes.clear(); - selectedNodes.addAll(exportModel.nodes.breadthFirstFlatten()); - - tree.setInput(this); + selectedNodes.addAll(toBeSelected); - for (Node root : exportModel.nodes.roots) { - tree.setSubtreeChecked(root, true); - } - - resetFilterString(filter.getText()); + // Fully refresh node tree + nodeTree.setInput(exportModel.nodes); modelSelector.removeSelectionListener(modelSelectorListener); int selectedIndex = -1; @@ -632,8 +351,16 @@ public class PDFExportPage extends WizardPage { } void validatePage() { + int diagramCount = 0; + Node singleDiagram = null; + for (Node n : selectedNodes) + if (n.getDiagramResource() != null) { + ++diagramCount; + singleDiagram = n; + } + //System.out.println("VALIDATE PAGE: " + exportLocationTouchedByUser); - if (selectedNodes.size() == 0) { + if (diagramCount == 0) { setMessage("Select the diagrams to export."); setErrorMessage(null); setPageComplete(false); @@ -645,8 +372,8 @@ public class PDFExportPage extends WizardPage { // Generate file name automatically if user hasn't touched the name manually. NamedResource nr = getSelectedModel(); if (nr != null) { - if (selectedNodes.size() == 1) { - generatedName = nr.getName() + "-" + selectedNodes.iterator().next().getName(); + if (diagramCount == 1 && singleDiagram != null) { + generatedName = nr.getName() + "-" + singleDiagram.getName(); } else { generatedName = nr.getName(); } @@ -701,11 +428,6 @@ public class PDFExportPage extends WizardPage { } exportModel.exportLocation = file; - int diagramCount = 0; - for (Node n : selectedNodes) - if (n.getDiagramResource() != null) - ++diagramCount; - String msg = diagramCount + " diagrams selected for export."; setMessage(msg); diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFExportPlan.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFExportPlan.java index a41715f3b..73e5b09d2 100644 --- a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFExportPlan.java +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFExportPlan.java @@ -19,6 +19,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.eclipse.jface.viewers.IStructuredSelection; import org.simantics.db.common.NamedResource; import org.simantics.db.management.ISessionContext; import org.simantics.export.core.pdf.PageNumbering; @@ -31,6 +32,8 @@ public class PDFExportPlan { // Input public ISessionContext sessionContext; public IProject project; + public IStructuredSelection initialSelection; + public NamedResource initialModelSelection; public List selectableModels = new ArrayList(); public NamedResource selection; public Collection recentLocations; diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/Preferences.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/Preferences.java index f248ca2fa..97265df58 100644 --- a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/Preferences.java +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/Preferences.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * Copyright (c) 2007, 2017 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -8,6 +8,7 @@ * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation + * Semantum Oy - page numbering additions *******************************************************************************/ package org.simantics.modeling.ui.pdf; @@ -21,6 +22,6 @@ public interface Preferences { String DIAGRAM_EXPORT_PDF_ZOOM_TO_FIT = "diagram.export.pdf.zoomToFit"; String DIAGRAM_EXPORT_PDF_ATTACH_TG = "diagram.export.pdf.attachTG"; String DIAGRAM_EXPORT_PDF_ATTACH_WIKI = "diagram.export.pdf.attachWiki"; - + String DIAGRAM_EXPORT_PDF_ADD_PAGE_NUMBERS = "diagram.export.pdf.addPageNumbers"; } diff --git a/bundles/org.simantics.modeling/src/org/simantics/modeling/requests/CollectionRequest.java b/bundles/org.simantics.modeling/src/org/simantics/modeling/requests/CollectionRequest.java index 70aa8c88b..183717325 100644 --- a/bundles/org.simantics.modeling/src/org/simantics/modeling/requests/CollectionRequest.java +++ b/bundles/org.simantics.modeling/src/org/simantics/modeling/requests/CollectionRequest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * Copyright (c) 2007, 2017 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -27,7 +27,6 @@ import org.simantics.db.Resource; import org.simantics.db.common.request.ReadRequest; import org.simantics.db.common.request.ResourceRead; import org.simantics.db.common.utils.NameUtils; -import org.simantics.db.exception.AdaptionException; import org.simantics.db.exception.DatabaseException; import org.simantics.db.request.Read; import org.simantics.diagram.query.DiagramRequests; @@ -69,22 +68,6 @@ public class CollectionRequest implements Read { return g.syncRequest(new GetName(r)); } -// Collection getPartOfGroups(Resource diagram) throws DatabaseException { -// Resource r = diagram; -// Deque result = new ArrayDeque(); -// loop: -// while (true) { -// for (Resource partOf : g.getObjects(r, b.PartOf)) { -// if (g.isInstanceOf(partOf, dr.DiagramLibrary)) { -// result.addFirst(safeGetName(partOf)); -// r = partOf; -// continue loop; -// } -// } -// return result; -// } -// } - @Override public CollectionResult perform(ReadGraph g) throws DatabaseException { this.g = g; @@ -96,53 +79,22 @@ public class CollectionRequest implements Read { SIMU = SimulationResource.getInstance(g); final CollectionResult result = new CollectionResult(); - final Deque roots = new ArrayDeque(); + final Deque roots = new ArrayDeque<>(); // 1. Based on input, look for the proper nodes to start browsing for diagrams. for (Resource r : input) { - /*if (g.isInstanceOf(r, SIMU.Model)) { - // Complete models - Resource composite = g.getPossibleObject(r, SIMU.HasConfiguration); - Resource diagram = composite != null ? g.getPossibleObject(composite, mr.CompositeToDiagram) : null; - - if (DEBUG) - System.out.println("Model root"); - - if (composite != null) { - Node node = new Node(null, safeGetName(r), diagram, composite, r); - roots.add(node); - result.roots.add(roots.peekLast()); - } - } else*/ if (g.isInstanceOf(r, l0.IndexRoot)) { -// for(Resource type : ModelingUtils.searchByTypeShallow(g, r, sr.ComponentType)) { -// Resource composite = g.getPossibleObject(type, sr.IsDefinedBy); -// Resource diagram = composite != null ? g.getPossibleObject(composite, mr.CompositeToDiagram) : null; -// if(composite != null) { -// Node node = new Node(null, safeGetName(r), diagram, composite, r); -// roots.add(node); -// result.roots.add(roots.peekLast()); -// } -// } - - Node node = new Node(null, safeGetName(r), null, r); - roots.add(node); - result.roots.add(roots.peekLast()); - + if (g.isInstanceOf(r, l0.IndexRoot)) { + Node node = new Node(null, safeGetName(r), null, r); + roots.add(node); + result.roots.add(roots.peekLast()); } else if (g.isInstanceOf(r, sr.Composite)) { // The contents of components String name = null; Resource model = g.getPossibleObject(r, SIMU.IsConfigurationOf); - //Resource componentType = g.getPossibleObject(r, sr.Defines); if (model != null) { name = safeGetName(model); - if (DEBUG) System.out.println("Configuration root: " + name); - -// } else if (componentType != null) { -// Resource singleInstance = componentType != null ? g.getPossibleObject(componentType, b.HasSingleInstance) : null; -// name = singleInstance != null ? safeGetName(singleInstance) : safeGetName(componentType); -// System.out.println("Composite of component type root: " + name); } else { name = safeGetName(r); if (DEBUG) @@ -153,19 +105,10 @@ public class CollectionRequest implements Read { diagram = (diagram != null && g.isInstanceOf(diagram, dr.Composite)) ? diagram : null; { - Node node = new Node(null, name, diagram, r); - roots.add(node); - result.roots.add(roots.peekLast()); + Node node = new Node(null, name, diagram, r); + roots.add(node); + result.roots.add(roots.peekLast()); } -// } else if (g.isInstanceOf(r, sr.Component)) { -// // Complete components themselves -// Resource componentType = g.getSingleType(r, sr.Component); -// Resource composite = g.getPossibleObject(componentType, sr.IsDefinedBy); -// Resource diagram = (composite != null && g.isInstanceOf(composite, sr.Composite)) ? g.getPossibleObject(composite, mr.CompositeToDiagram) : null; -// String name = safeGetName(r); -// System.out.println("Component root: " + name); -// roots.add(new Node(null, name, diagram, composite, r)); -// result.roots.add(roots.peekLast()); } else if (g.isInheritedFrom(r, dr.DefinedElement)) { // Symbols Resource composite = g.getPossibleObject(r, sr.IsDefinedBy); @@ -176,21 +119,13 @@ public class CollectionRequest implements Read { name += " Symbol"; if (DEBUG) System.out.println("Symbol root: " + name); - - { - Node node = new Node(null, name, composite, r); - roots.add(node); - result.roots.add(roots.peekLast()); + + { + Node node = new Node(null, name, composite, r); + roots.add(node); + result.roots.add(roots.peekLast()); } } -// } else if (g.isInheritedFrom(r, sr.Component)) { -// // Reusable component types -// Resource composite = g.getPossibleObject(r, sr.IsDefinedBy); -// Resource diagram = (composite != null && g.isInstanceOf(composite, sr.Composite)) ? g.getPossibleObject(composite, mr.CompositeToDiagram) : null; -// String name = safeGetName(r); -// System.out.println("Component type root: " + name); -// roots.add(new Node(null, name, diagram, r, composite)); -// result.roots.add(roots.peekLast()); } } @@ -206,35 +141,41 @@ public class CollectionRequest implements Read { private void loadComposites(ReadGraph graph, final Node node) throws DatabaseException { Resource diagram = node.getDiagramResource(); - //System.out.println("loadComposites(" + diagram + ", " + node + ")"); - if (diagram != null) { + if (DEBUG) + System.out.println("loadComposites(" + diagram + ", " + node + ")"); + if (diagram != null) result.addDiagram(diagram, node); -// node.setPartOfGroups(getPartOfGroups(diagram)); - } mon.setWorkRemaining(1000); for(Resource r : graph.getObjects(node.getDefiningResources().resources[0], l0.ConsistsOf)) { - if(graph.isInstanceOf(r, sr.Composite)) { - String compositeName = graph.syncRequest(new GetName(r)); - Resource definingDiagram = graph.getPossibleObject(r, mr.CompositeToDiagram); - Node n = new Node(node, compositeName, definingDiagram, r); + if(graph.isInstanceOf(r, sr.Composite)) { + String compositeName = graph.syncRequest(new GetName(r)); + Resource definingDiagram = graph.getPossibleObject(r, mr.CompositeToDiagram); + Node n = new Node(node, compositeName, definingDiagram, r); + if (DEBUG) + System.out.println("Found composite: " + n); loadComposites(graph, n); mon.worked(1); - } else if (graph.isInstanceOf(r, l0.Library)) { - String compositeName = graph.syncRequest(new GetName(r)); - Node n = new Node(node, compositeName, null, r); + } else if (graph.isInstanceOf(r, l0.Library)) { + String compositeName = graph.syncRequest(new GetName(r)); + Node n = new Node(node, compositeName, null, r); + if (DEBUG) + System.out.println("Found library: " + n); loadComposites(graph, n); mon.worked(1); - } else if (graph.isInheritedFrom(r, sr.Component)) { - String name = safeGetName(r); - Node n = new Node(node, name, null, r); - loadComposites(graph, n); - mon.worked(1); - } + } else if (graph.isInheritedFrom(r, sr.Component)) { + Resource definedBy = graph.getPossibleObject(r, sr.IsDefinedBy); + if (definedBy == null) + continue; + String name = safeGetName(r); + Node n = new Node(node, name, null, r); + if (DEBUG) + System.out.println("Found component: " + n); + loadComposites(graph, n); + mon.worked(1); + } } - } - }); ILog log = Platform.getLog(Platform.getBundle(Plugin.PLUGIN_ID)); @@ -263,9 +204,8 @@ public class CollectionRequest implements Read { return result; } - - static class GetName extends ResourceRead { + static class GetName extends ResourceRead { public GetName(Resource resource) { super(resource); } @@ -274,11 +214,10 @@ public class CollectionRequest implements Read { public String perform(ReadGraph graph) throws DatabaseException { try { return NameLabelUtil.modalName(graph, resource); - } catch (AdaptionException e) { + } catch (DatabaseException e) { return NameUtils.getSafeName(graph, resource); } } - } } \ No newline at end of file diff --git a/bundles/org.simantics.modeling/src/org/simantics/modeling/requests/CollectionResult.java b/bundles/org.simantics.modeling/src/org/simantics/modeling/requests/CollectionResult.java index ff06e43d0..c182c59c8 100644 --- a/bundles/org.simantics.modeling/src/org/simantics/modeling/requests/CollectionResult.java +++ b/bundles/org.simantics.modeling/src/org/simantics/modeling/requests/CollectionResult.java @@ -29,6 +29,9 @@ import org.simantics.scl.runtime.function.Function1; */ public class CollectionResult { + public static final IFilter DIAGRAM_RESOURCE_FILTER = o -> Nodes.DIAGRAM_RESOURCE_PREDICATE.test((Node) o); + public static final IFilter DIAGRAM_RESOURCE_AND_RVI_FILTER = o -> Nodes.DIAGRAM_RESOURCE_AND_RVI_PREDICATE.test((Node) o); + public class DiagramFilter implements IFilter { private final IFilter proxy; @@ -44,10 +47,28 @@ public class CollectionResult { } - final public Set roots = new ConcurrentSkipListSet(); - final private Set diagramSet = new ConcurrentSkipListSet(); - final public List diagramList = new Vector(); - final public Map diagrams = new ConcurrentHashMap(); + public final Set roots; + private final Set diagramSet; + public final List diagramList; + public final Map diagrams; + + public CollectionResult() { + this.roots = new ConcurrentSkipListSet(); + this.diagramSet = new ConcurrentSkipListSet(); + this.diagramList = new Vector(); + this.diagrams = new ConcurrentHashMap(); + } + + private CollectionResult(Set roots, Set diagramSet, List diagramList, Map diagrams) { + this.roots = roots; + this.diagramSet = diagramSet; + this.diagramList = diagramList; + this.diagrams = diagrams; + } + + public CollectionResult withRoots(Set roots) { + return new CollectionResult(roots, diagramSet, diagramList, diagrams); + } public void addDiagram(Resource r, Node n) { diagramList.add(n); diff --git a/bundles/org.simantics.modeling/src/org/simantics/modeling/requests/Node.java b/bundles/org.simantics.modeling/src/org/simantics/modeling/requests/Node.java index 3d52b2604..4a43e0840 100644 --- a/bundles/org.simantics.modeling/src/org/simantics/modeling/requests/Node.java +++ b/bundles/org.simantics.modeling/src/org/simantics/modeling/requests/Node.java @@ -27,15 +27,12 @@ import org.simantics.utils.strings.AlphanumComparator; */ public class Node implements Comparable { - public static final Comparator CASE_INSENSITIVE_COMPARATOR = new Comparator() { - @Override - public int compare(Node o1, Node o2) { - return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(o1.getName(), o2.getName()); - } - }; + public static final Comparator CASE_INSENSITIVE_COMPARATOR = + (o1, o2) -> AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(o1.getName(), o2.getName()); private final Node parent; private final List children = new ArrayList(); + private final List unmodifiableChildren = Collections.unmodifiableList(children); /** * May be null if there is no diagram for this node. @@ -44,10 +41,8 @@ public class Node implements Comparable { private final ResourceArray definingResource; // i.e. Composite private final String name; -// private String[] partOfGroups = {}; - private PageDesc pageDesc; - private String RVI; + private String rvi; /** * @param parent @@ -67,6 +62,13 @@ public class Node implements Comparable { parent.addChild(this); } + public Node cloneWithoutChildren(Node parent) { + Node clone = new Node(parent, name, diagram, definingResource.resources); + clone.setRVI(rvi); + clone.setPageDesc(pageDesc); + return clone; + } + public Node getParent() { return parent; } @@ -100,27 +102,19 @@ public class Node implements Comparable { } public Collection getChildren() { - return Collections.unmodifiableCollection(children); + return unmodifiableChildren; } -// public void setPartOfGroups(Collection groups) { -// this.partOfGroups = groups.toArray(new String[groups.size()]); -// } -// -// public String[] getPartOfGroups() { -// return partOfGroups; -// } - public void setPageDesc(PageDesc pageDesc) { this.pageDesc = pageDesc; } - public void setRVI(String RVI) { - this.RVI = RVI; + public void setRVI(String rvi) { + this.rvi = rvi; } public String getRVI() { - return RVI; + return rvi; } public PageDesc getPageDesc() { diff --git a/bundles/org.simantics.modeling/src/org/simantics/modeling/requests/Nodes.java b/bundles/org.simantics.modeling/src/org/simantics/modeling/requests/Nodes.java index 01c89aaf1..5d1da8797 100644 --- a/bundles/org.simantics.modeling/src/org/simantics/modeling/requests/Nodes.java +++ b/bundles/org.simantics.modeling/src/org/simantics/modeling/requests/Nodes.java @@ -7,6 +7,9 @@ import java.util.Collections; import java.util.Comparator; import java.util.Deque; import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Predicate; import org.eclipse.jface.viewers.IFilter; import org.simantics.scl.runtime.function.Function1; @@ -16,14 +19,17 @@ import org.simantics.scl.runtime.function.Function1; */ public class Nodes { + public static final Predicate DIAGRAM_RESOURCE_PREDICATE = n -> n.getDiagramResource() != null; + public static final Predicate DIAGRAM_RESOURCE_AND_RVI_PREDICATE = n -> n.getDiagramResource() != null && n.getRVI() != null; + public static Collection breadthFirstFlatten(IFilter filter, Collection roots) { - Collection result = new ArrayList(); - List sortedRoots = new ArrayList(roots); + Collection result = new ArrayList<>(); + List sortedRoots = new ArrayList<>(roots); Collections.sort(sortedRoots); - Deque todo = new ArrayDeque(sortedRoots); + Deque todo = new ArrayDeque<>(sortedRoots); while (!todo.isEmpty()) { Node n = todo.removeFirst(); - List sorted = new ArrayList(n.getChildren()); + List sorted = new ArrayList<>(n.getChildren()); Collections.sort(sorted); todo.addAll(sorted); if (filter == null || filter.select(n)) @@ -33,8 +39,8 @@ public class Nodes { } public static Collection depthFirstFlatten(IFilter filter, Collection roots, Comparator comparator) { - Collection result = new ArrayList(); - List sortedRoots = new ArrayList(roots); + Collection result = new ArrayList<>(); + List sortedRoots = new ArrayList<>(roots); Collections.sort(sortedRoots, comparator); for (Node n : sortedRoots) { depthFirstFlattenRec(filter, comparator, n, result); @@ -50,7 +56,7 @@ public class Nodes { if (children.isEmpty()) return result; - List sorted = new ArrayList(children); + List sorted = new ArrayList<>(children); Collections.sort(sorted, comparator); for (Node child : sorted) depthFirstFlattenRec(filter, comparator, child, result); @@ -67,7 +73,7 @@ public class Nodes { * if the walk was cancelled */ public static boolean walkTree(Function1 filter, Collection roots) { - List sortedRoots = new ArrayList(roots); + List sortedRoots = new ArrayList<>(roots); Collections.sort(sortedRoots); for (Node n : sortedRoots) if (!walkTreeRec(filter, n)) @@ -81,7 +87,7 @@ public class Nodes { Collection children = n.getChildren(); if (!children.isEmpty()) { - List sorted = new ArrayList(children); + List sorted = new ArrayList<>(children); Collections.sort(sorted); for (Node child : sorted) if (!walkTreeRec(filter, child)) @@ -90,4 +96,41 @@ public class Nodes { return true; } + public static boolean parentIsInSet(Set set, Node node) { + for (Node n = node.getParent(); n != null; n = n.getParent()) + if (set.contains(n)) + return true; + return false; + } + + public static Set depthFirstFilter(Predicate filter, Collection nodes) { + Set result = new TreeSet<>(Node.CASE_INSENSITIVE_COMPARATOR); + for (Node n : nodes) { + Node newNode = depthFirstFilterRec(filter, n, null); + if (newNode != null) + result.add(newNode); + } + return result; + } + + public static Node depthFirstFilter(Predicate filter, Node n) { + return depthFirstFilterRec(filter, n, null); + } + + private static Node depthFirstFilterRec(Predicate filter, Node n, Node newParent) { + Collection children = n.getChildren(); + if (children.isEmpty()) + return filter.test(n) ? n.cloneWithoutChildren(newParent) : null; + + Node newNode = n.cloneWithoutChildren(newParent); + int childCount = 0; + for (Node child : children) { + Node newChild = depthFirstFilterRec(filter, child, newNode); + if (newChild != null) + ++childCount; + } + + return childCount > 0 ? newNode : null; + } + }