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

Last change on this file since 2626 was 2626, checked in by jttt, 14 years ago

Fixed some of the warnings found by FindBugs

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