1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.modeling.ui.pdf;
15 import java.lang.reflect.InvocationTargetException;
16 import java.util.ArrayDeque;
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.Deque;
20 import java.util.HashSet;
21 import java.util.List;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
26 import org.eclipse.core.runtime.IPath;
27 import org.eclipse.core.runtime.IProgressMonitor;
28 import org.eclipse.core.runtime.Path;
29 import org.eclipse.core.runtime.SubMonitor;
30 import org.eclipse.jface.layout.GridDataFactory;
31 import org.eclipse.jface.operation.IRunnableWithProgress;
32 import org.eclipse.jface.resource.JFaceResources;
33 import org.eclipse.jface.resource.LocalResourceManager;
34 import org.eclipse.jface.viewers.CellLabelProvider;
35 import org.eclipse.jface.viewers.CheckStateChangedEvent;
36 import org.eclipse.jface.viewers.CheckboxTreeViewer;
37 import org.eclipse.jface.viewers.ICheckStateListener;
38 import org.eclipse.jface.viewers.ICheckStateProvider;
39 import org.eclipse.jface.viewers.ITreeContentProvider;
40 import org.eclipse.jface.viewers.StructuredSelection;
41 import org.eclipse.jface.viewers.TreeViewer;
42 import org.eclipse.jface.viewers.Viewer;
43 import org.eclipse.jface.viewers.ViewerCell;
44 import org.eclipse.jface.viewers.ViewerComparator;
45 import org.eclipse.jface.viewers.ViewerFilter;
46 import org.eclipse.jface.wizard.WizardPage;
47 import org.eclipse.swt.SWT;
48 import org.eclipse.swt.custom.CCombo;
49 import org.eclipse.swt.events.DisposeEvent;
50 import org.eclipse.swt.events.DisposeListener;
51 import org.eclipse.swt.events.ModifyEvent;
52 import org.eclipse.swt.events.ModifyListener;
53 import org.eclipse.swt.events.SelectionAdapter;
54 import org.eclipse.swt.events.SelectionEvent;
55 import org.eclipse.swt.events.SelectionListener;
56 import org.eclipse.swt.graphics.Color;
57 import org.eclipse.swt.layout.GridData;
58 import org.eclipse.swt.layout.GridLayout;
59 import org.eclipse.swt.layout.RowLayout;
60 import org.eclipse.swt.widgets.Button;
61 import org.eclipse.swt.widgets.Combo;
62 import org.eclipse.swt.widgets.Composite;
63 import org.eclipse.swt.widgets.Display;
64 import org.eclipse.swt.widgets.FileDialog;
65 import org.eclipse.swt.widgets.Label;
66 import org.eclipse.swt.widgets.Text;
67 import org.eclipse.swt.widgets.TreeItem;
68 import org.simantics.browsing.ui.common.views.DefaultFilterStrategy;
69 import org.simantics.browsing.ui.common.views.IFilterStrategy;
70 import org.simantics.databoard.Bindings;
71 import org.simantics.db.ReadGraph;
72 import org.simantics.db.Resource;
73 import org.simantics.db.common.NamedResource;
74 import org.simantics.db.common.request.ReadRequest;
75 import org.simantics.db.exception.DatabaseException;
76 import org.simantics.modeling.requests.Node;
77 import org.simantics.utils.FileUtils;
78 import org.simantics.utils.strings.AlphanumComparator;
79 import org.simantics.utils.ui.ISelectionUtils;
81 public class PDFExportPage extends WizardPage {
83 protected Display display;
85 protected PDFExportPlan exportModel;
87 protected IFilterStrategy filterStrategy = new DefaultFilterStrategy();
89 protected Combo modelSelector;
90 protected SelectionListener modelSelectorListener;
92 protected Text filter;
94 protected Matcher matcher = null;
96 protected CheckboxTreeViewer tree;
98 protected CCombo exportLocation;
99 protected ModifyListener exportLocationListener;
101 protected Set<Node> selectedNodes;
103 protected LocalResourceManager resourceManager;
104 protected Color noDiagramColor;
106 protected Label toFileLabel;
108 protected boolean exportLocationTouchedByUser = false;
110 ICheckStateProvider checkStateProvider = new ICheckStateProvider() {
112 public boolean isChecked(Object element) {
113 Node node = (Node) element;
115 // Primarily checked if any children are selected.
116 Collection<Node> children = node.getChildren();
117 if (!children.isEmpty()) {
118 for (Node child : node.getChildren())
119 if (isChecked(child))
122 // No children are checked, not checked.
126 // Otherwise checked only if selected.
127 return selectedNodes.contains(node);
130 public boolean isGrayed(Object element) {
131 Node node = (Node) element;
133 // Grayed if there are children but not all of them are selected.
134 Collection<Node> children = node.getChildren();
135 if (!children.isEmpty()) {
136 for (Node child : children)
137 if (!selectedNodes.contains(child))
141 // Grayed if the node itself contains no diagram.
142 if (node.getDiagramResource() == null)
145 // Otherwise never grayed.
150 protected PDFExportPage(PDFExportPlan model) {
151 super("Export Diagrams to PDF", "Define Exported Items", null);
152 this.exportModel = model;
153 this.selectedNodes = exportModel.selectedNodeSet;
157 public void createControl(Composite parent) {
158 this.display = parent.getDisplay();
160 Composite container = new Composite(parent, SWT.NONE);
162 GridLayout layout = new GridLayout();
163 layout.horizontalSpacing = 20;
164 layout.verticalSpacing = 10;
165 layout.numColumns = 3;
166 container.setLayout(layout);
168 resourceManager = new LocalResourceManager(JFaceResources.getResources());
169 container.addDisposeListener(new DisposeListener() {
171 public void widgetDisposed(DisposeEvent e) {
172 resourceManager.dispose();
175 noDiagramColor = container.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY);
177 Label modelSelectorLabel = new Label(container, SWT.NONE);
178 modelSelectorLabel.setText("Model Selector:");
179 GridDataFactory.fillDefaults().span(1, 1).applyTo(modelSelectorLabel);
180 modelSelector = new Combo(container, SWT.BORDER | SWT.READ_ONLY);
181 GridDataFactory.fillDefaults().span(2, 1).applyTo(modelSelector);
182 modelSelectorListener = new SelectionAdapter() {
184 public void widgetSelected(SelectionEvent e) {
185 NamedResource data = (NamedResource) modelSelector.getData(String.valueOf(modelSelector.getSelectionIndex()));
186 scheduleInitializeData(data);
190 // Fill model selector combo
191 for (int i = 0; i < exportModel.selectableModels.size(); ++i) {
192 NamedResource nr = exportModel.selectableModels.get(i);
193 modelSelector.add(nr.getName());
194 modelSelector.setData("" + i, nr);
197 modelSelector.addSelectionListener(modelSelectorListener);
199 // Label label = new Label(container, SWT.NONE);
200 // label.setText("Diagrams to Export:");
201 // GridDataFactory.fillDefaults().span(3, 1).applyTo(label);
203 Label filterLabel = new Label(container, SWT.NONE);
204 filterLabel.setText("Fi<er:");
205 GridDataFactory.fillDefaults().span(1, 1).applyTo(filterLabel);
206 filter = new Text(container, SWT.BORDER);
207 GridDataFactory.fillDefaults().span(2, 1).applyTo(filter);
208 filter.addModifyListener(new ModifyListener() {
210 public void modifyText(ModifyEvent e) {
211 resetFilterString(filter.getText());
215 tree = new CheckboxTreeViewer(container, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION);
217 tree.setUseHashlookup(true);
218 GridDataFactory.fillDefaults().grab(true, true).span(3, 1).applyTo(tree.getControl());
219 tree.getControl().setToolTipText("Selects the diagram to include in the exported document.");
220 tree.setAutoExpandLevel(TreeViewer.ALL_LEVELS);
221 tree.addCheckStateListener(new ICheckStateListener(){
222 void addOrRemoveSelection(Node node, boolean add) {
224 selectedNodes.add(node);
226 selectedNodes.remove(node);
228 void addOrRemoveSelectionRec(Node node, boolean add) {
229 addOrRemoveSelection(node, add);
230 for (Node child : node.getChildren())
231 addOrRemoveSelectionRec(child, add);
234 public void checkStateChanged(CheckStateChangedEvent event) {
235 final boolean checked = event.getChecked();
236 Node checkedNode = (Node) event.getElement();
238 Set<Node> nodes = new HashSet<Node>();
239 Set<Node> selection = ISelectionUtils.filterSetSelection(tree.getSelection(), Node.class);
240 if (selection.contains(checkedNode))
241 nodes.addAll(selection);
243 tree.setSelection(StructuredSelection.EMPTY);
244 nodes.add(checkedNode);
246 for (Node node : nodes) {
247 addOrRemoveSelectionRec(node, checked);
249 // tree.setSubtreeChecked(node, checked);
250 // The checked node is always either checked or not checked, never grayed.
251 // tree.setGrayed(node, checkStateProvider.isGrayed(node));
253 // Node parent = node.getParent();
254 // if (parent != null) {
255 // tree.setChecked(parent, checkStateProvider.isChecked(parent));
256 // tree.setGrayed(parent, checkStateProvider.isGrayed(parent));
260 refreshAndExpandTree();
265 tree.setContentProvider(new ITreeContentProvider(){
267 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
270 public void dispose() {
273 public Object[] getElements(Object inputElement) {
274 return exportModel.nodes.roots.toArray();
277 public boolean hasChildren(Object element) {
278 Node n = (Node) element;
279 if (n.getChildren().isEmpty()) return false;
280 for (Node c : n.getChildren()) if (hasDiagram(c)) return true;
285 public Object getParent(Object element) {
286 Node n = (Node) element;
287 return n.getParent();
290 public Object[] getChildren(Object parentElement) {
291 Node n = (Node) parentElement;
292 List<Object> result = new ArrayList<Object>( n.getChildren().size() );
293 for (Node c : n.getChildren())
296 return result.toArray();
299 boolean hasDiagram(Node n)
301 if (n.getDiagramResource()!=null) return true;
302 for (Node c : n.getChildren()) if (hasDiagram(c)) return true;
306 tree.setLabelProvider(new CellLabelProvider() {
308 public void update(ViewerCell cell) {
309 Object e = cell.getElement();
310 if (e instanceof Node) {
312 String name = DiagramPrinter.formDiagramName(n, false);
315 if (n.getDiagramResource() == null)
316 cell.setForeground(noDiagramColor);
318 cell.setForeground(null);
320 cell.setText("invalid input: " + e.getClass().getSimpleName());
324 tree.setComparator(new ViewerComparator(AlphanumComparator.CASE_INSENSITIVE_COMPARATOR));
325 tree.setFilters(new ViewerFilter[] {
328 public boolean select(Viewer viewer, Object parentElement, Object element) {
332 Node node = (Node) element;
333 // If any children are in sight, show this element.
334 for (Node child : node.getChildren()) {
335 if (select(viewer, element, child))
339 return matcher.reset(node.getName().toLowerCase()).matches();
343 tree.setCheckStateProvider(checkStateProvider);
346 Composite bar = new Composite(container, SWT.NONE);
347 GridDataFactory.fillDefaults().grab(true, false).span(3, 1).applyTo(bar);
348 bar.setLayout(new RowLayout());
349 Button selectAll = new Button(bar, SWT.PUSH);
350 selectAll.setText("Select &All");
351 selectAll.addSelectionListener(new SelectionAdapter() {
353 public void widgetSelected(SelectionEvent e) {
354 selectedNodes.addAll(exportModel.nodes.breadthFirstFlatten());
355 for (Node root : exportModel.nodes.roots)
356 tree.setSubtreeChecked(root, true);
360 Button clearSelection = new Button(bar, SWT.PUSH);
361 clearSelection.setText("&Clear Selection");
362 clearSelection.addSelectionListener(new SelectionAdapter() {
364 public void widgetSelected(SelectionEvent e) {
365 selectedNodes.clear();
366 for (Node root : exportModel.nodes.roots)
367 tree.setSubtreeChecked(root, false);
371 Button selectVisible = new Button(bar, SWT.PUSH);
372 selectVisible.setText("&Select Visible");
373 selectVisible.addSelectionListener(new SelectionAdapter() {
375 public void widgetSelected(SelectionEvent e) {
376 selectedNodes.addAll(getVisibleNodes());
377 refreshAndExpandTree();
381 Button deselectVisible = new Button(bar, SWT.PUSH);
382 deselectVisible.setText("&Deselect Visible");
383 deselectVisible.addSelectionListener(new SelectionAdapter() {
385 public void widgetSelected(SelectionEvent e) {
386 selectedNodes.removeAll(getVisibleNodes());
387 refreshAndExpandTree();
392 toFileLabel = new Label(container, SWT.NONE);
393 toFileLabel.setText("&To file:");
394 exportLocation = new CCombo(container, SWT.BORDER);
396 exportLocation.setText("");
397 GridDataFactory.fillDefaults().grab(true, false).span(1, 1).applyTo(exportLocation);
399 for (String path : exportModel.recentLocations) {
400 exportLocation.add(path);
403 exportLocationListener = new ModifyListener() {
405 public void modifyText(ModifyEvent e) {
406 //System.out.println("export location changed by user");
407 exportLocationTouchedByUser = true;
411 exportLocation.addModifyListener(exportLocationListener);
413 Button browseFileButton = new Button(container, SWT.PUSH);
415 browseFileButton.setText("Browse...");
416 browseFileButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
417 browseFileButton.addSelectionListener(new SelectionAdapter() {
419 public void widgetSelected(SelectionEvent e) {
420 FileDialog dialog = new FileDialog(getShell(), SWT.SAVE);
421 dialog.setFilterExtensions(new String[] { "*.pdf" });
422 dialog.setFilterNames(new String[] { "PDF Document" });
423 String loc = exportLocation.getText();
425 IPath p = new Path(loc);
427 if (f.isDirectory()) {
428 dialog.setFilterPath(f.toString());
429 } else if (f.isFile()) {
430 IPath path = p.removeLastSegments(1);
431 String name = p.lastSegment();
432 dialog.setFilterPath(path.toOSString());
433 dialog.setFileName(name);
435 dialog.setFilterPath(f.toString());
436 IPath path = p.removeLastSegments(1);
437 String name = p.lastSegment();
439 if (f.isDirectory()) {
440 dialog.setFilterPath(path.toOSString());
442 dialog.setFileName(name);
445 String file = dialog.open();
448 exportLocation.setText(file);
454 final Button zoomToFitButton = new Button(container, SWT.CHECK);
455 GridDataFactory.fillDefaults().grab(true, false).span(3, 1).applyTo(zoomToFitButton);
456 zoomToFitButton.setText("F&it by content");
457 zoomToFitButton.setSelection(exportModel.fitContentToPageMargins);
458 zoomToFitButton.addSelectionListener(new SelectionAdapter() {
460 public void widgetSelected(SelectionEvent e) {
461 exportModel.fitContentToPageMargins = zoomToFitButton.getSelection();
466 final Button attachTGButton = new Button(container, SWT.CHECK);
467 GridDataFactory.fillDefaults().grab(true, false).span(3, 1).applyTo( attachTGButton );
468 attachTGButton.setText("Attach &TG (Importable diagram)");
469 attachTGButton.setSelection(exportModel.attachTG);
470 attachTGButton.addSelectionListener(new SelectionAdapter() {
472 public void widgetSelected(SelectionEvent e) {
473 exportModel.attachTG = attachTGButton.getSelection();
478 final Button attachWikiButton = new Button(container, SWT.CHECK);
479 GridDataFactory.fillDefaults().grab(true, false).span(3, 1).applyTo( attachWikiButton );
480 attachWikiButton.setText("Attach &Wiki page");
481 attachWikiButton.setSelection(exportModel.attachWiki);
482 attachWikiButton.addSelectionListener(new SelectionAdapter() {
484 public void widgetSelected(SelectionEvent e) {
485 exportModel.attachWiki = attachWikiButton.getSelection();
489 setControl(container);
492 scheduleInitializeData(exportModel.selection);
495 private void scheduleInitializeData(final NamedResource modelSelection) {
496 display.asyncExec(new Runnable() {
499 if (filter.isDisposed())
503 initializeData(modelSelection);
504 } catch (DatabaseException e) {
506 } catch (InvocationTargetException e) {
507 e.getTargetException().printStackTrace();
508 } catch (InterruptedException e) {
515 private NamedResource getSelectedModel() {
516 int sel = modelSelector.getSelectionIndex();
518 NamedResource nr = (NamedResource) modelSelector.getData("" + sel);
524 private void setExportLocationWithoutNotification(String text) {
525 exportLocation.removeModifyListener(exportLocationListener);
526 exportLocation.setText(text);
527 exportLocation.addModifyListener(exportLocationListener);
530 private Collection<Node> getVisibleNodes() {
531 Collection<Node> result = new ArrayList<Node>();
533 Deque<TreeItem> todo = new ArrayDeque<TreeItem>();
534 for (TreeItem ti : tree.getTree().getItems()) {
538 while (!todo.isEmpty()) {
539 TreeItem item = todo.removeLast();
540 Node node = (Node) item.getData();
543 for (TreeItem child : item.getItems()) {
551 private void resetFilterString(String filterString) {
552 String patternString = filterStrategy.toPatternString(filterString);
553 if (patternString == null) {
556 matcher = Pattern.compile(patternString).matcher("");
558 refreshAndExpandTree();
561 private void refreshAndExpandTree() {
566 private void initializeData(final NamedResource modelSelection) throws DatabaseException, InvocationTargetException, InterruptedException {
567 if (modelSelection != null) {
568 // Process input selection to find the model/state selected by default.
570 // This may take longer than the user wants to wait without
574 long time = System.nanoTime();
576 getWizard().getContainer().run(true, true, new IRunnableWithProgress() {
578 public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
580 final SubMonitor mon = SubMonitor.convert(monitor, "Searching for exportable diagrams...", 100);
581 exportModel.sessionContext.getSession().syncRequest(new ReadRequest() {
583 public void run(ReadGraph graph) throws DatabaseException {
584 exportModel.nodes = DiagramPrinter.browse(mon.newChild(100), graph, new Resource[] { modelSelection.getResource() });
587 } catch (DatabaseException e) {
588 throw new InvocationTargetException(e);
596 long endTime = System.nanoTime();
597 if (exportModel.nodes != null)
598 System.out.println("Found " + exportModel.nodes.diagrams.size() + " diagrams in " + ((endTime - time)*1e-9) + " seconds.");
601 // Browsing was canceled by user.
602 if (exportModel.nodes == null)
605 // Setup selected states, select everything by default.
606 selectedNodes.clear();
607 selectedNodes.addAll(exportModel.nodes.breadthFirstFlatten());
611 for (Node root : exportModel.nodes.roots) {
612 tree.setSubtreeChecked(root, true);
615 resetFilterString(filter.getText());
617 modelSelector.removeSelectionListener(modelSelectorListener);
618 int selectedIndex = -1;
619 for (int i = 0; i < modelSelector.getItemCount(); ++i) {
620 Object obj = modelSelector.getData("" + i);
621 if (org.simantics.utils.ObjectUtils.objectEquals(obj, modelSelection)) {
625 if (selectedIndex == -1 && modelSelector.getItemCount() > 0)
627 if (selectedIndex != -1)
628 modelSelector.select(selectedIndex);
629 modelSelector.addSelectionListener(modelSelectorListener);
634 void validatePage() {
635 //System.out.println("VALIDATE PAGE: " + exportLocationTouchedByUser);
636 if (selectedNodes.size() == 0) {
637 setMessage("Select the diagrams to export.");
638 setErrorMessage(null);
639 setPageComplete(false);
643 if (!exportLocationTouchedByUser) {
644 String generatedName = null;
645 // Generate file name automatically if user hasn't touched the name manually.
646 NamedResource nr = getSelectedModel();
648 if (selectedNodes.size() == 1) {
649 generatedName = nr.getName() + "-" + selectedNodes.iterator().next().getName();
651 generatedName = nr.getName();
654 //System.out.println("generate name: " + generatedName);
655 if (generatedName != null) {
656 if (!FileUtils.isValidFileName(generatedName))
657 generatedName = (String) Bindings.STR_VARIANT.createUnchecked(Bindings.STRING, generatedName);
658 String name = generatedName + ".pdf";
661 if ( !exportModel.recentLocations.isEmpty() ) {
663 for ( String loc : exportModel.recentLocations )
665 if ( loc.endsWith(name) && !loc.equals(name) ) {
671 String firstLine = exportModel.recentLocations.iterator().next();
672 File f = new File(firstLine);
673 File parentFile = f.getParentFile();
674 if ( parentFile!=null ) {
675 name = new File( f.getParentFile(), name ).getAbsolutePath();
678 setExportLocationWithoutNotification(name);
682 String exportLoc = exportLocation.getText();
683 if (exportLoc.isEmpty()) {
684 setMessage("Select an export target file.");
685 setErrorMessage(null);
686 setPageComplete(false);
689 File file = new File(exportLoc);
691 if (file.isDirectory()) {
692 setErrorMessage("The target already exists and it is a directory.");
693 setPageComplete(false);
696 if (!file.isFile()) {
697 setErrorMessage("The target already exists and it is a not a regular file.");
698 setPageComplete(false);
702 exportModel.exportLocation = file;
704 int diagramCount = 0;
705 for (Node n : selectedNodes)
706 if (n.getDiagramResource() != null)
709 String msg = diagramCount + " diagrams selected for export.";
712 setErrorMessage(null);
713 setPageComplete(true);