Source code: com/port80/eclipse/jdt/graph/views/GraphViewer.java
1 package com.port80.eclipse.jdt.graph.views;
2
3 import java.awt.image.BufferedImage;
4 import java.io.File;
5 import java.io.FileInputStream;
6 import java.io.FileNotFoundException;
7 import java.io.FileOutputStream;
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.io.PrintWriter;
11 import java.util.ArrayList;
12 import java.util.Comparator;
13 import java.util.HashMap;
14 import java.util.Iterator;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.Set;
18
19 import org.eclipse.core.resources.IMarker;
20 import org.eclipse.core.runtime.IProgressMonitor;
21 import org.eclipse.jface.action.IStatusLineManager;
22 import org.eclipse.jface.viewers.ILabelProvider;
23 import org.eclipse.jface.viewers.ILabelProviderListener;
24 import org.eclipse.jface.viewers.ISelection;
25 import org.eclipse.jface.viewers.ISelectionChangedListener;
26 import org.eclipse.jface.viewers.ISelectionProvider;
27 import org.eclipse.jface.viewers.IStructuredContentProvider;
28 import org.eclipse.jface.viewers.IStructuredSelection;
29 import org.eclipse.jface.viewers.SelectionChangedEvent;
30 import org.eclipse.jface.viewers.StructuredSelection;
31 import org.eclipse.jface.viewers.Viewer;
32 import org.eclipse.swt.SWT;
33 import org.eclipse.swt.events.DisposeEvent;
34 import org.eclipse.swt.events.DisposeListener;
35 import org.eclipse.swt.events.KeyEvent;
36 import org.eclipse.swt.events.KeyListener;
37 import org.eclipse.swt.events.MenuAdapter;
38 import org.eclipse.swt.events.MenuEvent;
39 import org.eclipse.swt.events.MouseAdapter;
40 import org.eclipse.swt.events.MouseEvent;
41 import org.eclipse.swt.events.SelectionAdapter;
42 import org.eclipse.swt.events.SelectionEvent;
43 import org.eclipse.swt.graphics.GC;
44 import org.eclipse.swt.graphics.Image;
45 import org.eclipse.swt.graphics.ImageData;
46 import org.eclipse.swt.graphics.PaletteData;
47 import org.eclipse.swt.graphics.Point;
48 import org.eclipse.swt.graphics.custom.DirtyRectangle;
49 import org.eclipse.swt.widgets.Composite;
50 import org.eclipse.swt.widgets.Control;
51 import org.eclipse.swt.widgets.Menu;
52 import org.eclipse.swt.widgets.MenuItem;
53 import org.eclipse.ui.IEditorInput;
54 import org.eclipse.ui.IEditorSite;
55 import org.eclipse.ui.PartInitException;
56 import org.eclipse.ui.internal.WorkbenchWindow;
57 import org.eclipse.ui.part.EditorPart;
58 import org.eclipse.ui.part.FileEditorInput;
59
60 import com.port80.eclipse.util.ColorFactory;
61 import com.port80.eclipse.util.GraphEditorInput;
62 import com.port80.eclipse.util.ImageFactory;
63 import com.port80.graph.IGraph;
64 import com.port80.graph.IVertex;
65 import com.port80.graph.dot.impl.Dot;
66 import com.port80.graph.dot.parser.DotParser;
67 import com.port80.graph.impl.GraphPanel;
68 import com.port80.swt.widgets.GraphViewIncrementalFinder;
69 import com.port80.swt.widgets.IGraphViewer;
70 import com.port80.swt.widgets.IStatusLine;
71 import com.port80.swt.widgets.ImageRegionInfo;
72 import com.port80.swt.widgets.ScrolledCanvas;
73 import com.port80.util.Msg;
74
75 /**
76 * A graph viewer in eclipse to view graph rendered in a BufferedImage.
77 *
78 * @author chrisl
79 */
80 public class GraphViewer extends EditorPart implements IGraphViewer, ISelectionProvider, KeyListener {
81
82 ////////////////////////////////////////////////////////////////////////
83
84 private static final String NAME = "GraphViewer";
85 private static final boolean DEBUG = true;
86 private static final int SEED = -7;
87 private static final int PASSES = -7;
88
89 private static final int ALPHA_HIGHLIGHT = 0x18;
90 private static final int COLOR_HIGHLIGHT = 0x000000;
91
92 ////////////////////////////////////////////////////////////////////////
93
94 private IGraph fGraph;
95 private String fFilepath;
96 private Image fImage;
97 private Menu fPopup;
98 private IStatusLine fStatusLine;
99 private ScrolledCanvas fCanvas;
100 private GraphContentProvider fContentProvider;
101 private GraphLabelProvider fLabelProvider;
102 private GraphLabelComparator fLabelComparator;
103 private List fSelection;
104 private List fSelectionListeners;
105 private boolean fDirty;
106 private boolean fBeginFindSession;
107 private boolean fForwardFind;
108 /**
109 * The list of ImageRegionInfo of the selected regions in the back buffer.
110 * When region is deselected, image is restore into the back buffer.
111 */
112 private List fSelectedRegions;
113
114 ////////////////////////////////////////////////////////////////////////
115
116 /**
117 * @see org.eclipse.ui.IEditorPart#doSave(IProgressMonitor)
118 */
119 public void doSave(IProgressMonitor monitor) {
120 if (fFilepath != null && fFilepath.length() > 0) {
121 Msg.println(NAME + ".saveGraph(): filename=" + fFilepath);
122 fGraph.saveImage(fFilepath, 1.0f);
123 if (!fFilepath.endsWith(".dot"))
124 fFilepath += ".dot";
125 File ifile = Msg.createFile(fFilepath);
126 if (ifile != null) {
127 try {
128 PrintWriter writer = new PrintWriter(new FileOutputStream(ifile));
129 writer.println(fGraph.sprintGraph());
130 writer.close();
131 } catch (FileNotFoundException e) {
132 Msg.println(NAME + ".saveGraph(): Can't open output .dot file: " + fFilepath);
133 }
134 }
135 }
136 fDirty = false;
137 }
138
139 /**
140 * @see org.eclipse.ui.IEditorPart#doSaveAs()
141 */
142 public void doSaveAs() {
143 }
144
145 /**
146 * @see org.eclipse.ui.IEditorPart#gotoMarker(IMarker)
147 */
148 public void gotoMarker(IMarker marker) {
149 }
150
151 /**
152 * @see org.eclipse.ui.IEditorPart#init(IEditorSite, IEditorInput)
153 */
154 public void init(IEditorSite site, IEditorInput input) throws PartInitException {
155 if (!(input instanceof FileEditorInput) && !(input instanceof GraphEditorInput)) {
156 throw new PartInitException("Input=" + input.getName());
157 }
158 fSelection = new ArrayList();
159 fSelectionListeners = new ArrayList();
160 fSelectedRegions = new ArrayList();
161 fLabelComparator = new GraphLabelComparator();
162 setInput(input);
163 setSite(site);
164 setTitle(input.getName());
165 setTitleToolTip(input.getToolTipText());
166 }
167
168 public void setInput(IEditorInput input) {
169 super.setInput(input);
170 //
171 fGraph = loadGraph(input);
172 if (fGraph == null)
173 return;
174 //
175 fContentProvider = new GraphContentProvider(fGraph);
176 fLabelProvider = new GraphLabelProvider(fGraph);
177 fSelection.clear();
178 fSelectedRegions.clear();
179 }
180
181 /**
182 * @see org.eclipse.ui.IEditorPart#isDirty()
183 */
184 public boolean isDirty() {
185 return fDirty;
186 }
187
188 /**
189 * @see org.eclipse.ui.IEditorPart#isSaveAsAllowed()
190 */
191 public boolean isSaveAsAllowed() {
192 return false;
193 }
194
195 /**
196 * @see org.eclipse.ui.IWorkbenchPart#createPartControl(Composite)
197 */
198 public void createPartControl(Composite parent) {
199 fCanvas =
200 new ScrolledCanvas(
201 parent,
202 SWT.H_SCROLL | SWT.V_SCROLL );
203 fPopup = createPopup();
204 fCanvas.setMenu(fPopup);
205 fCanvas.setBackground(ColorFactory.getDefault().create(0xffffff));
206 fPopup.addMenuListener(new MenuAdapter() {
207 public void menuShown(MenuEvent e) {
208 showPopup(e);
209 }
210 });
211 //
212 BufferedImage image = new GraphPanel(fGraph, 1.0).getImage();
213 int w = image.getWidth();
214 int h = image.getHeight();
215 ImageData data = new ImageData(w, h, 24, new PaletteData(0xff0000, 0xff00, 0xff));
216 int[] pixels = new int[w];
217 for (int y = 0; y < h; ++y) {
218 image.getRGB(0, y, w, 1, pixels, 0, w);
219 data.setPixels(0, y, w, pixels, 0);
220 }
221 //data=ImageFactory.deriveIndexed(data);
222 fImage = new Image(parent.getDisplay(), data);
223 //
224 fCanvas.setImage(fImage);
225 //
226 fCanvas.addKeyListener(this);
227 fCanvas.addMouseListener(new MouseAdapter() {
228 public void mouseDown(MouseEvent e) {
229 if (e.button == 2) {
230 Point p = getCanvas().getViewOffset();
231 getCanvas().centerAt(e.x - p.x, e.y - p.y);
232 }
233 }
234 public void mouseDoubleClick(MouseEvent e) {
235 Point p = getCanvas().getViewOffset();
236 getCanvas().centerAt(e.x - p.x, e.y - p.y);
237 }
238 });
239 //
240 parent.addDisposeListener(new DisposeListener() {
241 public void widgetDisposed(DisposeEvent e) {
242 Image image=getImage();
243 if (image != null && !image.isDisposed())
244 image.dispose();
245 }
246 });
247 //
248 WorkbenchWindow window = (WorkbenchWindow) getSite().getWorkbenchWindow();
249 fStatusLine=new StatusLine(window.getActionBars().getStatusLineManager());
250 }
251
252 /**
253 * @see org.eclipse.ui.IWorkbenchPart#setFocus()
254 */
255 public void setFocus() {
256 }
257
258 /**
259 * @see org.eclipse.ui.IEditorPart#isSaveOnCloseNeeded()
260 */
261 public boolean isSaveOnCloseNeeded() {
262 return true;
263 }
264
265 ////////////////////////////////////////////////////////////////////////
266
267 public IStructuredContentProvider getContentProvider() {
268 return fContentProvider;
269 }
270
271 public ILabelProvider getLabelProvider() {
272 return fLabelProvider;
273 }
274
275 public Comparator getLabelComparator() {
276 return fLabelComparator;
277 }
278
279 public Control getControl() {
280 return fCanvas;
281 }
282
283 public void refresh() {
284 fCanvas.redraw();
285 }
286
287 ScrolledCanvas getCanvas() {
288 return fCanvas;
289 }
290
291 Image getImage() {
292 return fImage;
293 }
294
295 // ISelectionProvider interface ////////////////////////////////////////
296 //
297
298 public void setSelection(ISelection selection) {
299 if (selection == null || !(selection instanceof IStructuredSelection))
300 return;
301 setSelection((IStructuredSelection) selection, false);
302 }
303
304 public void setSelection(IStructuredSelection selection, boolean reveal) {
305 deHighlightSelection();
306 fSelection.clear();
307 for (Iterator it = selection.iterator(); it.hasNext();) {
308 Object a = it.next();
309 if (a instanceof IVertex) {
310 fSelection.add(a);
311 }
312 }
313 for (int i = 0; i < fSelectionListeners.size(); ++i) {
314 ISelectionChangedListener listener = (ISelectionChangedListener) fSelectionListeners.get(i);
315 listener.selectionChanged(new SelectionChangedEvent(this, new StructuredSelection(fSelection)));
316 }
317 highlightSelection();
318 if (reveal) {
319 IVertex a = (IVertex) fSelection.get(fSelection.size() - 1);
320 java.awt.geom.Rectangle2D bounds = (java.awt.geom.Rectangle2D) a.getAttr("-bounds");
321 fCanvas.centerAt(
322 (int) (bounds.getX() + bounds.getWidth() / 2),
323 (int) (bounds.getY() + bounds.getHeight() / 2));
324 } else
325 fCanvas.redraw();
326 }
327
328 public ISelection getSelection() {
329 return new StructuredSelection(fSelection);
330 }
331
332 public void addSelectionChangedListener(ISelectionChangedListener listener) {
333 fSelectionListeners.add(listener);
334 }
335
336 public void removeSelectionChangedListener(ISelectionChangedListener listener) {
337 fSelectionListeners.remove(listener);
338 }
339
340 private void deHighlightSelection() {
341 if (fSelectedRegions.size() == 0)
342 return;
343 GC gc = new GC(fImage);
344 ImageRegionInfo info;
345 for (int i = 0; i < fSelectedRegions.size(); ++i) {
346 info = (ImageRegionInfo) fSelectedRegions.get(i);
347 gc.drawImage(info.image, info.x, info.y);
348 info.image.dispose();
349 }
350 gc.dispose();
351 fSelectedRegions.clear();
352 }
353
354 private void highlightSelection() {
355 if (fSelection.size() == 0)
356 return;
357 DirtyRectangle dirtyrect = new DirtyRectangle(0, 0, 0, 0);
358 IVertex v;
359 java.awt.geom.Rectangle2D bounds;
360 ImageRegionInfo info;
361 for (int i = 0; i < fSelection.size(); ++i) {
362 v = (IVertex) fSelection.get(i);
363 bounds = (java.awt.geom.Rectangle2D) v.getAttr("-bounds");
364 info = new ImageRegionInfo(bounds);
365 fSelectedRegions.add(info);
366 dirtyrect.add(0, 0, info.width, info.height);
367 }
368 Image fog =
369 ImageFactory.getDefault().createFog(
370 ALPHA_HIGHLIGHT,
371 COLOR_HIGHLIGHT,
372 dirtyrect.width,
373 dirtyrect.height,
374 null);
375 GC gc = new GC(fImage);
376 for (int i = 0; i < fSelectedRegions.size(); ++i) {
377 info = (ImageRegionInfo) fSelectedRegions.get(i);
378 gc.copyArea(info.image, info.x, info.y);
379 gc.drawImage(fog, 0, 0, info.width, info.height, info.x, info.y, info.width, info.height);
380 }
381 fog.dispose();
382 gc.dispose();
383 }
384
385 ////////////////////////////////////////////////////////////////////////
386
387 /**
388 * @see org.eclipse.swt.events.KeyListener#keyPressed(KeyEvent)
389 */
390 public void keyPressed(KeyEvent e) {
391 if (DEBUG)
392 Msg.println(
393 NAME
394 + ".keyPressed(): character="
395 + e.character
396 + ", keycode="
397 + e.keyCode
398 + ", modifier="
399 + e.stateMask);
400 if (e.character == 's' || e.character == 'r') {
401 fBeginFindSession = true;
402 fForwardFind = (e.character == 's');
403 }
404 }
405
406 /**
407 * @see org.eclipse.swt.events.KeyListener#keyReleased(KeyEvent)
408 */
409 public void keyReleased(KeyEvent e) {
410 if (fBeginFindSession) {
411 fBeginFindSession = false;
412 beginIncrementalFind(fForwardFind);
413 }
414 }
415
416 ////////////////////////////////////////////////////////////////////////
417
418 private IGraph loadGraph(IEditorInput input) {
419 IGraph ret = null;
420 String filepath = null;
421 if (input instanceof GraphEditorInput) {
422 ret = (IGraph) input.getAdapter(IGraph.class);
423 if (ret != null)
424 return ret;
425 filepath = ((GraphEditorInput) input).getFilepath();
426 } else if (input instanceof FileEditorInput)
427 filepath = ((FileEditorInput) input).getFile().getLocation().toString();
428 if (filepath == null)
429 return null;
430 InputStream istream = null;
431 try {
432 istream = new FileInputStream(filepath);
433 } catch (FileNotFoundException e) {
434 Msg.err("filepath=" + filepath, e);
435 return null;
436 }
437 DotParser parser = new DotParser(istream, filepath);
438 ret = parser.parse();
439 try {
440 istream.close();
441 } catch (IOException e) {
442 }
443 //
444 if (ret.getAttr("bb") == null) {
445 Dot.dot(ret, SEED, PASSES);
446 }
447 return ret;
448 }
449
450 private Menu createPopup() {
451 Menu ret = new Menu(fCanvas);
452 MenuItem item = new MenuItem(ret, SWT.PUSH);
453 item.setText("GraphViewer Menu");
454 item.setEnabled(false);
455 new MenuItem(ret, SWT.SEPARATOR);
456 item = new MenuItem(ret, SWT.PUSH);
457 item.setText("&Incremental Find");
458 item.addSelectionListener(new SelectionAdapter() {
459 public void widgetSelected(SelectionEvent e) {
460 beginIncrementalFind(true);
461 }
462 });
463 return ret;
464 }
465
466 void showPopup(MenuEvent e) {
467 }
468
469 void beginIncrementalFind(boolean forward) {
470 GraphViewIncrementalFinder finder =
471 new GraphViewIncrementalFinder(this, fStatusLine, this);
472 String defaultString = null;
473 // if (fSelection.size() > 0) {
474 // IVertex selected = (IVertex) fSelection.get(0);
475 // defaultString = fLabelProvider.getText(selected);
476 // }
477 finder.beginSession(forward, defaultString);
478 }
479
480 ////////////////////////////////////////////////////////////////////////
481
482 static class GraphContentProvider implements IStructuredContentProvider {
483 IGraph fGraph;
484 public GraphContentProvider(IGraph graph) {
485 fGraph = graph;
486 }
487 public Object[] getElements(Object inputElement) {
488 if (fGraph == null)
489 return null;
490 Set vts = fGraph.allVertices();
491 return vts.toArray(new IVertex[vts.size()]);
492 }
493 public void dispose() {
494 }
495 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
496 }
497 }
498
499 static class GraphLabelProvider implements ILabelProvider {
500 private Map fLabelTable = new HashMap();
501 public GraphLabelProvider(IGraph graph) {
502 Set vts = graph.allVertices();
503 for (Iterator it = vts.iterator(); it.hasNext();) {
504 IVertex v = (IVertex) it.next();
505 fLabelTable.put(v, internalGetText(v));
506 }
507 }
508 public Image getImage(Object element) {
509 return null;
510 }
511 public String getText(Object element) {
512 return (String) fLabelTable.get(element);
513 }
514 public void addListener(ILabelProviderListener listener) {
515 }
516 public void dispose() {
517 }
518 public boolean isLabelProperty(Object element, String property) {
519 return false;
520 }
521 public void removeListener(ILabelProviderListener listener) {
522 }
523 private String internalGetText(Object element) {
524 if (!(element instanceof IVertex))
525 return null;
526 String ret = ((IVertex) element).getAttrString("label");
527 if (ret != null) {
528 return stripTags(ret);
529 }
530 return ((IVertex) element).getName();
531 }
532 /** Strip HTML like tags. */
533 private String stripTags(String s) {
534 char c;
535 StringBuffer ret = new StringBuffer();
536 for (int i = 0; i < s.length(); ++i) {
537 c = s.charAt(i);
538 if (c == '<')
539 i = skipTag(s, i + 1);
540 else
541 ret.append(c);
542 }
543 return ret.toString();
544 }
545 /** Increment index to the next '>' */
546 private int skipTag(String s, int index) {
547 int len = s.length();
548 while (index < len && s.charAt(index) != '>')
549 ++index;
550 return index;
551 }
552 }
553
554 class GraphLabelComparator implements Comparator {
555 public int compare(Object a, Object b) {
556 String name1 = getLabelProvider().getText((IVertex) a);
557 String name2 = getLabelProvider().getText((IVertex) b);
558 if (name1 == null && name2 != null)
559 return -1;
560 if (name1 == null && name2 == null)
561 return 0;
562 if (name1 != null && name2 == null)
563 return 1;
564 return name1.compareTo(name2);
565 }
566 }
567
568 class StatusLine implements IStatusLine {
569 IStatusLineManager fManager;
570 public StatusLine(IStatusLineManager manager) {
571 fManager=manager;
572 }
573 public void setMessage(String message) {
574 fManager.setMessage(message);
575 }
576 public void setErrorMessage(String message) {
577 fManager.setErrorMessage(message);
578 }
579 }
580
581 ////////////////////////////////////////////////////////////////////////
582
583 }