X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.modeling.ui%2Fsrc%2Forg%2Fsimantics%2Fmodeling%2Fui%2Fpdf%2FPDFExportPage.java;fp=bundles%2Forg.simantics.modeling.ui%2Fsrc%2Forg%2Fsimantics%2Fmodeling%2Fui%2Fpdf%2FPDFExportPage.java;h=ac6dd5d7cebf02910b8a9d6582eed2d1960c6346;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git 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 new file mode 100644 index 000000000..ac6dd5d7c --- /dev/null +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/pdf/PDFExportPage.java @@ -0,0 +1,716 @@ +/******************************************************************************* + * Copyright (c) 2007, 2010 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: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +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.Node; +import org.simantics.utils.FileUtils; +import org.simantics.utils.strings.AlphanumComparator; +import org.simantics.utils.ui.ISelectionUtils; + +public class PDFExportPage extends WizardPage { + + protected Display display; + + protected PDFExportPlan exportModel; + + protected IFilterStrategy filterStrategy = new DefaultFilterStrategy(); + + protected Combo modelSelector; + protected SelectionListener modelSelectorListener; + + protected Text filter; + + protected Matcher matcher = null; + + protected CheckboxTreeViewer tree; + + protected CCombo exportLocation; + protected ModifyListener exportLocationListener; + + protected Set selectedNodes; + + protected LocalResourceManager resourceManager; + protected Color noDiagramColor; + + 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; + this.selectedNodes = exportModel.selectedNodeSet; + } + + @Override + public void createControl(Composite parent) { + this.display = parent.getDisplay(); + + Composite container = new Composite(parent, SWT.NONE); + { + GridLayout layout = new GridLayout(); + layout.horizontalSpacing = 20; + layout.verticalSpacing = 10; + 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:"); + GridDataFactory.fillDefaults().span(1, 1).applyTo(modelSelectorLabel); + modelSelector = new Combo(container, SWT.BORDER | SWT.READ_ONLY); + GridDataFactory.fillDefaults().span(2, 1).applyTo(modelSelector); + modelSelectorListener = new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + NamedResource data = (NamedResource) modelSelector.getData(String.valueOf(modelSelector.getSelectionIndex())); + scheduleInitializeData(data); + } + }; + + // Fill model selector combo + for (int i = 0; i < exportModel.selectableModels.size(); ++i) { + NamedResource nr = exportModel.selectableModels.get(i); + modelSelector.add(nr.getName()); + modelSelector.setData("" + i, nr); + } + + 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(); + } + }); + + toFileLabel = new Label(container, SWT.NONE); + toFileLabel.setText("&To file:"); + exportLocation = new CCombo(container, SWT.BORDER); + { + exportLocation.setText(""); + GridDataFactory.fillDefaults().grab(true, false).span(1, 1).applyTo(exportLocation); + + for (String path : exportModel.recentLocations) { + exportLocation.add(path); + } + + exportLocationListener = new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + System.out.println("export location changed by user"); + exportLocationTouchedByUser = true; + validatePage(); + } + }; + exportLocation.addModifyListener(exportLocationListener); + } + Button browseFileButton = new Button(container, SWT.PUSH); + { + browseFileButton.setText("Browse..."); + browseFileButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); + browseFileButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + FileDialog dialog = new FileDialog(getShell(), SWT.SAVE); + dialog.setFilterExtensions(new String[] { "*.pdf" }); + dialog.setFilterNames(new String[] { "PDF Document" }); + String loc = exportLocation.getText(); + if (loc != null) { + IPath p = new Path(loc); + File f = p.toFile(); + if (f.isDirectory()) { + dialog.setFilterPath(f.toString()); + } else if (f.isFile()) { + IPath path = p.removeLastSegments(1); + String name = p.lastSegment(); + dialog.setFilterPath(path.toOSString()); + dialog.setFileName(name); + } else { + dialog.setFilterPath(f.toString()); + IPath path = p.removeLastSegments(1); + String name = p.lastSegment(); + f = path.toFile(); + if (f.isDirectory()) { + dialog.setFilterPath(path.toOSString()); + } + dialog.setFileName(name); + } + } + String file = dialog.open(); + if (file == null) + return; + exportLocation.setText(file); + validatePage(); + } + }); + } + + final Button zoomToFitButton = new Button(container, SWT.CHECK); + GridDataFactory.fillDefaults().grab(true, false).span(3, 1).applyTo(zoomToFitButton); + zoomToFitButton.setText("F&it by content"); + zoomToFitButton.setSelection(exportModel.fitContentToPageMargins); + zoomToFitButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + exportModel.fitContentToPageMargins = zoomToFitButton.getSelection(); + } + }); + + /* + final Button attachTGButton = new Button(container, SWT.CHECK); + GridDataFactory.fillDefaults().grab(true, false).span(3, 1).applyTo( attachTGButton ); + attachTGButton.setText("Attach &TG (Importable diagram)"); + attachTGButton.setSelection(exportModel.attachTG); + attachTGButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + exportModel.attachTG = attachTGButton.getSelection(); + } + }); + */ + + final Button attachWikiButton = new Button(container, SWT.CHECK); + GridDataFactory.fillDefaults().grab(true, false).span(3, 1).applyTo( attachWikiButton ); + attachWikiButton.setText("Attach &Wiki page"); + attachWikiButton.setSelection(exportModel.attachWiki); + attachWikiButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + exportModel.attachWiki = attachWikiButton.getSelection(); + } + }); + + setControl(container); + validatePage(); + + scheduleInitializeData(exportModel.selection); + } + + private void scheduleInitializeData(final NamedResource modelSelection) { + display.asyncExec(new Runnable() { + @Override + public void run() { + if (filter.isDisposed()) + return; + + try { + initializeData(modelSelection); + } catch (DatabaseException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.getTargetException().printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + } + + private NamedResource getSelectedModel() { + int sel = modelSelector.getSelectionIndex(); + if (sel != -1) { + NamedResource nr = (NamedResource) modelSelector.getData("" + sel); + return nr; + } + return null; + } + + private void setExportLocationWithoutNotification(String text) { + exportLocation.removeModifyListener(exportLocationListener); + exportLocation.setText(text); + 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 { + if (modelSelection != null) { + // Process input selection to find the model/state selected by default. + + // This may take longer than the user wants to wait without + // notification. + + // !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() }); + } + }); + } 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."); + } + + // Browsing was canceled by user. + if (exportModel.nodes == null) + return; + + // Setup selected states, select everything by default. + selectedNodes.clear(); + selectedNodes.addAll(exportModel.nodes.breadthFirstFlatten()); + + tree.setInput(this); + + for (Node root : exportModel.nodes.roots) { + tree.setSubtreeChecked(root, true); + } + + resetFilterString(filter.getText()); + + modelSelector.removeSelectionListener(modelSelectorListener); + int selectedIndex = -1; + for (int i = 0; i < modelSelector.getItemCount(); ++i) { + Object obj = modelSelector.getData("" + i); + if (org.simantics.utils.ObjectUtils.objectEquals(obj, modelSelection)) { + selectedIndex = i; + } + } + if (selectedIndex == -1 && modelSelector.getItemCount() > 0) + selectedIndex = 0; + if (selectedIndex != -1) + modelSelector.select(selectedIndex); + modelSelector.addSelectionListener(modelSelectorListener); + + validatePage(); + } + + void validatePage() { + //System.out.println("VALIDATE PAGE: " + exportLocationTouchedByUser); + if (selectedNodes.size() == 0) { + setMessage("Select the diagrams to export."); + setErrorMessage(null); + setPageComplete(false); + return; + } + + if (!exportLocationTouchedByUser) { + String generatedName = null; + // 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(); + } else { + generatedName = nr.getName(); + } + } + //System.out.println("generate name: " + generatedName); + if (generatedName != null) { + if (!FileUtils.isValidFileName(generatedName)) + generatedName = (String) Bindings.STR_VARIANT.createUnchecked(Bindings.STRING, generatedName); + String name = generatedName + ".pdf"; + + abu: + if ( !exportModel.recentLocations.isEmpty() ) { + + for ( String loc : exportModel.recentLocations ) + { + if ( loc.endsWith(name) && !loc.equals(name) ) { + name = loc; + break abu; + } + } + + String firstLine = exportModel.recentLocations.iterator().next(); + File f = new File(firstLine); + File parentFile = f.getParentFile(); + if ( parentFile!=null ) { + name = new File( f.getParentFile(), name ).getAbsolutePath(); + } + } + setExportLocationWithoutNotification(name); + } + } + + String exportLoc = exportLocation.getText(); + if (exportLoc.isEmpty()) { + setMessage("Select an export target file."); + setErrorMessage(null); + setPageComplete(false); + return; + } + File file = new File(exportLoc); + if (file.exists()) { + if (file.isDirectory()) { + setErrorMessage("The target already exists and it is a directory."); + setPageComplete(false); + return; + } + if (!file.isFile()) { + setErrorMessage("The target already exists and it is a not a regular file."); + setPageComplete(false); + return; + } + } + exportModel.exportLocation = file; + + int diagramCount = 0; + for (Node n : selectedNodes) + if (n.getDiagramResource() != null) + ++diagramCount; + + String msg = diagramCount + " diagrams selected for export."; + + setMessage(msg); + setErrorMessage(null); + setPageComplete(true); + } + +}