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

Last change on this file since 5923 was 5923, checked in by Don-vip, 11 years ago

fix #8655 - Removed unecessary toString() calls on Strings (patch by Skyman)

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