]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFExportPage.java
Improved PDF diagram export wizard user experience
[simantics/platform.git] / bundles / org.simantics.modeling.ui / src / org / simantics / modeling / ui / pdf / PDFExportPage.java
index 7ef2efb9406f283b578f6bfdcabc7669d91e997a..fb2f8b0c640adf08c4dcfb845ca46ced5bfb880b 100644 (file)
@@ -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
  *
  * 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<Node>            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<Node> 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<Node> 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&lter:");
-        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<Node> nodes = new HashSet<Node>();
-                    Set<Node> 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<Object> result = new ArrayList<Object>( 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<Node> getVisibleNodes() {
-        Collection<Node> result = new ArrayList<Node>();
-
-        Deque<TreeItem> todo = new ArrayDeque<TreeItem>();
-        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<Node> 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<Resource> 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);