/******************************************************************************* * 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); } }