]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.scl.ui/src/org/simantics/scl/ui/browser/SCLDocumentationBrowser.java
Externalize strings in org.simantics.scl.ui
[simantics/platform.git] / bundles / org.simantics.scl.ui / src / org / simantics / scl / ui / browser / SCLDocumentationBrowser.java
1 package org.simantics.scl.ui.browser;
2
3 import gnu.trove.map.hash.THashMap;
4
5 import java.io.IOException;
6 import java.nio.file.Paths;
7 import java.util.ArrayList;
8
9 import org.eclipse.core.runtime.Status;
10 import org.eclipse.jface.dialogs.ErrorDialog;
11 import org.eclipse.jface.layout.GridDataFactory;
12 import org.eclipse.jface.layout.GridLayoutFactory;
13 import org.eclipse.swt.SWT;
14 import org.eclipse.swt.browser.Browser;
15 import org.eclipse.swt.browser.LocationAdapter;
16 import org.eclipse.swt.browser.LocationEvent;
17 import org.eclipse.swt.browser.ProgressAdapter;
18 import org.eclipse.swt.browser.ProgressEvent;
19 import org.eclipse.swt.custom.SashForm;
20 import org.eclipse.swt.events.KeyAdapter;
21 import org.eclipse.swt.events.KeyEvent;
22 import org.eclipse.swt.events.KeyListener;
23 import org.eclipse.swt.events.MouseEvent;
24 import org.eclipse.swt.events.MouseWheelListener;
25 import org.eclipse.swt.events.SelectionAdapter;
26 import org.eclipse.swt.events.SelectionEvent;
27 import org.eclipse.swt.events.TraverseEvent;
28 import org.eclipse.swt.events.TraverseListener;
29 import org.eclipse.swt.graphics.Color;
30 import org.eclipse.swt.layout.FillLayout;
31 import org.eclipse.swt.widgets.Button;
32 import org.eclipse.swt.widgets.Composite;
33 import org.eclipse.swt.widgets.DirectoryDialog;
34 import org.eclipse.swt.widgets.Event;
35 import org.eclipse.swt.widgets.Listener;
36 import org.eclipse.swt.widgets.Text;
37 import org.eclipse.swt.widgets.Tree;
38 import org.eclipse.swt.widgets.TreeItem;
39 import org.simantics.scl.compiler.elaboration.modules.SCLValue;
40 import org.simantics.scl.compiler.markdown.html.GenerateAllHtmlDocumentation;
41 import org.simantics.scl.compiler.markdown.html.HierarchicalDocumentationRef;
42 import org.simantics.scl.compiler.markdown.html.HtmlDocumentationGeneration;
43 import org.simantics.scl.compiler.markdown.internal.HtmlEscape;
44 import org.simantics.scl.osgi.SCLOsgi;
45 import org.simantics.scl.ui.Activator;
46
47 public class SCLDocumentationBrowser {
48     
49     public static final String STANDARD_LIBRARY = "StandardLibrary"; //$NON-NLS-1$
50
51     Browser browser;
52     Button backButton;
53     Text pageName;
54     Button refreshButton;
55     Button forwardButton;
56     String currentPageName = ""; //$NON-NLS-1$
57     Button saveButton;
58     Button findButton;
59     Tree navigationTree;
60     
61     ArrayList<String> locationHistory = new ArrayList<String>();
62     int locationHistoryPosition = -1;
63     
64     ArrayList<Runnable> runWhenCompleted = new ArrayList<Runnable>(2);
65     Object runWhenCompletedLock = new Object();
66     
67     private void executeWhenCompleted(final String script) {
68         synchronized (runWhenCompletedLock) {
69             runWhenCompleted.add(new Runnable() {
70                 @Override
71                 public void run() {
72                     browser.execute(script);
73                 }
74             });
75         }
76     }
77     
78     private void newLocation(String location) {
79         if(locationHistoryPosition < 0 || 
80                 !location.equals(locationHistory.get(locationHistoryPosition))) {
81             ++locationHistoryPosition;
82             while(locationHistory.size() > locationHistoryPosition)
83                 locationHistory.remove(locationHistory.size()-1);
84             locationHistory.add(location);
85             updateButtons();
86         }
87     }
88     
89     private void back() {
90         if(locationHistoryPosition > 0) {
91             browser.setUrl(locationHistory.get(--locationHistoryPosition));
92             updateButtons();
93         }
94     }
95     
96     private void refresh() {
97         SCLOsgi.SOURCE_REPOSITORY.checkUpdates();
98         final Object yOffset = browser.evaluate("return window.pageYOffset !== undefined ? window.pageYOffset : ((document.compatMode || \"\") === \"CSS1Compat\") ? document.documentElement.scrollTop : document.body.scrollTop;"); //$NON-NLS-1$
99         if(yOffset != null)
100             executeWhenCompleted("window.scroll(0,"+yOffset+");"); //$NON-NLS-1$ //$NON-NLS-2$
101         browser.setUrl(locationHistory.get(locationHistoryPosition));
102         updateNavigationTree();
103     }
104     
105     private void forward() {
106         if(locationHistoryPosition < locationHistory.size()-1) {
107             browser.setUrl(locationHistory.get(++locationHistoryPosition));
108             updateButtons();
109         }
110     }
111     
112     private void updateButtons() {
113         backButton.setEnabled(locationHistoryPosition > 0);
114         forwardButton.setEnabled(locationHistoryPosition < locationHistory.size()-1);
115     }
116     
117     private void setCurrentLocation(String location) {
118         pageName.setText(location);
119         currentPageName = location;
120     }
121     
122     public SCLDocumentationBrowser(Composite parent) {
123         Color white = parent.getDisplay().getSystemColor(SWT.COLOR_WHITE);
124         
125         final Composite composite = new Composite(parent, SWT.NONE);
126         GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(composite);
127         composite.setBackground(white);
128         
129         Composite buttons = new Composite(composite, SWT.NONE);
130         buttons.setBackground(white);
131         GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(buttons);
132         GridLayoutFactory.fillDefaults().numColumns(6).margins(3, 3).applyTo(buttons);
133         
134         backButton = new Button(buttons, SWT.PUSH);
135         buttons.setBackground(composite.getDisplay().getSystemColor(SWT.COLOR_GRAY));
136         backButton.setToolTipText(Messages.SCLDocumentationBrowser_BackTT);
137         backButton.setEnabled(false);
138         backButton.setImage(Activator.getInstance().getImageRegistry().get("arrow_left")); //$NON-NLS-1$
139         
140         forwardButton = new Button(buttons, SWT.PUSH);
141         forwardButton.setToolTipText(Messages.SCLDocumentationBrowser_ForwardTT);
142         forwardButton.setEnabled(false);
143         forwardButton.setImage(Activator.getInstance().getImageRegistry().get("arrow_right")); //$NON-NLS-1$
144         
145         refreshButton = new Button(buttons, SWT.PUSH);
146         refreshButton.setToolTipText(Messages.SCLDocumentationBrowser_RefreshPageTT);
147         refreshButton.setImage(Activator.getInstance().getImageRegistry().get("arrow_refresh")); //$NON-NLS-1$
148         
149         pageName = new Text(buttons, SWT.BORDER | SWT.SINGLE);
150         GridDataFactory.fillDefaults().grab(true, false).align(SWT.FILL, SWT.CENTER).applyTo(pageName);
151
152         findButton = new Button(buttons, SWT.PUSH);
153         findButton.setToolTipText(Messages.SCLDocumentationBrowser_FindSCLDefinitionTT);
154         findButton.setImage(Activator.getInstance().getImageRegistry().get("find")); //$NON-NLS-1$
155
156         saveButton = new Button(buttons, SWT.PUSH);
157         saveButton.setToolTipText(Messages.SCLDocumentationBrowser_SaveDocumentationToDiskTT);
158         saveButton.setImage(Activator.getInstance().getImageRegistry().get("disk")); //$NON-NLS-1$
159         
160         SashForm browserBox = new SashForm(composite, SWT.BORDER | SWT.HORIZONTAL);
161         GridDataFactory.fillDefaults().grab(true, true).align(SWT.FILL, SWT.FILL).applyTo(browserBox);
162         browserBox.setLayout(new FillLayout());
163         
164         navigationTree = new Tree(browserBox, SWT.SINGLE);
165         updateNavigationTree();
166         navigationTree.addSelectionListener(new SelectionAdapter() {
167            @Override
168             public void widgetSelected(SelectionEvent e) {
169                TreeItem[] items = navigationTree.getSelection();
170                if(items.length == 1) {
171                    String documentationName = (String)items[0].getData();
172                    if(documentationName != null)
173                        setLocation(documentationName);
174                }
175             } 
176         });
177         
178         browser = new Browser(browserBox, SWT.BORDER);
179         browserBox.setWeights(new int[] {15, 85});
180         browser.addProgressListener(new ProgressAdapter() {
181             @Override
182             public void completed(ProgressEvent event) {
183                 ArrayList<Runnable> rs;
184                 synchronized(runWhenCompletedLock) {
185                     rs = runWhenCompleted;
186                     runWhenCompleted = new ArrayList<Runnable>(2);
187                 }
188                 for(Runnable r : rs)
189                     r.run();
190             }
191         });
192         browser.addLocationListener(new LocationAdapter() {
193             public void changing(LocationEvent event) {
194                 String location = event.location;
195                 if(location.startsWith("about:blank")) //$NON-NLS-1$
196                     return;
197                 newLocation(location);
198                 if(location.startsWith("about:")) { //$NON-NLS-1$
199                     location = location.substring(6);
200                     setCurrentLocation(location);
201                     int hashPos = location.indexOf('#');
202                     final String fragment;
203                     if(hashPos >= 0) {
204                         fragment = location.substring(hashPos);
205                         location = location.substring(0, hashPos);
206                     }
207                     else
208                         fragment = null;
209                     if(location.endsWith(".html")) //$NON-NLS-1$
210                         location = location.substring(0, location.length()-5);
211                     
212                     String html = HtmlDocumentationGeneration.generate(SCLOsgi.MODULE_REPOSITORY, location, null);
213                     
214                     browser.setText(html);
215                     if(fragment != null)
216                         executeWhenCompleted("location.hash = \"" + fragment + "\";"); //$NON-NLS-1$ //$NON-NLS-2$
217                     event.doit = false;
218                 }
219                 else
220                     setCurrentLocation(location);
221             }
222         });
223         
224         KeyListener keyListener = new KeyAdapter() {
225             @Override
226             public void keyPressed(KeyEvent e) {
227                 if((e.stateMask & SWT.ALT) != 0) {
228                     if(e.keyCode == SWT.ARROW_LEFT) {
229                         back();
230                         return;
231                     }
232                     else if(e.keyCode == SWT.ARROW_RIGHT) {
233                         forward();
234                         return;
235                     }
236                 }
237                 else if((e.stateMask & SWT.CTRL) != 0) {
238                     if(e.keyCode == 'r' || e.keyCode == 'R') {
239                         refresh();
240                         return;
241                     }
242                     if(e.keyCode == 'l' || e.keyCode == 'L') {
243                         pageName.selectAll();
244                         pageName.setFocus();
245                         return;
246                     }
247                     if(e.keyCode == 'k' || e.keyCode == 'K') {
248                         pageName.setText("?"); //$NON-NLS-1$
249                         pageName.setSelection(1);
250                         pageName.setFocus();
251                         return;
252                     }
253                     if(e.keyCode == 'h' || e.keyCode == 'H') {
254                         find();
255                         return;
256                     }
257                 }
258                 if(e.keyCode == SWT.PAGE_DOWN) {
259                     browser.execute("window.scrollBy(0,0.8*(window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight));"); //$NON-NLS-1$
260                 }
261                 else if(e.keyCode == SWT.PAGE_UP) {
262                     browser.execute("window.scrollBy(0,-0.8*(window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight));"); //$NON-NLS-1$
263                 }
264                 else if(e.keyCode == SWT.ARROW_DOWN) {
265                     browser.execute("window.scrollBy(0,100);"); //$NON-NLS-1$
266                 }
267                 else if(e.keyCode == SWT.ARROW_UP) {
268                     browser.execute("window.scrollBy(0,-100);"); //$NON-NLS-1$
269                 }
270                 else if(e.keyCode == SWT.HOME) {
271                     if(e.widget != pageName)
272                         browser.execute("window.scroll(0,0);"); //$NON-NLS-1$
273                 }
274                 else if(e.keyCode == SWT.END) {
275                     if(e.widget != pageName)
276                         browser.execute("window.scroll(0,document.body.scrollHeight);"); //$NON-NLS-1$
277                 }
278             }
279             
280         };
281         composite.addKeyListener(keyListener);
282         browser.addKeyListener(keyListener);
283         pageName.addKeyListener(keyListener);
284         
285         MouseWheelListener wheelListener = new MouseWheelListener() { 
286             @Override
287             public void mouseScrolled(MouseEvent e) {
288                 browser.execute("window.scrollBy(0,"+e.count*(-30)+");"); //$NON-NLS-1$ //$NON-NLS-2$
289             }
290         };
291         composite.addMouseWheelListener(wheelListener);
292         browser.addMouseWheelListener(wheelListener);     
293         backButton.addSelectionListener(new SelectionAdapter() {
294             @Override
295             public void widgetSelected(SelectionEvent e) {
296                 back();
297             }
298         });
299         forwardButton.addSelectionListener(new SelectionAdapter() {
300             @Override
301             public void widgetSelected(SelectionEvent e) {
302                 forward();
303             }
304         });
305         refreshButton.addListener(SWT.Selection, new Listener() {
306             @Override
307             public void handleEvent(Event event) {
308                 refresh();
309             }
310         });
311         pageName.addTraverseListener(new TraverseListener() {
312             @Override
313             public void keyTraversed(TraverseEvent e) {
314                 if(e.detail == SWT.TRAVERSE_RETURN)
315                     setLocation(pageName.getText());
316                 else if(e.detail == SWT.TRAVERSE_ESCAPE)
317                     pageName.setText(currentPageName);
318             }
319         });
320         findButton.addSelectionListener(new SelectionAdapter() {
321             @Override
322             public void widgetSelected(SelectionEvent e) {
323                 find();
324             }
325         });
326         saveButton.addSelectionListener(new SelectionAdapter() {
327             @Override
328             public void widgetSelected(SelectionEvent e) {
329                 DirectoryDialog dialog = new DirectoryDialog(saveButton.getShell());
330                 dialog.setText(Messages.SCLDocumentationBrowser_SaveDocumentationDialog_Title);
331                 dialog.setMessage(Messages.SCLDocumentationBrowser_SaveDocumentationDialog_Msg);
332                 String directory = dialog.open();
333                 if(directory != null) {
334                     try {
335                         GenerateAllHtmlDocumentation.generate(SCLOsgi.MODULE_REPOSITORY, Paths.get(directory));
336                     } catch (IOException ex) {
337                         ex.printStackTrace();
338                         ErrorDialog.openError(saveButton.getShell(), "Documentation generation failed", null, //$NON-NLS-1$
339                                 new Status(Status.ERROR, "org.simantics.scl.ui", 0, ex.toString(), ex)); //$NON-NLS-1$
340                     }
341                 }
342             }
343         });
344     }
345     
346     private void find() {
347         SCLDefinitionSelectionDialog dialog = new SCLDefinitionSelectionDialog(findButton.getShell());
348         if(dialog.open() == SCLDefinitionSelectionDialog.OK) {
349             SCLValue value = (SCLValue)dialog.getFirstResult();
350             if(value != null) {
351                 setLocation(value.getName().module + "#" + HtmlEscape.escape(value.getName().name)); //$NON-NLS-1$
352             }
353         }
354     }
355     
356     private static class ExpStatus {
357         THashMap<String, ExpStatus> expandedItems = new THashMap<String, ExpStatus>();
358     }
359     
360     private static ExpStatus getExpStatus(Tree tree) {
361         ExpStatus status = new ExpStatus();
362         for(TreeItem child : tree.getItems()) {
363             if(child.getExpanded())
364                 status.expandedItems.put(child.getText(), getExpStatus(child));
365         }
366         return status;
367     }
368     
369     private static ExpStatus getExpStatus(TreeItem item) {
370         ExpStatus status = new ExpStatus();
371         for(TreeItem child : item.getItems()) {
372             if(child.getExpanded())
373                 status.expandedItems.put(child.getText(), getExpStatus(child));
374         }
375         return status;
376     }
377     
378     private static void setExpStatus(Tree tree, ExpStatus status) {
379         for(TreeItem child : tree.getItems()) {
380             ExpStatus childStatus = status.expandedItems.get(child.getText());
381             if(childStatus != null) {
382                 child.setExpanded(true);
383                 setExpStatus(child, childStatus);
384             }
385         }
386     }
387     
388     private static void setExpStatus(TreeItem item, ExpStatus status) {
389         for(TreeItem child : item.getItems()) {
390             ExpStatus childStatus = status.expandedItems.get(child.getText());
391             child.setExpanded(true);
392             if (childStatus != null) {
393                 setExpStatus(child, childStatus);
394             }
395         }
396     }
397     
398     private void updateNavigationTree() {
399         HierarchicalDocumentationRef root = HierarchicalDocumentationRef.generateTree(SCLOsgi.SOURCE_REPOSITORY);
400
401         ExpStatus status = getExpStatus(navigationTree);
402         navigationTree.removeAll();
403         for(HierarchicalDocumentationRef navItem : root.getChildren()) {
404             TreeItem item = new TreeItem(navigationTree, SWT.NONE);
405             configureTreeItem(navItem, item);
406         }
407         setExpStatus(navigationTree, status);
408     }
409     
410     private void configureTreeItem(HierarchicalDocumentationRef navItem, TreeItem item) {
411         item.setText(navItem.getName());
412         item.setData(navItem.getDocumentationName());
413         for(HierarchicalDocumentationRef childNavItem : navItem.getChildren()) {
414             TreeItem childItem = new TreeItem(item, SWT.NONE);
415             configureTreeItem(childNavItem, childItem);
416         }
417     }
418
419     public void setLocation(String path) {
420         browser.setUrl("about:" + path); //$NON-NLS-1$
421     }
422 }