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

Last change on this file since 3558 was 3501, checked in by bastiK, 14 years ago

fixed #4632 - Button Help puts help window under main window

  • 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 String message = tr("<html><p class=\"warning-header\">Help content for help topic missing</p>"
234 + "<p class=\"warning-body\">Help content for the help topic <strong>{0}</strong> is "
235 + "not available yet. It is missing both in your local language ({1}) and in english.<br><br>"
236 + "Please help to improve the JOSM help system and fill in the missing information. "
237 + "You can both edit the <a href=\"{2}\">help topic in your local language ({1})</a> and "
238 + "the <a href=\"{3}\">help topic in english</a>."
239 + "</p></html>",
240 relativeHelpTopic,
241 Locale.getDefault().getDisplayName(),
242 getHelpTopicEditUrl(buildAbsoluteHelpTopic(relativeHelpTopic)),
243 getHelpTopicEditUrl(buildAbsoluteHelpTopic(relativeHelpTopic, Locale.ENGLISH))
244 );
245 loadTopic(message);
246 }
247
248 /**
249 * Displays a error page if a help topic couldn't be loaded because of network or IO error.
250 *
251 * @param relativeHelpTopic the help topic
252 * @param e the exception
253 */
254 protected void handleHelpContentReaderException(String relativeHelpTopic, HelpContentReaderException e) {
255 String message = tr("<html><p class=\"error-header\">Error when retrieving help information</p>"
256 + "<p class=\"error-body\">The content for the help topic <strong>{0}</strong> could "
257 + "not be loaded. The error message is (untranslated):<br>"
258 + "<tt>{1}</tt>"
259 + "</p></html>",
260 relativeHelpTopic,
261 e.toString()
262 );
263 loadTopic(message);
264 }
265
266 /**
267 * Loads a help topic given by a relative help topic name (i.e. "/Action/New")
268 *
269 * First tries to load the language specific help topic. If it is missing, tries to
270 * load the topic in english.
271 *
272 * @param relativeHelpTopic the relative help topic
273 */
274 protected void loadRelativeHelpTopic(String relativeHelpTopic) {
275 String url = HelpUtil.getHelpTopicUrl(HelpUtil.buildAbsoluteHelpTopic(relativeHelpTopic));
276 String content = null;
277 try {
278 content = reader.fetchHelpTopicContent(url, true);
279 } catch(MissingHelpContentException e) {
280 url = HelpUtil.getHelpTopicUrl(HelpUtil.buildAbsoluteHelpTopic(relativeHelpTopic, Locale.ENGLISH));
281 try {
282 logger.info("fetching url: " + url);
283 content = reader.fetchHelpTopicContent(url, true);
284 } catch(MissingHelpContentException e1) {
285 this.url = url;
286 handleMissingHelpContent(relativeHelpTopic);
287 return;
288 } catch(HelpContentReaderException e1) {
289 e1.printStackTrace();
290 handleHelpContentReaderException(relativeHelpTopic,e1);
291 return;
292 }
293 } catch(HelpContentReaderException e) {
294 e.printStackTrace();
295 handleHelpContentReaderException(relativeHelpTopic, e);
296 return;
297 }
298 loadTopic(content);
299 history.setCurrentUrl(url);
300 this.url = url;
301 }
302
303 /**
304 * Loads a help topic given by an absolute help topic name, i.e.
305 * "/De:Help/Action/New"
306 *
307 * @param absoluteHelpTopic the absolute help topic name
308 */
309 protected void loadAbsoluteHelpTopic(String absoluteHelpTopic) {
310 String url = HelpUtil.getHelpTopicUrl(absoluteHelpTopic);
311 String content = null;
312 try {
313 content = reader.fetchHelpTopicContent(url, true);
314 } catch(MissingHelpContentException e) {
315 this.url = url;
316 handleMissingHelpContent(absoluteHelpTopic);
317 return;
318 } catch(HelpContentReaderException e) {
319 e.printStackTrace();
320 handleHelpContentReaderException(absoluteHelpTopic, e);
321 return;
322 }
323 loadTopic(content);
324 history.setCurrentUrl(url);
325 this.url = url;
326 }
327
328 /**
329 * Opens an URL and displays the content.
330 *
331 * If the URL is the locator of an absolute help topic, help content is loaded from
332 * the JOSM wiki. Otherwise, the help browser loads the page from the given URL
333 *
334 * @param url the url
335 */
336 public void openUrl(String url) {
337 if (!isVisible()) {
338 setVisible(true);
339 toFront();
340 } else {
341 toFront();
342 }
343 String helpTopic = HelpUtil.extractAbsoluteHelpTopic(url);
344 if (helpTopic == null) {
345 try {
346 this.url = url;
347 String content = reader.fetchHelpTopicContent(url, false);
348 loadTopic(content);
349 history.setCurrentUrl(url);
350 this.url = url;
351 } catch(Exception e) {
352 HelpAwareOptionPane.showOptionDialog(
353 Main.parent,
354 tr(
355 "<html>Failed to open help page for url {0}.<br>"
356 + "This is most likely due to a network problem, please check<br>"
357 + "your internet connection</html>",
358 url.toString()
359 ),
360 tr("Failed to open URL"),
361 JOptionPane.ERROR_MESSAGE,
362 null, /* no icon */
363 null, /* standard options, just OK button */
364 null, /* default is standard */
365 null /* no help context */
366 );
367 }
368 history.setCurrentUrl(url);
369 } else {
370 loadAbsoluteHelpTopic(helpTopic);
371 }
372 }
373
374 /**
375 * Loads and displays the help information for a help topic given
376 * by a relative help topic name, i.e. "/Action/New"
377 *
378 * @param relativeHelpTopic the relative help topic
379 */
380 public void openHelpTopic(String relativeHelpTopic) {
381 if (!isVisible()) {
382 setVisible(true);
383 toFront();
384 } else {
385 toFront();
386 }
387 loadRelativeHelpTopic(relativeHelpTopic);
388 }
389
390 class OpenInBrowserAction extends AbstractAction {
391 public OpenInBrowserAction() {
392 //putValue(NAME, tr("Open in Browser"));
393 putValue(SHORT_DESCRIPTION, tr("Open the current help page in an external browser"));
394 putValue(SMALL_ICON, ImageProvider.get("help", "internet"));
395 }
396
397 public void actionPerformed(ActionEvent e) {
398 OpenBrowser.displayUrl(getUrl());
399 }
400 }
401
402 class EditAction extends AbstractAction {
403 public EditAction() {
404 // putValue(NAME, tr("Edit"));
405 putValue(SHORT_DESCRIPTION, tr("Edit the current help page"));
406 putValue(SMALL_ICON,ImageProvider.get("dialogs", "edit"));
407 }
408
409 public void actionPerformed(ActionEvent e) {
410 String url = getUrl();
411 if(url == null)
412 return;
413 if (!url.startsWith(HelpUtil.getWikiBaseHelpUrl())) {
414 String message = tr(
415 "<html>The current URL <tt>{0}</tt><br>"
416 + "is an external URL. Editing is only possible for help topics<br>"
417 + "on the help server <tt>{1}</tt>.</html>",
418 getUrl(),
419 HelpUtil.getWikiBaseUrl()
420 );
421 JOptionPane.showMessageDialog(
422 Main.parent,
423 message,
424 tr("Warning"),
425 JOptionPane.WARNING_MESSAGE
426 );
427 return;
428 }
429 url = url.replaceAll("#[^#]*$", "");
430 OpenBrowser.displayUrl(url+"?action=edit");
431 }
432 }
433
434 class ReloadAction extends AbstractAction {
435 public ReloadAction() {
436 //putValue(NAME, tr("Reload"));
437 putValue(SHORT_DESCRIPTION, tr("Reload the current help page"));
438 putValue(SMALL_ICON, ImageProvider.get("dialogs", "refresh"));
439 }
440
441 public void actionPerformed(ActionEvent e) {
442 openUrl(getUrl());
443 }
444 }
445
446 static class BackAction extends AbstractAction implements Observer {
447 private HelpBrowserHistory history;
448 public BackAction(HelpBrowserHistory history) {
449 this.history = history;
450 history.addObserver(this);
451 //putValue(NAME, tr("Back"));
452 putValue(SHORT_DESCRIPTION, tr("Go to the previous page"));
453 putValue(SMALL_ICON, ImageProvider.get("help", "previous"));
454 setEnabled(history.canGoBack());
455 }
456
457 public void actionPerformed(ActionEvent e) {
458 history.back();
459 }
460 public void update(Observable o, Object arg) {
461 //System.out.println("BackAction: canGoBoack=" + history.canGoBack() );
462 setEnabled(history.canGoBack());
463 }
464 }
465
466 static class ForwardAction extends AbstractAction implements Observer {
467 private HelpBrowserHistory history;
468 public ForwardAction(HelpBrowserHistory history) {
469 this.history = history;
470 history.addObserver(this);
471 //putValue(NAME, tr("Forward"));
472 putValue(SHORT_DESCRIPTION, tr("Go to the next page"));
473 putValue(SMALL_ICON, ImageProvider.get("help", "next"));
474 setEnabled(history.canGoForward());
475 }
476
477 public void actionPerformed(ActionEvent e) {
478 history.forward();
479 }
480 public void update(Observable o, Object arg) {
481 setEnabled(history.canGoForward());
482 }
483 }
484
485 class HomeAction extends AbstractAction {
486 public HomeAction() {
487 //putValue(NAME, tr("Home"));
488 putValue(SHORT_DESCRIPTION, tr("Go to the JOSM help home page"));
489 putValue(SMALL_ICON, ImageProvider.get("help", "home"));
490 }
491
492 public void actionPerformed(ActionEvent e) {
493 openHelpTopic("/");
494 }
495 }
496
497 class HyperlinkHandler implements HyperlinkListener {
498
499 /**
500 * Scrolls the help browser to the element with id <code>id</code>
501 *
502 * @param id the id
503 * @return true, if an element with this id was found and scrolling was successful; false, otherwise
504 */
505 protected boolean scrollToElementWithId(String id) {
506 Document d = help.getDocument();
507 if (d instanceof HTMLDocument) {
508 HTMLDocument doc = (HTMLDocument) d;
509 Element element = doc.getElement(id);
510 try {
511 Rectangle r = help.modelToView(element.getStartOffset());
512 if (r != null) {
513 Rectangle vis = help.getVisibleRect();
514 r.height = vis.height;
515 help.scrollRectToVisible(r);
516 return true;
517 }
518 } catch(BadLocationException e) {
519 System.err.println(tr("Warning: bad location in HTML document. Exception was: {0}", e.toString()));
520 e.printStackTrace();
521 }
522 }
523 return false;
524 }
525
526 /**
527 * Checks whether the hyperlink event originated on a <a ...> element with
528 * a relative href consisting of a URL fragment only, i.e.
529 * <a href="#thisIsALocalFragment">. If so, replies the fragment, i.e.
530 * "thisIsALocalFragment".
531 *
532 * Otherwise, replies null
533 *
534 * @param e the hyperlink event
535 * @return the local fragment
536 */
537 protected String getUrlFragment(HyperlinkEvent e) {
538 AttributeSet set = e.getSourceElement().getAttributes();
539 Object value = set.getAttribute(Tag.A);
540 if (value == null || ! (value instanceof SimpleAttributeSet)) return null;
541 SimpleAttributeSet atts = (SimpleAttributeSet)value;
542 value = atts.getAttribute(javax.swing.text.html.HTML.Attribute.HREF);
543 if (value == null) return null;
544 String s = (String)value;
545 if (s.matches("#.*"))
546 return s.substring(1);
547 return null;
548 }
549
550 public void hyperlinkUpdate(HyperlinkEvent e) {
551 if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED)
552 return;
553 if (e.getURL() == null) {
554 // Probably hyperlink event on a an A-element with a href consisting of
555 // a fragment only, i.e. "#ALocalFragment".
556 //
557 String fragment = getUrlFragment(e);
558 if (fragment != null) {
559 // first try to scroll to an element with id==fragment. This is the way
560 // table of contents are built in the JOSM wiki. If this fails, try to
561 // scroll to a <A name="..."> element.
562 //
563 if (!scrollToElementWithId(fragment)) {
564 help.scrollToReference(fragment);
565 }
566 } else {
567 HelpAwareOptionPane.showOptionDialog(
568 Main.parent,
569 tr("Failed to open help page. The target URL is empty."),
570 tr("Failed to open help page"),
571 JOptionPane.ERROR_MESSAGE,
572 null, /* no icon */
573 null, /* standard options, just OK button */
574 null, /* default is standard */
575 null /* no help context */
576 );
577 }
578 } else if (e.getURL().toString().endsWith("action=edit")) {
579 OpenBrowser.displayUrl(e.getURL().toString());
580 } else {
581 url = e.getURL().toString();
582 openUrl(e.getURL().toString());
583 }
584 }
585 }
586}
Note: See TracBrowser for help on using the repository browser.