public TipWindow(Window owner, FocusableTip ft, String msg) {

		super(owner);
		this.ft = ft;
		// Render plain text tool tips correctly.
		if (msg!=null && msg.length()>=6 &&
				!msg.substring(0,6).toLowerCase().equals("<html>")) {
			msg = "<html>" + RSyntaxUtilities.escapeForHtml(msg, "<br>", false);
		}
		this.text = msg;
		tipListener = new TipListener();

		JPanel cp = new JPanel(new BorderLayout());
		cp.setBorder(TipUtil.getToolTipBorder());
		cp.setBackground(TipUtil.getToolTipBackground());
		textArea = new JEditorPane("text/html", text);
		TipUtil.tweakTipEditorPane(textArea);
		if (ft.getImageBase()!=null) { // Base URL for images
			((HTMLDocument)textArea.getDocument()).setBase(ft.getImageBase());
		}
		textArea.addMouseListener(tipListener);
		textArea.addHyperlinkListener(new HyperlinkListener() {
			public void hyperlinkUpdate(HyperlinkEvent e) {
				if (e.getEventType()==HyperlinkEvent.EventType.ACTIVATED) {
					TipWindow.this.ft.possiblyDisposeOfTipWindow();
				}
			}
		});
		cp.add(textArea);

		setFocusableWindowState(false);
		setContentPane(cp);
		setBottomPanel(); // Must do after setContentPane()
		pack();

		// InputMap/ActionMap combo doesn't work for JWindows (even when
		// using the JWindow's JRootPane), so we'll resort to KeyListener
		KeyAdapter ka = new KeyAdapter() {
			public void keyPressed(KeyEvent e) {
				if (e.getKeyCode()==KeyEvent.VK_ESCAPE) {
					TipWindow.this.ft.possiblyDisposeOfTipWindow();
				}
			}
		};
		addKeyListener(ka);
		textArea.addKeyListener(ka);

		// Ensure only 1 TipWindow is ever visible.  If the caller does what
		// they're supposed to and only creates these on the EDT, the
		// synchronization isn't necessary, but we'll be extra safe.
		synchronized (TipWindow.class) {
			if (visibleInstance!=null) {
				visibleInstance.dispose();
			}
			visibleInstance = this;
		}

	}