source: josm/trunk/src/org/openstreetmap/josm/gui/help/HelpBrowser.java@ 4175

Last change on this file since 4175 was 3975, checked in by bastiK, 13 years ago

fixed #6092, ref #6093 - spelling

  • Property svn:eol-style set to native
File size: 21.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.help;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.buildAbsoluteHelpTopic;
5import static org.openstreetmap.josm.gui.help.HelpUtil.getHelpTopicEditUrl;
6import static org.openstreetmap.josm.tools.I18n.tr;
7
8import java.awt.BorderLayout;
9import java.awt.Dimension;
10import java.awt.Rectangle;
11import java.awt.event.ActionEvent;
12import java.awt.event.KeyEvent;
13import java.awt.event.WindowAdapter;
14import java.awt.event.WindowEvent;
15import java.io.BufferedReader;
16import java.io.InputStreamReader;
17import java.io.StringReader;
18import java.util.Locale;
19import java.util.Observable;
20import java.util.Observer;
21import java.util.logging.Logger;
22
23import javax.swing.AbstractAction;
24import javax.swing.JButton;
25import javax.swing.JComponent;
26import javax.swing.JDialog;
27import javax.swing.JEditorPane;
28import javax.swing.JOptionPane;
29import javax.swing.JPanel;
30import javax.swing.JScrollPane;
31import javax.swing.JSeparator;
32import javax.swing.JToolBar;
33import javax.swing.KeyStroke;
34import javax.swing.SwingUtilities;
35import javax.swing.event.HyperlinkEvent;
36import javax.swing.event.HyperlinkListener;
37import javax.swing.text.AttributeSet;
38import javax.swing.text.BadLocationException;
39import javax.swing.text.Document;
40import javax.swing.text.Element;
41import javax.swing.text.SimpleAttributeSet;
42import javax.swing.text.html.HTMLDocument;
43import javax.swing.text.html.HTMLEditorKit;
44import javax.swing.text.html.StyleSheet;
45import javax.swing.text.html.HTML.Tag;
46
47import org.openstreetmap.josm.Main;
48import org.openstreetmap.josm.gui.HelpAwareOptionPane;
49import org.openstreetmap.josm.tools.ImageProvider;
50import org.openstreetmap.josm.tools.OpenBrowser;
51import org.openstreetmap.josm.tools.WindowGeometry;
52
53public class HelpBrowser extends JDialog {
54 static private final Logger logger = Logger.getLogger(HelpBrowser.class.getName());
55
56 /** the unique instance */
57 private static HelpBrowser instance;
58
59 /**
60 * Replies the unique instance of the help browser
61 *
62 * @return the unique instance of the help browser
63 */
64 static public HelpBrowser getInstance() {
65 if (instance == null) {
66 instance = new HelpBrowser();
67 }
68 return instance;
69 }
70
71 /**
72 * Show the help page for help topic <code>helpTopic</code>.
73 *
74 * @param helpTopic the help topic
75 */
76 public static void setUrlForHelpTopic(final String helpTopic) {
77 final HelpBrowser browser = getInstance();
78 Runnable r = new Runnable() {
79 public void run() {
80 browser.openHelpTopic(helpTopic);
81 browser.setVisible(true);
82 browser.toFront();
83 }
84 };
85 SwingUtilities.invokeLater(r);
86 }
87
88 /**
89 * Launches the internal help browser and directs it to the help page for
90 * <code>helpTopic</code>.
91 *
92 * @param helpTopic the help topic
93 */
94 static public void launchBrowser(String helpTopic) {
95 HelpBrowser browser = getInstance();
96 browser.openHelpTopic(helpTopic);
97 browser.setVisible(true);
98 browser.toFront();
99 }
100
101 /** the help browser */
102 private JEditorPane help;
103
104 /** the help browser history */
105 private HelpBrowserHistory history;
106
107 /** the currently displayed URL */
108 private String url;
109
110 private HelpContentReader reader;
111
112 /**
113 * Builds the style sheet used in the internal help browser
114 *
115 * @return the style sheet
116 */
117 protected StyleSheet buildStyleSheet() {
118 StyleSheet ss = new StyleSheet();
119 BufferedReader reader = new BufferedReader(
120 new InputStreamReader(
121 getClass().getResourceAsStream("/data/help-browser.css")
122 )
123 );
124 StringBuffer css = new StringBuffer();
125 try {
126 String line = null;
127 while ((line = reader.readLine()) != null) {
128 css.append(line);
129 css.append("\n");
130 }
131 reader.close();
132 } catch(Exception e) {
133 System.err.println(tr("Failed to read CSS file ''help-browser.css''. Exception is: {0}", e.toString()));
134 e.printStackTrace();
135 return ss;
136 }
137 ss.addRule(css.toString());
138 return ss;
139 }
140
141 protected JToolBar buildToolBar() {
142 JToolBar tb = new JToolBar();
143 tb.add(new JButton(new HomeAction()));
144 tb.add(new JButton(new BackAction(history)));
145 tb.add(new JButton(new ForwardAction(history)));
146 tb.add(new JButton(new ReloadAction()));
147 tb.add(new JSeparator());
148 tb.add(new JButton(new OpenInBrowserAction()));
149 tb.add(new JButton(new EditAction()));
150 return tb;
151 }
152
153 protected void build() {
154 help = new JEditorPane();
155 HTMLEditorKit kit = new HTMLEditorKit();
156 kit.setStyleSheet(buildStyleSheet());
157 help.setEditorKit(kit);
158 help.setEditable(false);
159 help.addHyperlinkListener(new HyperlinkHandler());
160 help.setContentType("text/html");
161 history = new HelpBrowserHistory(this);
162
163 JPanel p = new JPanel(new BorderLayout());
164 setContentPane(p);
165
166 p.add(new JScrollPane(help), BorderLayout.CENTER);
167
168 addWindowListener(new WindowAdapter(){
169 @Override public void windowClosing(WindowEvent e) {
170 setVisible(false);
171 }
172 });
173
174 p.add(buildToolBar(), BorderLayout.NORTH);
175 help.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "Close");
176 help.getActionMap().put("Close", new AbstractAction(){
177 public void actionPerformed(ActionEvent e) {
178 setVisible(false);
179 }
180 });
181
182 setMinimumSize(new Dimension(400, 200));
183 setTitle(tr("JOSM Help Browser"));
184 }
185
186 @Override
187 public void setVisible(boolean visible) {
188 if (visible) {
189 new WindowGeometry(
190 getClass().getName() + ".geometry",
191 WindowGeometry.centerInWindow(
192 getParent(),
193 new Dimension(600,400)
194 )
195 ).applySafe(this);
196 } else if (!visible && isShowing()){
197 new WindowGeometry(this).remember(getClass().getName() + ".geometry");
198 }
199 super.setVisible(visible);
200 }
201
202 public HelpBrowser() {
203 reader = new HelpContentReader(HelpUtil.getWikiBaseUrl());
204 build();
205 }
206
207 protected void loadTopic(String content) {
208 Document document = help.getEditorKit().createDefaultDocument();
209 try {
210 help.getEditorKit().read(new StringReader(content), document, 0);
211 } catch (Exception e) {
212 e.printStackTrace();
213 }
214 help.setDocument(document);
215 }
216
217 /**
218 * Replies the current URL
219 *
220 * @return the current URL
221 */
222
223 public String getUrl() {
224 return url;
225 }
226
227 /**
228 * Displays a warning page when a help topic doesn't exist yet.
229 *
230 * @param relativeHelpTopic the help topic
231 */
232 protected void handleMissingHelpContent(String relativeHelpTopic) {
233 // i18n: do not translate "warning-header" and "warning-body"
234 String message = tr("<html><p class=\"warning-header\">Help content for help topic missing</p>"
235 + "<p class=\"warning-body\">Help content for the help topic <strong>{0}</strong> is "
236 + "not available yet. It is missing both in your local language ({1}) and in English.<br><br>"
237 + "Please help to improve the JOSM help system and fill in the missing information. "
238 + "You can both edit the <a href=\"{2}\">help topic in your local language ({1})</a> and "
239 + "the <a href=\"{3}\">help topic in English</a>."
240 + "</p></html>",
241 relativeHelpTopic,
242 Locale.getDefault().getDisplayName(),
243 getHelpTopicEditUrl(buildAbsoluteHelpTopic(relativeHelpTopic)),
244 getHelpTopicEditUrl(buildAbsoluteHelpTopic(relativeHelpTopic, Locale.ENGLISH))
245 );
246 loadTopic(message);
247 }
248
249 /**
250 * Displays a error page if a help topic couldn't be loaded because of network or IO error.
251 *
252 * @param relativeHelpTopic the help topic
253 * @param e the exception
254 */
255 protected void handleHelpContentReaderException(String relativeHelpTopic, HelpContentReaderException e) {
256 String message = tr("<html><p class=\"error-header\">Error when retrieving help information</p>"
257 + "<p class=\"error-body\">The content for the help topic <strong>{0}</strong> could "
258 + "not be loaded. The error message is (untranslated):<br>"
259 + "<tt>{1}</tt>"
260 + "</p></html>",
261 relativeHelpTopic,
262 e.toString()
263 );
264 loadTopic(message);
265 }
266
267 /**
268 * Loads a help topic given by a relative help topic name (i.e. "/Action/New")
269 *
270 * First tries to load the language specific help topic. If it is missing, tries to
271 * load the topic in English.
272 *
273 * @param relativeHelpTopic the relative help topic
274 */
275 protected void loadRelativeHelpTopic(String relativeHelpTopic) {
276 String url = HelpUtil.getHelpTopicUrl(HelpUtil.buildAbsoluteHelpTopic(relativeHelpTopic));
277 String content = null;
278 try {
279 content = reader.fetchHelpTopicContent(url, true);
280 } catch(MissingHelpContentException e) {
281 url = HelpUtil.getHelpTopicUrl(HelpUtil.buildAbsoluteHelpTopic(relativeHelpTopic, Locale.ENGLISH));
282 try {
283 logger.info("fetching url: " + url);
284 content = reader.fetchHelpTopicContent(url, true);
285 } catch(MissingHelpContentException e1) {
286 this.url = url;
287 handleMissingHelpContent(relativeHelpTopic);
288 return;
289 } catch(HelpContentReaderException e1) {
290 e1.printStackTrace();
291 handleHelpContentReaderException(relativeHelpTopic,e1);
292 return;
293 }
294 } catch(HelpContentReaderException e) {
295 e.printStackTrace();
296 handleHelpContentReaderException(relativeHelpTopic, e);
297 return;
298 }
299 loadTopic(content);
300 history.setCurrentUrl(url);
301 this.url = url;
302 }
303
304 /**
305 * Loads a help topic given by an absolute help topic name, i.e.
306 * "/De:Help/Action/New"
307 *
308 * @param absoluteHelpTopic the absolute help topic name
309 */
310 protected void loadAbsoluteHelpTopic(String absoluteHelpTopic) {
311 String url = HelpUtil.getHelpTopicUrl(absoluteHelpTopic);
312 String content = null;
313 try {
314 content = reader.fetchHelpTopicContent(url, true);
315 } catch(MissingHelpContentException e) {
316 this.url = url;
317 handleMissingHelpContent(absoluteHelpTopic);
318 return;
319 } catch(HelpContentReaderException e) {
320 e.printStackTrace();
321 handleHelpContentReaderException(absoluteHelpTopic, e);
322 return;
323 }
324 loadTopic(content);
325 history.setCurrentUrl(url);
326 this.url = url;
327 }
328
329 /**
330 * Opens an URL and displays the content.
331 *
332 * If the URL is the locator of an absolute help topic, help content is loaded from
333 * the JOSM wiki. Otherwise, the help browser loads the page from the given URL
334 *
335 * @param url the url
336 */
337 public void openUrl(String url) {
338 if (!isVisible()) {
339 setVisible(true);
340 toFront();
341 } else {
342 toFront();
343 }
344 String helpTopic = HelpUtil.extractAbsoluteHelpTopic(url);
345 if (helpTopic == null) {
346 try {
347 this.url = url;
348 String content = reader.fetchHelpTopicContent(url, false);
349 loadTopic(content);
350 history.setCurrentUrl(url);
351 this.url = url;
352 } catch(Exception e) {
353 HelpAwareOptionPane.showOptionDialog(
354 Main.parent,
355 tr(
356 "<html>Failed to open help page for url {0}.<br>"
357 + "This is most likely due to a network problem, please check<br>"
358 + "your internet connection</html>",
359 url.toString()
360 ),
361 tr("Failed to open URL"),
362 JOptionPane.ERROR_MESSAGE,
363 null, /* no icon */
364 null, /* standard options, just OK button */
365 null, /* default is standard */
366 null /* no help context */
367 );
368 }
369 history.setCurrentUrl(url);
370 } else {
371 loadAbsoluteHelpTopic(helpTopic);
372 }
373 }
374
375 /**
376 * Loads and displays the help information for a help topic given
377 * by a relative help topic name, i.e. "/Action/New"
378 *
379 * @param relativeHelpTopic the relative help topic
380 */
381 public void openHelpTopic(String relativeHelpTopic) {
382 if (!isVisible()) {
383 setVisible(true);
384 toFront();
385 } else {
386 toFront();
387 }
388 loadRelativeHelpTopic(relativeHelpTopic);
389 }
390
391 class OpenInBrowserAction extends AbstractAction {
392 public OpenInBrowserAction() {
393 //putValue(NAME, tr("Open in Browser"));
394 putValue(SHORT_DESCRIPTION, tr("Open the current help page in an external browser"));
395 putValue(SMALL_ICON, ImageProvider.get("help", "internet"));
396 }
397
398 public void actionPerformed(ActionEvent e) {
399 OpenBrowser.displayUrl(getUrl());
400 }
401 }
402
403 class EditAction extends AbstractAction {
404 public EditAction() {
405 // putValue(NAME, tr("Edit"));
406 putValue(SHORT_DESCRIPTION, tr("Edit the current help page"));
407 putValue(SMALL_ICON,ImageProvider.get("dialogs", "edit"));
408 }
409
410 public void actionPerformed(ActionEvent e) {
411 String url = getUrl();
412 if(url == null)
413 return;
414 if (!url.startsWith(HelpUtil.getWikiBaseHelpUrl())) {
415 String message = tr(
416 "<html>The current URL <tt>{0}</tt><br>"
417 + "is an external URL. Editing is only possible for help topics<br>"
418 + "on the help server <tt>{1}</tt>.</html>",
419 getUrl(),
420 HelpUtil.getWikiBaseUrl()
421 );
422 JOptionPane.showMessageDialog(
423 Main.parent,
424 message,
425 tr("Warning"),
426 JOptionPane.WARNING_MESSAGE
427 );
428 return;
429 }
430 url = url.replaceAll("#[^#]*$", "");
431 OpenBrowser.displayUrl(url+"?action=edit");
432 }
433 }
434
435 class ReloadAction extends AbstractAction {
436 public ReloadAction() {
437 //putValue(NAME, tr("Reload"));
438 putValue(SHORT_DESCRIPTION, tr("Reload the current help page"));
439 putValue(SMALL_ICON, ImageProvider.get("dialogs", "refresh"));
440 }
441
442 public void actionPerformed(ActionEvent e) {
443 openUrl(getUrl());
444 }
445 }
446
447 static class BackAction extends AbstractAction implements Observer {
448 private HelpBrowserHistory history;
449 public BackAction(HelpBrowserHistory history) {
450 this.history = history;
451 history.addObserver(this);
452 //putValue(NAME, tr("Back"));
453 putValue(SHORT_DESCRIPTION, tr("Go to the previous page"));
454 putValue(SMALL_ICON, ImageProvider.get("help", "previous"));
455 setEnabled(history.canGoBack());
456 }
457
458 public void actionPerformed(ActionEvent e) {
459 history.back();
460 }
461 public void update(Observable o, Object arg) {
462 //System.out.println("BackAction: canGoBoack=" + history.canGoBack() );
463 setEnabled(history.canGoBack());
464 }
465 }
466
467 static class ForwardAction extends AbstractAction implements Observer {
468 private HelpBrowserHistory history;
469 public ForwardAction(HelpBrowserHistory history) {
470 this.history = history;
471 history.addObserver(this);
472 //putValue(NAME, tr("Forward"));
473 putValue(SHORT_DESCRIPTION, tr("Go to the next page"));
474 putValue(SMALL_ICON, ImageProvider.get("help", "next"));
475 setEnabled(history.canGoForward());
476 }
477
478 public void actionPerformed(ActionEvent e) {
479 history.forward();
480 }
481 public void update(Observable o, Object arg) {
482 setEnabled(history.canGoForward());
483 }
484 }
485
486 class HomeAction extends AbstractAction {
487 public HomeAction() {
488 //putValue(NAME, tr("Home"));
489 putValue(SHORT_DESCRIPTION, tr("Go to the JOSM help home page"));
490 putValue(SMALL_ICON, ImageProvider.get("help", "home"));
491 }
492
493 public void actionPerformed(ActionEvent e) {
494 openHelpTopic("/");
495 }
496 }
497
498 class HyperlinkHandler implements HyperlinkListener {
499
500 /**
501 * Scrolls the help browser to the element with id <code>id</code>
502 *
503 * @param id the id
504 * @return true, if an element with this id was found and scrolling was successful; false, otherwise
505 */
506 protected boolean scrollToElementWithId(String id) {
507 Document d = help.getDocument();
508 if (d instanceof HTMLDocument) {
509 HTMLDocument doc = (HTMLDocument) d;
510 Element element = doc.getElement(id);
511 try {
512 Rectangle r = help.modelToView(element.getStartOffset());
513 if (r != null) {
514 Rectangle vis = help.getVisibleRect();
515 r.height = vis.height;
516 help.scrollRectToVisible(r);
517 return true;
518 }
519 } catch(BadLocationException e) {
520 System.err.println(tr("Warning: bad location in HTML document. Exception was: {0}", e.toString()));
521 e.printStackTrace();
522 }
523 }
524 return false;
525 }
526
527 /**
528 * Checks whether the hyperlink event originated on a <a ...> element with
529 * a relative href consisting of a URL fragment only, i.e.
530 * <a href="#thisIsALocalFragment">. If so, replies the fragment, i.e.
531 * "thisIsALocalFragment".
532 *
533 * Otherwise, replies null
534 *
535 * @param e the hyperlink event
536 * @return the local fragment
537 */
538 protected String getUrlFragment(HyperlinkEvent e) {
539 AttributeSet set = e.getSourceElement().getAttributes();
540 Object value = set.getAttribute(Tag.A);
541 if (value == null || ! (value instanceof SimpleAttributeSet)) return null;
542 SimpleAttributeSet atts = (SimpleAttributeSet)value;
543 value = atts.getAttribute(javax.swing.text.html.HTML.Attribute.HREF);
544 if (value == null) return null;
545 String s = (String)value;
546 if (s.matches("#.*"))
547 return s.substring(1);
548 return null;
549 }
550
551 public void hyperlinkUpdate(HyperlinkEvent e) {
552 if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED)
553 return;
554 if (e.getURL() == null) {
555 // Probably hyperlink event on a an A-element with a href consisting of
556 // a fragment only, i.e. "#ALocalFragment".
557 //
558 String fragment = getUrlFragment(e);
559 if (fragment != null) {
560 // first try to scroll to an element with id==fragment. This is the way
561 // table of contents are built in the JOSM wiki. If this fails, try to
562 // scroll to a <A name="..."> element.
563 //
564 if (!scrollToElementWithId(fragment)) {
565 help.scrollToReference(fragment);
566 }
567 } else {
568 HelpAwareOptionPane.showOptionDialog(
569 Main.parent,
570 tr("Failed to open help page. The target URL is empty."),
571 tr("Failed to open help page"),
572 JOptionPane.ERROR_MESSAGE,
573 null, /* no icon */
574 null, /* standard options, just OK button */
575 null, /* default is standard */
576 null /* no help context */
577 );
578 }
579 } else if (e.getURL().toString().endsWith("action=edit")) {
580 OpenBrowser.displayUrl(e.getURL().toString());
581 } else {
582 url = e.getURL().toString();
583 openUrl(e.getURL().toString());
584 }
585 }
586 }
587}
Note: See TracBrowser for help on using the repository browser.