PageRenderTime 174ms CodeModel.GetById 14ms app.highlight 143ms RepoModel.GetById 1ms app.codeStats 1ms

/bundles/plugins-trunk/InfoViewer/infoviewer/InfoViewer.java

#
Java | 1412 lines | 998 code | 191 blank | 223 comment | 133 complexity | 54a053dd92c8b7de9168bfd2fd434b1f MD5 | raw file
   1/*
   2 * InfoViewer.java - Info viewer for HTML, txt
   3 * Copyright (C) 2000-2002 Dirk Moebius
   4 * Based on HTMLViewer.java Copyright (C) 1999 Slava Pestov
   5 *
   6 * :tabSize=4:indentSize=4:noTabs=true:maxLineLen=0:
   7 *
   8 * This program is free software; you can redistribute it and/or
   9 * modify it under the terms of the GNU General Public License
  10 * as published by the Free Software Foundation; either version 2
  11 * of the License, or any later version.
  12 *
  13 * This program is distributed in the hope that it will be useful,
  14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16 * GNU General Public License for more details.
  17 *
  18 * You should have received a copy of the GNU General Public License
  19 * along with this program; if not, write to the Free Software
  20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  21 */
  22
  23package infoviewer;
  24
  25import infoviewer.actions.InfoViewerAction;
  26import infoviewer.actions.ToggleSidebar;
  27import infoviewer.workaround.EnhancedJEditorPane;
  28import infoviewer.workaround.EnhancedJToolBar;
  29
  30import java.awt.BorderLayout;
  31import java.awt.Cursor;
  32import java.awt.Dimension;
  33import java.awt.Font;
  34import java.awt.GridLayout;
  35import java.awt.Image;
  36import java.awt.event.ActionEvent;
  37import java.awt.event.ActionListener;
  38import java.awt.event.InputEvent;
  39import java.awt.event.KeyAdapter;
  40import java.awt.event.KeyEvent;
  41import java.awt.event.MouseAdapter;
  42import java.awt.event.MouseEvent;
  43import java.beans.PropertyChangeEvent;
  44import java.beans.PropertyChangeListener;
  45import java.io.File;
  46import java.io.FileNotFoundException;
  47import java.io.IOException;
  48import java.net.MalformedURLException;
  49import java.net.URI;
  50import java.net.URL;
  51import java.util.Arrays;
  52import java.util.Enumeration;
  53
  54
  55import javax.accessibility.AccessibleHypertext;
  56import javax.accessibility.AccessibleText;
  57import javax.swing.Box;
  58import javax.swing.ImageIcon;
  59import javax.swing.JButton;
  60import javax.swing.JComponent;
  61import javax.swing.JEditorPane;
  62import javax.swing.JLabel;
  63import javax.swing.JMenu;
  64import javax.swing.JMenuBar;
  65import javax.swing.JMenuItem;
  66import javax.swing.JPanel;
  67import javax.swing.JPopupMenu;
  68import javax.swing.JRootPane;
  69import javax.swing.JScrollBar;
  70import javax.swing.JScrollPane;
  71import javax.swing.JSeparator;
  72import javax.swing.JToolBar;
  73import javax.swing.SwingUtilities;
  74import javax.swing.Timer;
  75import javax.swing.border.BevelBorder;
  76import javax.swing.event.HyperlinkEvent;
  77import javax.swing.event.HyperlinkListener;
  78import javax.swing.text.Caret;
  79import javax.swing.text.Document;
  80import javax.swing.text.Style;
  81import javax.swing.text.html.HTMLDocument;
  82import javax.swing.text.html.HTMLEditorKit;
  83import javax.swing.text.html.HTMLFrameHyperlinkEvent;
  84import javax.swing.text.html.StyleSheet;
  85
  86import org.gjt.sp.jedit.ActionContext;
  87import org.gjt.sp.jedit.ActionSet;
  88import org.gjt.sp.jedit.Buffer;
  89import org.gjt.sp.jedit.EBComponent;
  90import org.gjt.sp.jedit.EBMessage;
  91import org.gjt.sp.jedit.EditBus;
  92import org.gjt.sp.jedit.EditPane;
  93import org.gjt.sp.jedit.GUIUtilities;
  94import org.gjt.sp.jedit.MiscUtilities;
  95import org.gjt.sp.jedit.View;
  96import org.gjt.sp.jedit.gui.HistoryModel;
  97import org.gjt.sp.jedit.jEdit;
  98import org.gjt.sp.jedit.gui.DefaultFocusComponent;
  99import org.gjt.sp.jedit.gui.DockableWindowManager;
 100import org.gjt.sp.jedit.gui.HistoryTextField;
 101import org.gjt.sp.jedit.io.FileVFS;
 102import org.gjt.sp.jedit.msg.BufferUpdate;
 103import org.gjt.sp.jedit.msg.EditPaneUpdate;
 104import org.gjt.sp.jedit.msg.PropertiesChanged;
 105import org.gjt.sp.util.Log;
 106
 107/**
 108 * an info viewer for jEdit. It uses a Swing JEditorPane to display the HTML,
 109 * and implements a URL history, bookmarks and some other web browsing
 110 * functions.
 111 *
 112 * @author Dirk Moebius
 113 * @author Slava Pestov
 114 */
 115public class InfoViewer extends JPanel implements HyperlinkListener, PropertyChangeListener,
 116	EBComponent, DefaultFocusComponent
 117{
 118	// {{{ Proteced Members
 119	protected JPanel outerPanel;
 120
 121	protected JPanel innerPanel;
 122
 123	protected MyScrollPane scrViewer;
 124
 125	// }}}
 126
 127	/**
 128	 * Creates a new info viewer instance.
 129	 *
 130	 * @param view
 131	 *                where this dockable is docked into.
 132	 * @param position
 133	 *                docking position.
 134	 */
 135	public InfoViewer(View view, String position)
 136	{
 137		setName("infoviewer");
 138		if (position == null)
 139			position = DockableWindowManager.FLOATING;
 140		setLayout(new BorderLayout());
 141		this.view = view;
 142		this.isDocked = !(position.equals(DockableWindowManager.FLOATING));
 143		this.history = new History();
 144		this.historyhandler = new URLButtonHandler(false);
 145		this.bookmarkhandler = new URLButtonHandler(true);
 146
 147		// initialize actions
 148		createActions();
 149
 150		/** this is for handling the "esc" key only */
 151		KeyHandler escKeyHandler = new KeyHandler();
 152		addKeyListener(escKeyHandler);
 153		JRootPane root = getRootPane();
 154		if (root != null)
 155			root.addKeyListener(escKeyHandler);
 156
 157		// the menu
 158		JMenuBar mb = createMenu();
 159		// the toolbar
 160		JToolBar tb = createToolbar();
 161		// the url address bar
 162		JPanel addressBar = createAddressBar();
 163		addressBar.addKeyListener(escKeyHandler);
 164		// the status bar
 165		JPanel statusBar = createStatusBar();
 166
 167		// the viewer
 168		viewer = new EnhancedJEditorPane();
 169		viewer.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
 170		viewer.addKeyListener(escKeyHandler);
 171		viewer.setEditable(false);
 172		viewer.setFocusable(true);
 173		Font vf = jEdit.getFontProperty("metal.secondary.font");
 174		viewer.setFont(jEdit.getFontProperty("helpviewer.font", vf));
 175		viewer.addHyperlinkListener(this);
 176		viewer.addPropertyChangeListener(this);
 177		viewer.addMouseListener(new MouseHandler());
 178
 179		scrViewer = new MyScrollPane(viewer);
 180		scrViewer.setFocusable(false);
 181		viewer.setArrowKeyHandler(new ArrowKeyHandler());
 182		// HTMLEditorKit is not yet in use here
 183
 184		// the inner content: url textfield, viewer, status bar
 185		String appearancePrefix = "infoviewer.appearance."
 186			+ (isDocked ? "docked." : "floating.");
 187		innerPanel = new JPanel(new BorderLayout());
 188
 189		innerPanel.add(scrViewer, BorderLayout.CENTER);
 190		if (jEdit.getBooleanProperty(appearancePrefix + "showAddressbar"))
 191			innerPanel.add(addressBar, BorderLayout.NORTH);
 192		if (jEdit.getBooleanProperty(appearancePrefix + "showStatusbar"))
 193			innerPanel.add(statusBar, BorderLayout.SOUTH);
 194
 195		// the outer content: toolbar, inner content
 196		outerPanel = new JPanel(new BorderLayout());
 197		outerPanel.add(innerPanel, BorderLayout.CENTER);
 198		if (jEdit.getBooleanProperty(appearancePrefix + "showToolbar"))
 199			outerPanel.add(tb, BorderLayout.NORTH);
 200
 201		// overall layout: menu, outer content
 202		if (jEdit.getBooleanProperty(appearancePrefix + "showMenu"))
 203			add(mb, BorderLayout.NORTH);
 204		add(outerPanel, BorderLayout.CENTER);
 205
 206		updateStatus();
 207		updateTimers();
 208
 209		// show start URL (either homepage or current buffer)
 210		if (jEdit.getBooleanProperty("infoviewer.autoupdate")
 211			&& (jEdit.getBooleanProperty("infoviewer.autoupdate.onSwitch")
 212				|| jEdit.getBooleanProperty("infoviewer.autoupdate.onSave") || jEdit
 213				.getBooleanProperty("infoviewer.autoupdate.onChange")))
 214		{
 215			// auto-update and sync with buffer: open current buffer
 216			// at startup
 217			gotoBufferURL();
 218		}
 219		else
 220		{
 221		    if (jEdit.getBooleanProperty("infoviewer.restorePreviousPage") && urlField.getModel().getSize() > 0) {
 222		        // Restore previous page on startup
 223		        gotoURL(urlField.getModel().getItem(0), true, 0);
 224		    }
 225            else {
 226                // open homepage at startup
 227                String home = jEdit.getProperty("infoviewer.homepage");
 228                currentURL = new TitledURLEntry("Infoviewer Homepage", home);
 229                if (home != null)
 230                    gotoURL(home, true, 0);
 231            }
 232        }
 233		urlField.addKeyListener(escKeyHandler);
 234		setFocusCycleRoot(true);
 235		Caret c = viewer.getCaret();
 236		c.setVisible(true);
 237	}
 238
 239	protected Document getDocument()
 240	{
 241		return viewer.getDocument();
 242	}
 243
 244	public TitledURLEntry getCurrentURL()
 245	{
 246		String url = urlField.getText();
 247		if (url == null || url.length() < 1)
 248			return null;
 249		currentURL = new TitledURLEntry(title.getText(), urlField.getText());
 250		int scrollBarPos = scrViewer.getVerticalScrollBar().getValue();
 251		currentURL.setScrollBarPos(scrollBarPos);
 252		return currentURL;
 253	}
 254
 255	// {{{ gotoURL()
 256	public void gotoURL(String url)
 257	{
 258		gotoURL(url, true, 0);
 259	}
 260
 261	public void gotoURL(String url, boolean addToHistory, int vertPos)
 262	{
 263		try
 264		{
 265			gotoURL(new URL(url), addToHistory, vertPos);
 266		}
 267		catch (MalformedURLException mfue)
 268		{
 269
 270		}
 271	}
 272
 273	/**
 274	 * Displays the specified URL in the HTML component.
 275	 *
 276	 * @param url
 277	 *                The URL as String
 278	 * @param addToHistory
 279	 *                Should the URL be added to the back/forward history?
 280	 */
 281	public void gotoURL(TitledURLEntry entry, boolean addToHistory)
 282	{
 283		String url = entry.getURL();
 284		try
 285		{
 286			URI baseURI = new File(MiscUtilities.constructPath(jEdit.getJEditHome(), "doc")).toURI();
 287			baseURL = baseURI.toURL().toString();
 288		}
 289		catch (MalformedURLException mu)
 290		{
 291			Log.log(Log.ERROR, this, mu);
 292			// what to do?
 293		}
 294		if (MiscUtilities.isURL(url))
 295		{
 296			if (url.startsWith(baseURL))
 297			{
 298				shortURL = url.substring(baseURL.length());
 299				if (shortURL.startsWith("/"))
 300					shortURL = shortURL.substring(1);
 301			}
 302			else
 303			{
 304				shortURL = url;
 305			}
 306		}
 307		else
 308		{
 309			shortURL = url;
 310			if (baseURL.endsWith("/"))
 311				url = baseURL + url;
 312			else
 313				url = baseURL + '/' + url;
 314		}
 315
 316		if (url == null)
 317			return;
 318		url = url.trim();
 319		if (url.length() == 0)
 320			return;
 321
 322		try
 323		{
 324			URL u = new URL(url);
 325			gotoURL(u, addToHistory, entry.getScrollBarPos());
 326		}
 327		catch (MalformedURLException mu)
 328		{
 329			urlField.setText(url);
 330			showError(props("infoviewer.error.badurl.message", new Object[] { mu }));
 331		}
 332	}
 333
 334
 335	/**
 336	 * Convenience function
 337	 *
 338	 * @param url
 339	 * @param addToHistory
 340	 */
 341	public void gotoURL(URL url, boolean addToHistory)
 342	{
 343		gotoURL(url, addToHistory, 0);
 344	}
 345
 346	/**
 347	 * Displays the specified URL in the HTML component.
 348	 *
 349	 * @param url
 350	 *                The URL
 351	 * @param addToHistory
 352	 *                Should the URL be added to the back/forward history?
 353	 */
 354	public void gotoURL(URL url, boolean addToHistory, final int scrollBarPos)
 355	{
 356		if (url == null)
 357			return;
 358		String urlText = url.toString().trim();
 359		if (urlText.length() == 0)
 360			return;
 361
 362		if (addToHistory) 
 363		{
 364			history.add(getCurrentURL());
 365			HistoryModel hm = urlField.getModel();			
 366			hm.addItem(urlText);
 367		}
 368
 369		urlField.setText(urlText);
 370		viewer.setCursor(Cursor.getDefaultCursor());
 371		currentURL = new TitledURLEntry(urlText, urlText, scrollBarPos);
 372		currentStatus = LOADING;
 373
 374		updateStatus();
 375		updateGoMenu();
 376
 377		try
 378		{
 379			// viewer.getEditorKit().createDefaultDocument();
 380			viewer.setPage(url);
 381
 382			// the style of the viewer
 383			if (viewer.getEditorKit() instanceof HTMLEditorKit)
 384			{
 385				HTMLEditorKit htmlEditorKit = (HTMLEditorKit) (viewer
 386					.getEditorKit());
 387				// HTMLDocument
 388				// doc=(HTMLDocument)viewer.getDocument();
 389				// Log.log(Log.DEBUG, this, "htmleditorkit in
 390				// use");
 391				StyleSheet styles;
 392				// StyleSheet
 393				// styles=htmlEditorKit.getStyleSheet();
 394				// if(doc!=null) {
 395				// Log.log(Log.DEBUG, this, "styles from doc");
 396				// styles=doc.getStyleSheet();
 397				// code below dies with NPE then
 398				// }
 399				// else {
 400				// Log.log(Log.DEBUG, this, "styles from
 401				// editor");
 402				styles = htmlEditorKit.getStyleSheet();
 403				// }
 404				Enumeration rules;
 405				if (styles != null)
 406				{
 407					// list available styles (which contain
 408					// 'font-size')
 409					rules = styles.getStyleNames();
 410					while (rules.hasMoreElements())
 411					{
 412						String name = (String) rules.nextElement();
 413						Style rule = styles.getStyle(name);
 414						if (rule.toString().indexOf("font-size") > -1)
 415						{
 416							Log.log(Log.DEBUG, this, name + "[old] : "
 417								+ rule.toString());
 418						}
 419					}
 420
 421					// make body fontsize smaller
 422					Style bodyrule = styles.getStyle("body");
 423					Style bodyruleparent = (Style) bodyrule.getResolveParent();
 424					if (bodyrule != null)
 425					{
 426						styles.removeStyle("body");
 427						Style newbodyrule = styles.addStyle("body",
 428							bodyruleparent);
 429
 430						if (bodyruleparent != null)
 431							Log.log(Log.DEBUG, this, "bodyrule.p="
 432								+ bodyruleparent.toString());
 433						Log.log(Log.DEBUG, this, "bodyrule.1="
 434							+ bodyrule.toString());
 435						// String
 436						// val=(String)bodyrule.getAttribute("font-size");
 437						// Log.log(Log.DEBUG, this,
 438						// "body.font-size="+val);
 439						// bodyrule.removeAttribute("font-size");
 440						Enumeration attrs = bodyrule.getAttributeNames();
 441						if (attrs != null)
 442						{
 443							// Log.log(Log.DEBUG,
 444							// this, "copying
 445							// attributes");
 446							while (attrs.hasMoreElements())
 447							{
 448								Object name = attrs.nextElement();
 449								// Log.log(Log.DEBUG,
 450								// this, "
 451								// attribute.name="+name.toString());
 452								if (!name.toString().equals("font-size"))
 453								{
 454									newbodyrule.addAttribute(name,
 455											bodyrule.getAttribute(name));
 456								}
 457							}
 458						}
 459
 460						// Action myaction=new
 461						// StyledEditorKit.FontSizeAction("new
 462						// font size",
 463						// Integer.parseInt(size));
 464						// myaction.actionPerformed(null);
 465
 466						// HTMLDocument doc =
 467						// ((HTMLDocument)
 468						// viewer.getDocument());
 469						// doc.setCharacterAttributes(0,
 470						// doc.getLength(), newbodyrule,
 471						// true);
 472
 473						/*
 474						 * Log.log(Log.DEBUG, this,
 475						 * "bodyrule.2="+bodyrule.toString());
 476						 * bodyrule.addAttribute("font-size","10pt");
 477						 * Log.log(Log.DEBUG, this,
 478						 * "bodyrule.3="+bodyrule.toString());
 479						 * newbodyrule.addAttributes(bodyrule);
 480						 */
 481						Log.log(Log.DEBUG, this, "bodyrule.2="
 482							+ newbodyrule.toString());
 483					}
 484					// styles.setBaseFontSize(1);
 485					// htmlEditorKit.setStyleSheet(styles);
 486					viewer.repaint();
 487
 488					// list available styles (which contain
 489					// 'font-size')
 490					rules = styles.getStyleNames();
 491					while (rules.hasMoreElements())
 492					{
 493						String name = (String) rules.nextElement();
 494						Style rule = styles.getStyle(name);
 495						if (rule.toString().indexOf("font-size") > -1)
 496						{
 497							Log.log(Log.DEBUG, this, name + "[new] : "
 498								+ rule.toString());
 499						}
 500					}
 501				}
 502				else
 503				{
 504					// Log.log(Log.WARNING, this, "empty
 505					// style set");
 506				}
 507			}
 508			else
 509			{
 510				// Log.log(Log.WARNING, this, "unexpected kind
 511				// of editorkit in use");
 512			}
 513		}
 514		catch (FileNotFoundException fnf)
 515		{
 516			String[] args = { urlText };
 517			showError(props("infoviewer.error.filenotfound.message", args));
 518		}
 519		catch (IOException io)
 520		{
 521			Log.log(Log.ERROR, this, io);
 522			String[] args = { urlText, io.getMessage() };
 523			showError(props("infoviewer.error.ioerror.message", args));
 524		}
 525		catch (Exception ex)
 526		{
 527			Log.log(Log.ERROR, this,
 528				"JEditorPane.setPage() threw an exception, probably a Swing bug:", ex);
 529		}
 530		finally
 531		{
 532			updateTimers();
 533			previousScrollBarValue = scrollBarPos;
 534		}
 535	}
 536
 537	/**
 538	 * Show the contents of the current jEdit buffer in InfoViewer.
 539	 */
 540	public void gotoBufferURL()
 541	{
 542		Buffer buffer = view.getBuffer();
 543		String url = buffer.getPath();
 544		if (buffer.getVFS() instanceof FileVFS)
 545			url = "file:" + url;
 546		gotoURL(url, false, 0);
 547	}
 548
 549	/**
 550	 * Go forward in history. Beep if that's not possible.
 551	 */
 552	public void forward()
 553	{
 554		TitledURLEntry ent = history.getNext(getCurrentURL());
 555
 556		if (ent == null)
 557			getToolkit().beep();
 558		else
 559			gotoURL(ent, false);
 560	}
 561
 562	/**
 563	 * Go back in history. Beep, if that's not possible.
 564	 */
 565	public void back()
 566	{
 567		TitledURLEntry prevURL = history.getPrevious(getCurrentURL());
 568
 569		if (prevURL == null) {
 570			getToolkit().beep();
 571		}
 572		else {
 573			gotoURL(prevURL, false);
 574		}
 575	}
 576
 577	/**
 578	 * Reload the current URL.
 579	 */
 580	public void reload()
 581	{
 582		if (currentURL == null)
 583			return;
 584
 585		previousScrollBarValue = scrViewer.getVerticalScrollBar().getValue();
 586		// Clear the viewer and flush viewers' memorized URL:
 587		viewer.getDocument().putProperty(Document.StreamDescriptionProperty, null);
 588		gotoURL(getCurrentURL(), false);
 589	}
 590
 591	/**
 592	 * Add the current page to the bookmark list.
 593	 */
 594	public void addToBookmarks()
 595	{
 596		if (currentURL == null)
 597		{
 598			GUIUtilities.error(null, "infoviewer.error.nourl", null);
 599			return;
 600		}
 601
 602		jEdit.setProperty("infoviewer.bookmarks.title." + bookmarks.getSize(), currentURL
 603			.getTitle());
 604		jEdit.setProperty("infoviewer.bookmarks.url." + bookmarks.getSize(), currentURL
 605			.getURL());
 606		bookmarks.add(currentURL);
 607
 608		jEdit.unsetProperty("infoviewer.bookmarks.title." + bookmarks.getSize());
 609		jEdit.unsetProperty("infoviewer.bookmarks.url." + bookmarks.getSize());
 610
 611		// add menu item
 612		JMenuItem mi = new JMenuItem(currentURL.getTitle());
 613		mBmarks.add(mi);
 614		mi.setActionCommand(currentURL.getURL());
 615		mi.addActionListener(bookmarkhandler);
 616	}
 617
 618	/**
 619	 * Return the JEditorPane instance that is used to view HTML and text
 620	 * URLs.
 621	 */
 622	public JEditorPane getViewer()
 623	{
 624		return viewer;
 625	}
 626
 627	/**
 628	 * From interface HyperlinkListener: called when a hyperlink is clicked,
 629	 * entered or leaved.
 630	 */
 631	public void hyperlinkUpdate(HyperlinkEvent evt)
 632	{
 633		URL url = evt.getURL();
 634		if (evt.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
 635		{
 636			if (evt instanceof HTMLFrameHyperlinkEvent)
 637			{
 638				((HTMLDocument) viewer.getDocument())
 639					.processHTMLFrameHyperlinkEvent((HTMLFrameHyperlinkEvent) evt);
 640			}
 641			else
 642			{
 643				if (url != null)
 644					gotoURL(url, true, 0);
 645			}
 646		}
 647		else if (evt.getEventType() == HyperlinkEvent.EventType.ENTERED)
 648		{
 649			viewer.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
 650			if (url != null)
 651				setStatusText(url.toString());
 652		}
 653		else if (evt.getEventType() == HyperlinkEvent.EventType.EXITED)
 654		{
 655			viewer.setCursor(Cursor.getDefaultCursor());
 656			updateStatus();
 657		}
 658	}
 659
 660	/**
 661	 * From interface PropertyChangeListener: called, when a property is
 662	 * changed. This is used to listen for "page" property change events,
 663	 * which occur, when the page is loaded completely.
 664	 */
 665	public void propertyChange(PropertyChangeEvent e)
 666	{
 667		if ("page".equals(e.getPropertyName()))
 668			pageComplete();
 669	}
 670
 671	/**
 672	 * From interface EBComponent: Listen for messages on the EditBus.
 673	 * Currently it listens for PropertiesChanged messages, to update any
 674	 * bookmark changes.
 675	 */
 676	public void handleMessage(EBMessage msg)
 677	{
 678		if (msg instanceof EditPaneUpdate)
 679		{
 680			EditPaneUpdate emsg = (EditPaneUpdate) msg;
 681			EditPane editPane = emsg.getEditPane();
 682			if (editPane == view.getEditPane())
 683			{
 684				if (emsg.getWhat() == EditPaneUpdate.BUFFER_CHANGED)
 685				{
 686					if (jEdit.getBooleanProperty("infoviewer.autoupdate")
 687						&& jEdit
 688							.getBooleanProperty("infoviewer.autoupdate.onSwitch"))
 689					{
 690						gotoBufferURL();
 691					}
 692				}
 693			}
 694		}
 695		else if (msg instanceof BufferUpdate)
 696		{
 697			BufferUpdate bmsg = (BufferUpdate) msg;
 698			if (bmsg.getWhat() == BufferUpdate.DIRTY_CHANGED
 699				&& bmsg.getBuffer() == view.getBuffer()
 700				&& !bmsg.getBuffer().isDirty())
 701			{
 702				// buffer save detected
 703				if (jEdit.getBooleanProperty("infoviewer.autoupdate")
 704					&& jEdit.getBooleanProperty("infoviewer.autoupdate.onSave") )
 705				{
 706					reload();
 707				}
 708			}
 709		}
 710		else if (msg instanceof PropertiesChanged)
 711		{
 712			updateBookmarksMenu();
 713			updateGoMenu();
 714			updateTimers();
 715		}
 716	}
 717
 718	public void addNotify()
 719	{
 720		super.addNotify();
 721		EditBus.addToBus(this);
 722	}
 723
 724	public void focusAddressBar()
 725	{
 726		urlField.requestFocus(true);
 727	}
 728
 729	public void focusOnDefaultComponent()
 730	{
 731		viewer.requestFocus();
 732	}
 733
 734	public void removeNotify()
 735	{
 736		super.removeNotify();
 737		EditBus.removeFromBus(this);
 738
 739		if (periodicTimer != null)
 740			periodicTimer.stop();
 741	}
 742
 743	public String getShortURL()
 744	{
 745		return shortURL;
 746	}
 747
 748	public String getBaseURL()
 749	{
 750		return baseURL;
 751	}
 752
 753	// {{{ createActions ()
 754	private void createActions()
 755	{
 756		aOpenFile = new infoviewer.actions.open_file();
 757		aOpenBuffer = new infoviewer.actions.open_buffer();
 758		aEditURL = new infoviewer.actions.edit_url();
 759		aReload = new infoviewer.actions.reload();
 760		aClose = new infoviewer.actions.close();
 761		aCopy = new infoviewer.actions.copy();
 762		aSelectAll = new infoviewer.actions.select_all();
 763		aBack = new infoviewer.actions.back();
 764		aOpenLocation = new infoviewer.actions.OpenLocation();
 765		aForward = new infoviewer.actions.forward();
 766		aHome = new infoviewer.actions.home();
 767		aBookmarksAdd = new infoviewer.actions.bookmarks_add();
 768		aBookmarksEdit = new infoviewer.actions.bookmarks_edit();
 769		aToggleSidebar = new infoviewer.actions.ToggleSidebar();
 770
 771		aAbout = new infoviewer.actions.about();
 772		aFollowLink = new infoviewer.actions.follow_link();
 773	}
 774
 775	// }}}
 776
 777	// {{{ createMenu()
 778	private JMenuBar createMenu()
 779	{
 780		// File menu
 781		JMenu mFile = new JMenu(props("infoviewer.menu.file"));
 782		mFile.setMnemonic(props("infoviewer.menu.file.mnemonic").charAt(0));
 783		mFile.add(aOpenFile);
 784		mFile.add(aOpenBuffer);
 785		mFile.add(aEditURL);
 786		mFile.add(aReload);
 787		mFile.add(new JSeparator());
 788		mFile.add(aClose);
 789
 790		// Edit menu
 791		JMenu mEdit = new JMenu(props("infoviewer.menu.edit"));
 792		mEdit.setMnemonic(props("infoviewer.menu.edit.mnemonic").charAt(0));
 793		mEdit.add(aCopy);
 794		mEdit.add(aSelectAll);
 795
 796		// View menu
 797		JMenu mView = new JMenu(props("infoviewer.menu.view"));
 798		mView.setMnemonic(props("infoviewer.menu.view.mnemonic").charAt(0));
 799		JMenuItem item = aToggleSidebar.menuItem();
 800		mView.add(item);
 801
 802		// Goto menu
 803
 804		mGoto = new JMenu(props("infoviewer.menu.goto"));
 805		mGoto.setMnemonic(props("infoviewer.menu.goto.mnemonic").charAt(0));
 806		updateGoMenu();
 807
 808		// Bookmarks menu
 809		mBmarks = new JMenu(props("infoviewer.menu.bmarks"));
 810		mBmarks.setMnemonic(props("infoviewer.menu.bmarks.mnemonic").charAt(0));
 811		updateBookmarksMenu();
 812
 813		// Help menu
 814		mHelp = new JMenu(props("infoviewer.menu.help"));
 815		mHelp.setMnemonic(props("infoviewer.menu.help.mnemonic").charAt(0));
 816		updateHelpMenu();
 817
 818		// Menubar
 819		JMenuBar mb = new JMenuBar();
 820		mb.add(mFile);
 821		mb.add(mEdit);
 822		mb.add(mView);
 823		mb.add(mGoto);
 824		mb.add(mBmarks);
 825		mb.add(mHelp);
 826
 827		return mb;
 828	}
 829
 830	// }}}
 831
 832	// {{{
 833	private JToolBar createToolbar()
 834	{
 835		EnhancedJToolBar tb = new EnhancedJToolBar(JToolBar.HORIZONTAL);
 836
 837		tb.add(aBack);
 838		tb.add(aForward);
 839		tb.add(aReload);
 840		tb.add(aHome);
 841		tb.add(aOpenFile);
 842		tb.add(aEditURL);
 843		tb.add(aOpenBuffer);
 844
 845		tb.add(Box.createHorizontalGlue());
 846
 847		bStartStop = new JButton(ICON_ANIM)
 848		{
 849			private static final long serialVersionUID = 3350768542711107896L;
 850
 851			// Otherwise the animated gif keeps calling this method
 852			// even when
 853			// the component is no longer visible, causing a memory
 854			// leak.
 855			public boolean imageUpdate(Image img, int infoflags, int x, int y, int w,
 856				int h)
 857			{
 858				if (!isDisplayable())
 859					return false;
 860				else
 861					return super.imageUpdate(img, infoflags, x, y, w, h);
 862			}
 863
 864		};
 865		bStartStop.setDisabledIcon(ICON_NOANIM);
 866		bStartStop.setBorderPainted(false);
 867		bStartStop.setEnabled(false);
 868		tb.add(bStartStop);
 869
 870		return tb;
 871	}
 872
 873	// }}}
 874
 875	public void toggleSideBar()
 876	{
 877	}
 878
 879	private JPanel createAddressBar()
 880	{
 881		// the url textfield
 882		urlField = new HistoryTextField("infoviewer");
 883		urlField.setFocusAccelerator('l');
 884		urlField.addActionListener(new ActionListener()
 885		{
 886			public void actionPerformed(ActionEvent evt)
 887			{
 888				gotoURL(urlField.getText(), true, -1);
 889			}
 890		});
 891
 892		// url textfield and label
 893		JPanel panel = new JPanel(new BorderLayout());
 894		panel.add(new JLabel(props("infoviewer.label.gotoURL")), BorderLayout.WEST);
 895		panel.add(urlField, BorderLayout.CENTER);
 896
 897		return panel;
 898	}
 899
 900	private JPanel createStatusBar()
 901	{
 902		// the status text field
 903		status = new JLabel(GREET);
 904		status.setBorder(new BevelBorder(BevelBorder.LOWERED));
 905		status.setFont(new Font("Dialog", Font.PLAIN, 10));
 906		status.setMinimumSize(new Dimension(100, status.getPreferredSize().height));
 907
 908		// the title text field
 909		title = new JLabel("No Document");
 910		title.setBorder(new BevelBorder(BevelBorder.LOWERED));
 911		title.setFont(new Font("Dialog", Font.PLAIN, 10));
 912		title.setMinimumSize(new Dimension(100, title.getPreferredSize().height));
 913
 914		// status and title field
 915		JPanel statusBar = new JPanel(new GridLayout(1, 0));
 916		statusBar.add(status);
 917		statusBar.add(title);
 918
 919		return statusBar;
 920	}
 921
 922	/**
 923	 * Update the bookmarks menu according to the bookmarks stored in the
 924	 * properties.
 925	 */
 926	private synchronized void updateBookmarksMenu()
 927	{
 928		mBmarks.removeAll();
 929		mBmarks.add(aBookmarksAdd);
 930		mBmarks.add(aBookmarksEdit);
 931		mBmarks.add(new JSeparator());
 932
 933		// add bookmarks
 934		bookmarks = new Bookmarks();
 935		for (int i = 0; i < bookmarks.getSize(); i++)
 936		{
 937			String title = bookmarks.getTitle(i);
 938			if (title.length() > 0 && title.charAt(0) == '-')
 939				mBmarks.add(new JSeparator());
 940			else
 941			{
 942				JMenuItem mi = new JMenuItem(title);
 943				mBmarks.add(mi);
 944				mi.setActionCommand(bookmarks.getURL(i));
 945				mi.addActionListener(bookmarkhandler);
 946			}
 947		}
 948	}
 949
 950	private void updateHelpMenu()
 951	{
 952		mHelp.removeAll();
 953		mHelp.add(aAbout);
 954
 955		// add a menu item for the docs
 956		JMenuItem mi = new JMenuItem(props("infoviewer.menu.help.readme"));
 957		mi.setActionCommand(props("infoviewer.menu.help.readme.url"));
 958		mi.addActionListener(bookmarkhandler);
 959		mi.setMnemonic(props("infoviewer.menu.help.readme.mnemonic").charAt(0));
 960		mHelp.add(mi);
 961	}
 962
 963	private synchronized void updateGoMenu()
 964	{
 965		mGoto.removeAll();
 966		mGoto.add(aOpenLocation);
 967		mGoto.add(aBack);
 968		mGoto.add(aForward);
 969		mGoto.add(aHome);
 970		mGoto.add(new JSeparator());
 971
 972		// add history
 973		TitledURLEntry[] entr = history.getGoMenuEntries();
 974		int pos = history.getHistoryPos();
 975
 976		for (int i = 0; i < entr.length; i++)
 977		{
 978			JMenuItem mi = new JMenuItem(entr[i].getTitle(),
 979				entr[i].equals(currentURL) ? ICON_CHECK : ICON_NOCHECK);
 980
 981			mi.setActionCommand(entr[i].getURL());
 982			mi.addActionListener(historyhandler);
 983			mGoto.add(mi);
 984		}
 985	}
 986
 987	private synchronized void updateGoMenuTitles()
 988	{
 989		TitledURLEntry[] entr = history.getGoMenuEntries();
 990		for (int i = 0; i < entr.length; i++)
 991		{
 992
 993			JMenuItem mi = mGoto.getItem(i + 5);
 994			if (mi == null)
 995			{
 996				mi = new JMenuItem();
 997				mGoto.add(mi);
 998			}
 999			mi.setText(entr[i].getTitle());
1000		}
1001	}
1002
1003	private void updateActions()
1004	{
1005		SwingUtilities.invokeLater(new Runnable()
1006		{
1007			public void run()
1008			{
1009				aForward.setEnabled(history.hasNext());
1010				aBack.setEnabled(history.hasPrevious());
1011				aEditURL.setEnabled(currentURL != null);
1012				bStartStop.setEnabled(currentStatus == LOADING);
1013			}
1014		});
1015	}
1016
1017	private void updateStatus()
1018	{
1019		switch (currentStatus)
1020		{
1021		case LOADING:
1022			setStatusText(props("infoviewer.status.loading", new Object[] { currentURL
1023				.getURL() }));
1024			break;
1025		case READY:
1026			int size = viewer.getDocument().getLength();
1027			setStatusText(props("infoviewer.status.ready", new Integer[] { new Integer(
1028				size) }));
1029			break;
1030		case ERROR:
1031			setStatusText(props("infoviewer.status.error"));
1032			break;
1033		default:
1034			setStatusText(GREET);
1035			break;
1036		}
1037
1038		updateActions();
1039	}
1040
1041	private void updateTimers()
1042	{
1043		if (periodicTimer != null)
1044			periodicTimer.stop();
1045
1046		if (jEdit.getBooleanProperty("infoviewer.autoupdate"))
1047		{
1048			if (jEdit.getBooleanProperty("infoviewer.autoupdate.periodically"))
1049			{
1050				try
1051				{
1052					periodicDelay = Integer
1053						.parseInt(jEdit
1054							.getProperty("infoviewer.autoupdate.periodically.delay"));
1055				}
1056				catch (NumberFormatException e)
1057				{
1058					periodicDelay = 20000;
1059				}
1060
1061				periodicTimer = new Timer(periodicDelay, new ActionListener()
1062				{
1063					public void actionPerformed(ActionEvent evt)
1064					{
1065						if (currentStatus != LOADING && currentURL != null)
1066						{
1067							Log.log(Log.DEBUG, this,
1068								"periodic update (every "
1069									+ periodicDelay + "ms): "
1070									+ currentURL);
1071							reload();
1072						}
1073					}
1074				});
1075
1076				periodicTimer.setInitialDelay(periodicDelay);
1077				periodicTimer.setRepeats(true);
1078				periodicTimer.setCoalesce(true);
1079				periodicTimer.start();
1080			}
1081		}
1082	}
1083
1084	private void pageComplete()
1085	{
1086		// restore previous vertical scrollbar value, if page was
1087		// reloaded
1088		if (previousScrollBarValue >= 0)
1089		{
1090			JScrollBar jsb = scrViewer.getVerticalScrollBar();
1091			if (previousScrollBarValue < jsb.getMaximum())
1092				jsb.setValue(previousScrollBarValue);
1093			else jsb.setValue(jsb.getMaximum());
1094		}
1095
1096		// try to get the title of the document
1097		Document doc = viewer.getDocument();
1098		if (doc != null)
1099		{
1100			String newTitle = getTitleFromDocument(doc);
1101			if (currentURL != null)
1102			{
1103				currentURL.setTitle(newTitle);
1104			}
1105			// set the new window title
1106			setTitle(newTitle);
1107			// update title in the "Go" menu history
1108			updateGoMenuTitles();
1109		}
1110
1111		currentStatus = READY;
1112		updateStatus();
1113	}
1114
1115	/** try to get the title of the document */
1116	private String getTitleFromDocument(Document doc)
1117	{
1118		Object obj = doc.getProperty(Document.TitleProperty);
1119		if (obj == null)
1120			return currentURL != null ? currentURL.getURL()
1121				: props("infoviewer.notitle");
1122		else
1123			return obj.toString();
1124	}
1125
1126	private void setStatusText(final String text)
1127	{
1128		SwingUtilities.invokeLater(new Runnable()
1129		{
1130			public void run()
1131			{
1132				status.setText(text);
1133			}
1134		});
1135	}
1136
1137	private void setTitle(final String text)
1138	{
1139		SwingUtilities.invokeLater(new Runnable()
1140		{
1141			public void run()
1142			{
1143				title.setText(text);
1144			}
1145		});
1146	}
1147
1148
1149	protected void dismiss()
1150	{
1151		DockableWindowManager dwm = jEdit.getActiveView().getDockableWindowManager();
1152		String name = getName();
1153		dwm.hideDockableWindow(name);
1154	}
1155
1156	protected void showError(String errortext)
1157	{
1158		viewer.getDocument().putProperty(Document.StreamDescriptionProperty, null);
1159		viewer.getEditorKit().createDefaultDocument();
1160		viewer.setContentType("text/html");
1161		viewer.setText("<html><head></head><body>\n" + "<h1>Error</h1><p>\n" + errortext
1162			+ "\n</body></html>");
1163		currentURL = null;
1164		currentStatus = ERROR;
1165		updateStatus();
1166	}
1167
1168	/** convenience method for jEdit.getProperty(String). */
1169	private static String props(String key)
1170	{
1171		return jEdit.getProperty(key);
1172	}
1173
1174	/** convenience method for jEdit.getProperty(String,Object[]). */
1175	private static String props(String key, Object[] args)
1176	{
1177		return jEdit.getProperty(key, args);
1178	}
1179
1180
1181	// greet string
1182	private final static String GREET = props("infoviewer.greetstring", new Object[] {
1183		props("infoviewer.title"), props("plugin.infoviewer.InfoViewerPlugin.version") });
1184
1185	// status numbers for updateStatus()
1186	private final static int LOADING = 1;
1187
1188	private final static int READY = 2;
1189
1190	private final static int ERROR = 3;
1191
1192	// icons
1193	private final static ImageIcon ICON_ANIM = new ImageIcon(InfoViewer.class
1194		.getResource("images/fish_anim.gif"));
1195
1196	private final static ImageIcon ICON_NOANIM = new ImageIcon(InfoViewer.class
1197		.getResource("images/fish.gif"));
1198
1199	private final static ImageIcon ICON_CHECK = new ImageIcon(InfoViewer.class
1200		.getResource("images/checkmenu_check.gif"));
1201
1202	private final static ImageIcon ICON_NOCHECK = new ImageIcon(InfoViewer.class
1203		.getResource("images/checkmenu_nocheck.gif"));
1204
1205	// infoviewer actions
1206	private InfoViewerAction aOpenFile;
1207
1208	private InfoViewerAction aOpenBuffer;
1209
1210	private InfoViewerAction aEditURL;
1211
1212	private InfoViewerAction aReload;
1213
1214	private InfoViewerAction aClose;
1215
1216	private InfoViewerAction aCopy;
1217
1218	private InfoViewerAction aSelectAll;
1219
1220	private InfoViewerAction aBack;
1221
1222	private InfoViewerAction aOpenLocation;
1223
1224	private InfoViewerAction aForward;
1225
1226	private InfoViewerAction aHome;
1227
1228	private InfoViewerAction aBookmarksAdd;
1229
1230	private InfoViewerAction aBookmarksEdit;
1231
1232	private InfoViewerAction aAbout;
1233
1234	protected ToggleSidebar aToggleSidebar;
1235
1236	private infoviewer.actions.follow_link aFollowLink;
1237
1238	// gui elements
1239	private JLabel status;
1240
1241	private JLabel title;
1242
1243	private EnhancedJEditorPane viewer;
1244
1245	protected HistoryTextField urlField;
1246
1247	private JButton bStartStop;
1248
1249	private JMenu mGoto;
1250
1251	private JMenu mBmarks;
1252
1253	private JMenu mHelp;
1254
1255	// misc
1256	private View view;
1257
1258	private TitledURLEntry currentURL;
1259
1260	private int currentStatus;
1261
1262	private Bookmarks bookmarks;
1263
1264	private History history;
1265
1266	private URLButtonHandler bookmarkhandler;
1267
1268	private URLButtonHandler historyhandler;
1269
1270	private boolean isDocked;
1271
1272	private Timer periodicTimer;
1273
1274	private int periodicDelay;
1275
1276	private int previousScrollBarValue;
1277
1278	protected String baseURL;
1279
1280	private String shortURL;
1281
1282	private ActionContext actionContext;
1283
1284	private ActionSet actionSet;
1285
1286	private class URLButtonHandler implements ActionListener
1287	{
1288		private boolean addToHistory = true;
1289
1290		public URLButtonHandler(boolean addToHistory)
1291		{
1292			this.addToHistory = addToHistory;
1293		}
1294
1295		/**
1296		 * A bookmark was selected in the Bookmarks menu. Open the
1297		 * corresponding URL in the InfoViewer. The URL will be added to
1298		 * the history, if this URLButtonHandler was initialized with
1299		 * <code>addToHistory = true</code>.
1300		 */
1301		public void actionPerformed(ActionEvent evt)
1302		{
1303			String cmd = evt.getActionCommand();
1304			gotoURL(cmd, addToHistory, -1);
1305		}
1306	}
1307
1308	private class MouseHandler extends MouseAdapter
1309	{
1310		JPopupMenu popup = null;
1311
1312		public void mousePressed(MouseEvent evt)
1313		{
1314			if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) != 0)
1315			{
1316				evt.consume();
1317
1318				AccessibleText txt = viewer.getAccessibleContext()
1319					.getAccessibleText();
1320
1321				if (txt != null && txt instanceof AccessibleHypertext)
1322				{
1323					AccessibleHypertext hyp = (AccessibleHypertext) txt;
1324					int charIndex = hyp.getIndexAtPoint(evt.getPoint());
1325					int linkIndex = hyp.getLinkIndex(charIndex);
1326					if (linkIndex >= 0)
1327					{
1328						// user clicked on a link
1329						aFollowLink.setEnabled(true);
1330						aFollowLink.setClickPoint(evt.getPoint());
1331					}
1332					else
1333						aFollowLink.setEnabled(false);
1334				}
1335
1336				JPopupMenu popup = getPopup();
1337				popup.show(viewer, evt.getX() - 1, evt.getY() - 1);
1338			}
1339		}
1340
1341		private JPopupMenu getPopup()
1342		{
1343			if (popup == null)
1344			{
1345				popup = new JPopupMenu();
1346				popup.add(aBack);
1347				popup.add(aForward);
1348				popup.addSeparator();
1349				popup.add(aEditURL);
1350				popup.add(aOpenBuffer);
1351				popup.add(aReload);
1352				popup.addSeparator();
1353				popup.add(aFollowLink);
1354			}
1355			return popup;
1356		}
1357	}
1358
1359
1360	public class ArrowKeyHandler
1361	{
1362		int[] keys;
1363
1364		public ArrowKeyHandler()
1365		{
1366			keys = new int[] { KeyEvent.VK_UP, KeyEvent.VK_DOWN, KeyEvent.VK_LEFT,
1367				KeyEvent.VK_RIGHT, KeyEvent.VK_HOME, KeyEvent.VK_END,
1368				KeyEvent.VK_PAGE_UP, KeyEvent.VK_PAGE_DOWN };
1369			Arrays.sort(keys);
1370		}
1371
1372		public void processKeyEvent(KeyEvent e)
1373		{
1374			if (Arrays.binarySearch(keys, e.getID()) > -1)
1375			{
1376				scrViewer.processKeyEvent(e);
1377				e.consume();
1378			}
1379		}
1380
1381	}
1382
1383	class KeyHandler extends KeyAdapter
1384	{
1385		public void keyPressed(KeyEvent evt)
1386		{
1387			if (evt.getKeyCode() == KeyEvent.VK_ESCAPE)
1388			{
1389				dismiss();
1390				evt.consume();
1391			}
1392		}
1393	}
1394
1395	class MyScrollPane extends JScrollPane
1396	{
1397		public MyScrollPane(JComponent c)
1398		{
1399			super(c);
1400		};
1401
1402		public void processKeyEvent(KeyEvent e)
1403		{
1404			super.processKeyEvent(e);
1405		}
1406
1407		private static final long serialVersionUID = 390984816401470412L;
1408	}
1409
1410	public static final long serialVersionUID = 1236527;
1411
1412}